分享

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

 F2967527 2019-08-06

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

安全性

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

html/template 自动开启安全模式将需要编码的数据处理成纯文本,各种不同的转义上下文(escaping contextual)可以安全的嵌入 HTML 模板,如 JavaScript、CSS 和 URI 上下文。

举个例子:

  1. import 'text/template'
  2. ...
  3. t, err := template.New('foo').Parse(`{{define 'T'}}Hello, {{.}}!{{end}}`)
  4. err = t.ExecuteTemplate(out, 'T', '<script>alert('you have been pwned')</script>')
  5. 输出:
  6. Hello, <script>alert('you have been pwned')</script>!
  7. 复制代码

可以看到 text/template 包中的 JavaScript 上下文没有被转义。

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

输出:

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>? 其中包含多个有害字符,下面是在不同上下文中的不同转义结果:

  1. Context {{.}} After
  2. {{.}} O'Reilly: How are <i>you</i>?
  3. <a title='{{.}}'> O'Reilly: How are you?
  4. <a href='/{{.}}'> O'Reilly: How are %3ci%3eyou%3c/i%3e?
  5. <a href='?q={{.}}'> O'Reilly%3a%20How%20are%3ci%3e...%3f
  6. <a onx='f('{{.}}')'> O\x27Reilly: How are \x3ci\x3eyou...?
  7. <a onx='f({{.}})'> 'O\x27Reilly: How are \x3ci\x3eyou...?'
  8. <a onx='pattern = /{{.}}/;'> O\x27Reilly: How are \x3ci\x3eyou...\x3f
  9. 复制代码

可以发现,'、<、>、? 在不同的上下文中会被转义成不同的字符编码。

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

  1. Context {{.}} After
  2. {{.}} left
  3. <a title='{{.}}'> left
  4. <a href='{{.}}'> left
  5. <a href='/{{.}}'> left
  6. <a href='?dir={{.}}'> left
  7. <a style='border-{{.}}: 4px'> left
  8. <a style='align: {{.}}'> left
  9. <a style='background: '{{.}}'> left
  10. <a style='background: url('{{.}}')> left
  11. <style>p.{{.}} {color:red}</style> left
  12. 复制代码

没有任何字符串可以在 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 的类型可以避免转义,因为它们不是纯文本!

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

避免转义的方法:

  1. // 模板
  2. Hello, {{.}}!
  3. // 使用 template.HTML 包装
  4. tmpl.Execute(out, template.HTML(`<b>World</b>`))
  5. 输出:
  6. // 没有被转义
  7. Hello, <b>World</b>!
  8. // 添加转义函数
  9. {{ html .HTMLContent }}
  10. 复制代码

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

总结

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

参考资料

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多