Go 语言标准库 html/template 包深刻浅出

html/template 包是对 text/template 包的二次封装,增长了一些安全性的处理,核心的接口和逻辑都是同样的。html

安全性

渲染模板技术一直存在跨站脚本攻击的风险,本质上是网站将用户的输入不做转义写入生成的页面中,若是用户提交一段浏览器脚本,则会在用户的页面中执行,进而产生不可预知的风险。前端

html/template 自动开启安全模式将须要编码的数据处理成纯文本,各类不一样的转义上下文(escaping contextual)能够安全的嵌入 HTML 模板,如 JavaScript、CSS 和 URI 上下文。json

举个例子:api

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

输出:

Hello, <script>alert('you have been pwned')</script>!
复制代码

能够看到 text/template 包中的 JavaScript 上下文没有被转义。浏览器

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
复制代码

输出:安全

Hello, <script>alert('you have been pwned')</script>!bash

JavaScript 上下文被成功转义。函数

上下文

包内有这几种上下文: HTML、CSS、JavaScript 和 URI,在不一样的上下文中会自动添加不一样的转义函数。post

<a href="/search?q={{.}}">{{.}}</a>网站

在解析时,每个 {{.}} 根据所处的上下文,都会被添加转义函数。

<a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>

URL 上下文中的添加 urlescaper 和 attrescaper 转义函数。

HTML 上下文中的添加 htmlescaper 转义函数

假设 {{.}} 的字符串表示为 O'Reilly: How are <i>you</i>? 其中包含多个有害字符,下面是在不一样上下文中的不一样转义结果:

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;? <a title='{{.}}'> O&#39;Reilly: How are you? <a href="/{{.}}"> O&#39;Reilly: How are %3ci%3eyou%3c/i%3e? <a href="?q={{.}}"> O&#39;Reilly%3a%20How%20are%3ci%3e...%3f <a onx='f("{{.}}")'> O\x27Reilly: How are \x3ci\x3eyou...? <a onx='f({{.}})'> "O\x27Reilly: How are \x3ci\x3eyou...?" <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f 复制代码

能够发现,'、<、>、? 在不一样的上下文中会被转义成不一样的字符编码。

若是 {{.}} 只包括无害字符,如字符串 left,则不会进行任何转义。

Context                              {{.}} After
{{.}}                                left
<a title='{{.}}'>                    left
<a href='{{.}}'>                     left
<a href='/{{.}}'>                    left
<a href='?dir={{.}}'>                left
<a style="border-{{.}}: 4px">        left
<a style="align: {{.}}">             left
<a style="background: '{{.}}'> left <a style="background: url('{{.}}')>  left
<style>p.{{.}} {color:red}</style>   left
复制代码

没有任何字符串能够在 JavaScript 上下文中使用。

若是 {{.}} 等于结构体 struct{A, B string}{ "foo", "bar" } 在模板 <script>var pair = {{.}};</script> 会渲染成:

<script>var pair = {"A": "foo", "B": "bar"};</script>

也就是将结构体用 json 包 marshaled 序列化以后嵌入 JavaScript 上下文中。

避免转义的方法

默认状况下,html/template 包假设全部的流(pipeline)提供纯文本的输入。添加转义流阶段必须正确和安全的嵌入不一样上下文的纯文本流中。

当一个数据不是纯文本时,须要确保不会对它进行转义。

类型 HTML,JS,URL 和其余来自 content.go 的类型能够避免转义,由于它们不是纯文本!

说白了就是传入的时候不传传文本,使用各类类型的函数包装一下再传递。

避免转义的方法:

// 模板
Hello, {{.}}!

// 使用 template.HTML 包装
tmpl.Execute(out, template.HTML(`<b>World</b>`))

输出:

// 没有被转义
Hello, <b>World</b>!


// 添加转义函数
{{ html .HTMLContent }}
复制代码

类型函数有这么几种:CSS、HTML、HTMLAttr、JS、JSStr、URL 和 Srcset。

总结

由于是 HTML 模板,最终会生成前端页面,为了保证安全性,在内部将不一样的字符串识别成不一样的上下文,对每种上下文会自动添加不一样的转义函数对不一样的内容进行转义,若是不想内容被转义,能够把纯文本使用各类类型函数包装,包装过的纯文本会被转义函数忽略。

参考资料

相关文章
相关标签/搜索