用 html/template 替代 text/template 是防 XSS 的底线要求,因其默认 HTML 转义;富文本需用 bluemonday 白名单净化,禁用正则过滤。

用 html/template 替代 text/template 是底线
Go 里防 XSS 最基础也最关键的一步,就是确保所有 HTML 渲染都走 html/template。它不是“可选优化”,而是硬性要求——text/template 完全不转义,任何用户输入塞进去都会原样输出,等于直接给攻击者开后门。
常见错误包括:在 Gin/Echo 中误用 c.String() 或 c.Render() 拼接用户数据;或自定义模板引擎时没继承 html/template 的转义逻辑。
- 必须显式 import
"html/template",不能只 import"text/template" - 模板中所有变量插值(如
{{.Name}}、{{.Content}})默认按 HTML 上下文转义 - 若需插入可信 HTML(如富文本),必须先净化再显式标记:
{{.SafeHTML | safeHTML}},且safeHTML要是自定义的 func,不能直接 cast 成template.HTML
富文本场景必须用 bluemonday 白名单过滤
当业务允许用户发带格式的内容(比如文章、评论里的 <b>、<a>),html/template 的自动转义会把所有标签干掉,这时不能退回到手动拼接或信任 template.HTML,而要引入白名单净化。
bluemonday 是目前 Go 社区最成熟的选择,它不靠黑名单(容易绕过),而是只保留明确允许的标签和属性。
立即学习“go语言免费学习笔记(深入)”;
- 用
bluemonday.UGCPolicy()支持常见排版标签(<p>、<strong>、<a href>),但自动剥离onclick、onload、javascript:等危险属性 - 不要自己写正则去删
<script>——正则无法正确解析嵌套/变体(如<scr<script>ipt>>)</script>ipt>> - 净化动作必须放在入库前或渲染前,不能只做一次缓存;如果内容被二次编辑,需重新净化
JSON API 也要防 XSS:响应头 + Content-Type 缺一不可
很多人以为 JSON 接口不会触发 XSS,但浏览器 MIME 嗅探可能把 application/json 当成 HTML 解析——尤其当响应体开头是 <script> 或包含 HTML 标签时。
这不是理论风险,真实漏洞已在多个生产系统复现过。
- 务必设置
w.Header().Set("Content-Type", "application/json; charset=utf-8") - 加
w.Header().Set("X-Content-Type-Options", "nosniff")关闭 MIME 嗅探 - 避免在 JSON 字段里直接塞未过滤的用户输入——比如
{"message": "<img src="https://img.php.cn/" alt="如何构建Go语言安全防御XSS模块">"},即使前端用JSON.parse,某些老浏览器或调试工具仍可能执行 - 对返回给前端的字符串字段,可在中间件统一调用
html.EscapeString()(仅限纯文本字段,不适用于已结构化数据)
CSP 头不能只写 script-src 'self'
Content Security Policy 是最后一道防线,但它极易配错。只写 script-src 'self' 看似安全,实际几乎无效——因为现代前端框架(React/Vue)大量依赖内联事件或 eval(),而 CSP 默认禁止这些,结果要么关掉 CSP,要么放宽到 'unsafe-inline',等于形同虚设。
真正可用的 CSP 必须配合应用实际行为来定制。
- 优先启用
default-src 'none',再逐个放开必要资源(如img-src 'self' https:) - 禁用
unsafe-inline和unsafe-eval;改用 nonce 或 hash 方式加载内联脚本 - 对第三方 JS(如统计、埋点)用
script-src明确限定域名,而非宽泛的https: - CSP 报告地址(
report-uri)要单独部署接收端,否则报错日志全丢
最常被忽略的点是:XSS 防护不是某个中间件 or 一个包的事,它是贯穿输入校验、存储、输出编码、HTTP 头配置的完整链路。任何一个环节松动(比如数据库里存了未净化的富文本,又忘了在模板里调用 bluemonday),整条防线就失效。


















