html/template自动转义仅防纯文本xss,富文本、属性值、js上下文需bluemonday白名单过滤;html.escapestring只转5字符且无上下文感知,无法防御javascript:、onerror、编码绕过等攻击。

html/template 的自动转义只防纯文本插入,富文本、属性值、JS 上下文里照样被 XSS 绕过,必须额外用 bluemonday 做白名单过滤。
为什么 html.EscapeString 和模板 {{.X}} 不够用
它只转义 、<code>>、"、'、& 这 5 个字符,但 XSS 可以绕开:
- 写进
href="javascript:alert(1)"或onerror="..."属性里,转义后仍可执行 - 输入
data:text/html,<script>alert(1)</script>,EscapeString完全不处理 - 大小写混写(
oNcLick)、URL 编码(%6F%6E%63%6C%69%63%6B)、空格/换行绕过都逃得掉 - 模板里用
{{.Raw | safeHTML}}直接禁用转义,等于把门敞开
怎么用 bluemonday 做白名单过滤
别写正则、别搞黑名单删除,直接用现成策略:
- 普通用户评论/简介 →
bluemonday.UGCPolicy()(允许a、strong、em,禁掉所有事件属性和javascript:、data:协议) - 后台富文本编辑器 →
bluemonday.StrictPolicy()(连style都默认删,更保守) - 自定义时用
AllowAttrs("href").OnElements("a")显式声明,而不是StripAttrs("onerror")这种被动删除 - 过滤必须在服务端做,不能只靠前端
DOMPurify—— 后端没拦住,存储型 XSS 就已落地
模板里千万别用 safeHTML 放行未过滤内容
这是最常见也最危险的误操作:
-
c.HTML(http.StatusOK, "page.tmpl", gin.H{"content": rawInput})→ 模板里写{{.content | safeHTML}}= 直接执行 - 正确流程是:接收输入 →
bluemonday.Policy.Sanitize(rawInput)→ 存入 DB 或传给模板 → 模板里用{{.sanitized}}(不加safeHTML) - 如果用了
safeHTML,又没提前过滤,等于把<script></script>原样塞进 HTML,浏览器照单全收 - Gin 的
HTML方法本身不介入内容清洗,它只负责把字符串塞进模板并触发html/template的默认转义逻辑
真正难的不是调用 bluemonday,而是判断哪些字段属于富文本上下文、哪些只是普通字段;同一个字段在不同页面位置(比如既出现在 <p></p> 里,又拼进 <a href="%7B%7B.url%7D%7D"></a>)需要不同策略,漏一处就可能被绕过。











