text/template 对 HTML 一无所知,把用户输入原样塞进页面就是 XSS。html/template 按 {{.}} 所在的位置(HTML 文本、属性、URL、JS、CSS)选择转义方式。这篇把同一个恶意输入放进不同位置,逐个看它被改成了什么,以及哪些写法会让保护失效。
[HTML 文本] html/template: <p><script>alert(1)</script></p>
[带引号的属性值] html/template: <a title="" onmouseover="alert(1)">x</a>
[不带引号的属性值] html/template: <a title=x onmouseover=alert(1)>x</a>
[href 里的 URL(危险协议)] html/template: <a href="#ZgotmplZ">x</a>
[href 里的 URL(正常)] html/template: <a href="https://example.com/a%20b?x=1&y=2">x</a>
[URL 查询参数] html/template: <a href="/s?q=a%26b%3d%3cc%3e">x</a>
[JavaScript 里的字符串] html/template: <script>var s = "\u003c/script\u003e\u003cscript\u003ealert(1)";</script>
[JavaScript 里的数字] html/template: <script>var n = 42 ;</script>
[JavaScript 里的对象] html/template: <script>var o = {"a":1,"b":"\u003cx\u003e"};</script>
[CSS 属性值] html/template: <p style="color: ZgotmplZ">x</p>
[HTML 注释里] html/template: x
对比:text/template 处理 HTML 文本: <p><script>alert(1)</script></p>同一个 {{.}},在 HTML 文本里是 < 实体,在属性值里是 ",在 URL 里是百分号编码,在 JS 里是字符串字面量或 JSON,在 CSS 里不安全的值直接变成 ZgotmplZ。text/template 则全部原样输出。
传 string:<p><b>粗体</b></p>
传 template.HTML(声明“这段已经安全”):<p><b>粗体</b></p>
传 template.HTML 但里面有脚本:<p><script>alert(1)</script></p>(调用方负责保证它安全)
传 template.URL("javascript:alert(1)") 到 href:<a href="javascript:alert%281%29">x</a>传 template.HTML 就会原样输出——哪怕里面是 <script>;传 template.URL 会跳过协议检查。这些类型是让你放入已经由你自己净化过的内容,不是让你把用户输入强转成它。
分支让上下文不一致: ← 执行出错:html/template:t:1:5: {{if}} branches end in different contexts: {stateURL delimDoubleQuote urlPartNone jsCtxRegexp [] at…
在 <script> 里拼 JS 标识符:<script>var "x" = 1;</script>
JS 反引号模板字符串里的 {{.}}(值是 a`b):<script>var s = `a\u0060b`;</script>{{if}} 的两个分支把模板带到不同的上下文,Execute 不会猜,而是报错并且不输出任何内容。把 {{.}} 当 JS 标识符用也不行,值只会变成字符串。
已经 Execute 过的模板再 Parse:html/template: cannot Parse after Execute
转义分析是在第一次 Execute 时完成的,之后再 Parse 会返回 cannot Parse after Execute。
This package wraps text/template so you can share its template API to parse and execute HTML templates safely. tmpl, err := template.New("name").Parse(...) // Error checking elided err = tmpl.Execute(out, data) If successful, tmpl will now be injection-safe.文档的承诺是:Parse 和 Execute 都成功后,模板就是防注入的——但这个承诺不包括上面的绕过类型。