html/template 默认防 XSS 因其对普通字符串自动 HTML 转义,使 alert(1) 变成文本显示;绕过方式包括显式用 template.HTML、| safeHTML 或手动拼接 HTML,均需严格清洗后才可信任。

html/template 默认就防 XSS,但前提是别绕过它——比如用 template.HTML 强转、或手动拼接 HTML 字符串再塞进模板里。
为什么默认渲染不会执行 JS?
Go 的 html/template 在解析 {{.}} 或 {{.Content}} 时,会自动对输出内容做 HTML 转义:<script>alert(1)</script> 会被变成 <script>alert(1)</script>,浏览器只当它是文本,不执行。
- 这个行为是模板引擎在编译阶段就绑定的,不是靠 runtime 检查
- 只要变量值是普通字符串(
string类型),且没被显式标记为“安全”,就一定转义 - 前端框架如 Vue 的
{{ }}和 Go 模板冲突时,别改 Go 的转义逻辑,应调用Delims改模板定界符,例如tpl.Delims("[[", "]]")
哪些操作会意外绕过转义?
真正危险的不是模板本身,而是开发者主动“解封”了输出。以下写法都会让 XSS 复活:
- 在 handler 里用
template.HTML(userInput)包装后传入模板 —— 这等于告诉模板:“信我,这是安全 HTML” - 模板内写
{{.RawHTML | safeHTML}}却没定义safeHTML函数,或函数只是原样返回 - 用
fmt.Sprintf拼 HTML 片段再传给模板,比如data := fmt.Sprintf(`<div onclick="alert(%q)">`, input) —— 转义发生在模板层,你拼的时候已经埋雷了 <h3>需要显示富文本时怎么办?</h3> <p>真有合法 HTML 需求(如后台编辑器内容),不能直接信任用户输入。必须走清洗流程:</p> <ul> <li>服务端用 <code>bluemonday或go-sanitize库白名单过滤,只保留<p></p>、<strong></strong>等安全标签 - 清洗后的结果再用
template.HTML()包裹传入模板 - 绝对不要在前端用
v-html直接渲染未清洗的后端字段 - 如果连清洗都做不到(比如纯客户端富文本),至少加
Content-Security-Policy: script-src 'none'响应头堵死 inline script 执行
最容易被忽略的一点:模板转义只管“输出”,不管“属性值”。比如 <a href="https://www.php.cn/link/8627a5992a7f67f8c1e3db85cf9c23eb">link</a>,如果 .URL 是 javascript:alert(1),照样触发 XSS。这种场景必须额外校验 URL 协议,或用 url.URL 解析后再取 .Scheme 判断是否为 http/https。


















