Go防XSS核心是上下文感知输出:必须用html/template(非text/template),{{.Field}}自动按位置转义;富文本须先经bluemonday过滤再转template.HTML;html.EscapeString仅防基础XSS,HTTP头和CSP仅为补位。

Go 里防 XSS 的核心不是“怎么处理字符串”,而是“在哪、用什么上下文输出它”——html/template 自动转义是默认安全基线,手动调用 html.EscapeString 或正则清洗反而容易绕过或破坏防护逻辑。
必须用 html/template,别碰 text/template
这是最常踩的坑:只改模板后缀或变量写法,却忘了 import 包名。只要用了 text/template,哪怕插在 <p>{{.Content}}</p> 里,<script>alert(1)</script> 也会原样执行。
- 正确写法:
import "html/template",然后t := template.Must(template.New("t").Parse(tmpl)) - 错误写法:
import "text/template",或 import 对了但解析时误用text/template的函数 -
{{.Field}}在html/template下会按插入位置自动转义:HTML 内容区、属性值、<script>块、style属性各有一套规则
富文本必须走 bluemonday 过滤,不能靠 template.HTML 伪装安全
template.HTML 不是过滤器,是“我已确认这段 HTML 安全”的信任声明。用户提交 <img src=x onerror=alert(1)>,你包一层 template.HTML(userInput) 就等于主动放行。
- 安装:
go get github.com/microcosm-cc/bluemonday - 用
bluemonday.UGCPolicy()(不是StrictPolicy),它保留<p>、<strong>、<a href>等常用标签,但剔除onerror、javascript:、data:等危险内容 - 过滤必须在 handler 层完成:
clean := p.Sanitize(userInput),再传给模板:data := struct{ Content template.HTML }{template.HTML(clean)} - 模板里禁止出现
{{.Raw | safeHTML}}—— 过滤责任不在模板
别依赖 html.EscapeString,它只防最基础的 HTML 插入
html.EscapeString 只转义 <、>、"、'、& 这 5 个字符,对 href="javascript:alert(1)"、onload=eval(...)、style=expression(...) 完全无效。
立即学习“go语言免费学习笔记(深入)”;
- 能用场景:纯文本插入 HTML 内容区,比如
<p>{{.Name}}</p>,且.Name不进属性、不进 JS、不进 CSS - 不能用场景:拼进
<a href="{{.URL}}">→ 必须额外校验协议:strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "/") - 更不能用场景:拼进
<script>var url = "{{.URL}}";</script>→ 必须用json.Marshal输出到data-属性,前端再读取
HTTP 响应头只是补位,不是防线主体
CSP 和 X-Content-Type-Options 能缓解已注入的恶意 HTML 执行,但拦不住服务端拼错上下文导致的漏洞。它们不能替代 html/template 或 bluemonday。
- 必设:
w.Header().Set("X-Content-Type-Options", "nosniff"),防止浏览器把 JSON 或文本当 HTML 解析 - CSP 推荐:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'(若真需内联脚本) - 删掉
X-XSS-Protection:Chrome 78+、Firefox 76+ 已弃用,设了也无效
真正难的不是选哪个库,而是每次往模板里插变量前,都得问一句:它最终出现在哪?HTML 内容区?属性值?<script> 里?还是 JSON 响应体里?上下文错了,再严格的过滤也白搭。


















