html/template
包是对 text/template 包的二次封装,增加了一些安全性的处理,核心的接口和逻辑都是一样的。
安全性
渲染模板技术一直存在跨站脚本攻击的风险,本质上是网站将用户的输入不作转义写入生成的页面中,如果用户提交一段浏览器脚本,则会在用户的页面中执行,进而产生不可预知的风险。
html/template
自动开启安全模式将需要编码的数据处理成纯文本,各种不同的转义上下文(escaping contextual)可以安全的嵌入 HTML 模板,如 JavaScript、CSS 和 URI 上下文。
举个例子:
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>!
JavaScript 上下文被成功转义。
上下文
包内有这几种上下文: HTML、CSS、JavaScript 和 URI,在不同的上下文中会自动添加不同的转义函数。
<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 <i>you</i>?
<a title='{{.}}'> O'Reilly: How are you?
<a href="/{{.}}"> O'Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}"> O'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 模板,最终会生成前端页面,为了保证安全性,在内部将不同的字符串识别成不同的上下文,对每种上下文会自动添加不同的转义函数对不同的内容进行转义,如果不想内容被转义,可以把纯文本使用各种类型函数包装,包装过的纯文本会被转义函数忽略。