html/template 包是对 text/template 包的二次封装,增加了一些安全性的处理,核心的接口和逻辑都是一样的。
安全性渲染模板技术一直存在跨站脚本攻击的风险,本质上是网站将用户的输入不作转义写入生成的页面中,如果用户提交一段浏览器脚本,则会在用户的页面中执行,进而产生不可预知的风险。 html/template 自动开启安全模式将需要编码的数据处理成纯文本,各种不同的转义上下文(escaping contextual)可以安全的嵌入 HTML 模板,如 JavaScript、CSS 和 URI 上下文。
举个例子: 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 上下文没有被转义。 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>? 其中包含多个有害字符,下面是在不同上下文中的不同转义结果: {{.}} 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 ,则不会进行任何转义。 <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 的类型可以避免转义,因为它们不是纯文本! 说白了就是传入的时候不传传文本,使用各种类型的函数包装一下再传递。 避免转义的方法: tmpl.Execute(out, template.HTML(`<b>World</b>`))
类型函数有这么几种:CSS、HTML、HTMLAttr、JS、JSStr、URL 和 Srcset。 总结因为是 HTML 模板,最终会生成前端页面,为了保证安全性,在内部将不同的字符串识别成不同的上下文,对每种上下文会自动添加不同的转义函数对不同的内容进行转义,如果不想内容被转义,可以把纯文本使用各种类型函数包装,包装过的纯文本会被转义函数忽略。 参考资料
|