Go 的 html/template 能防 XSS 是因为它基于上下文自动转义输出而非过滤输入:插在 HTML 文本、属性、JS 或 CSS 中时,分别采用对应规则转义,如 < 变 <、" 变 "、JS 中反斜杠 Unicode 转义等。

Go 的 html/template 为什么能防 XSS
它不是“过滤”输入,而是根据上下文自动转义输出——变量插进 HTML 标签内容、属性值、JS 字符串、CSS 里,转义规则完全不同。html/template 在渲染时识别这些上下文,把 变成 <code><、" 变成 "、javascript: 在属性中被截断,从而让恶意脚本无法执行。
关键点:
- 必须用
html/template,不能用text/template(后者不转义) - 模板中直接写
{{.UserInput}}才生效;如果手动拼接字符串再传入,就绕过了转义 - 对 JS 内联脚本、CSS 行内样式等高危上下文,它仍会严格处理,但无法防御
href="javascript:..."这类 URL 协议劫持(需额外校验)
Gin 中模板渲染的常见 XSS 漏洞点
很多问题出在“以为用了模板就安全”,实际却在几个环节主动绕过防护:
- 用
{{.RawHTML | safeHTML}}或template.HTML类型强制跳过转义——仅当内容绝对可信(如 CMS 后台富文本)才可用,且必须配合后端白名单过滤 - 把用户输入拼进
script标签内:比如<script>var name = "{{.Name}}";</script>——这里{{.Name}}会被转义,但若攻击者输入");alert(1);//,仍可能闭合引号触发执行;应改用json.Marshal输出并加application/json类型 - 从 URL 参数或表单取值后,没走模板、直接拼进
c.String()或c.JSON()返回给前端——这类响应不受html/template管控,必须手动校验或编码
为什么只靠 Gin 模板不够,还得配 CSP
html/template 解决的是“输出时转义”,但挡不住存储型 XSS(恶意脚本已存进数据库)、也防不了第三方 CDN 脚本被污染、更无法限制 eval() 或内联事件(onclick="...")。这时候需要 Content-Security-Policy 头补位。
在 Gin 中添加 CSP 的典型方式:
router.Use(func(c *gin.Context) {
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'; form-action 'self';")
c.Next()
})
- 务必禁用
'unsafe-inline'和'unsafe-eval',否则内联脚本和eval依然可执行 - 如果用了 Vue/React 等框架,可能需要
script-src 'self' 'unsafe-inline'——这不是推荐做法,而是先开Content-Security-Policy-Report-Only收集违规日志,再逐步改造代码移除内联 - CSP 不会阻止 DOM 型 XSS(纯前端漏洞),但它能大幅压缩攻击面,比如禁止
data:协议加载脚本、阻断onerror事件注入
别漏掉 Cookie 和 HTTP 头的协同防护
就算模板和 CSP 都到位,XSS 成功后仍可能窃取会话凭证——所以必须切断这条链路:
- 设置 Cookie 时加
HttpOnly和Secure标志:c.SetCookie("session_id", "...", 3600, "/", "example.com", true, true),这样 JS 就读不到 session - 返回头里补上
X-Content-Type-Options: nosniff,防止浏览器把text/plain当作text/html执行(尤其上传功能易踩坑) - 避免在错误页或调试信息中回显原始请求参数(比如 400 错误页显示
Invalid email: <script>...</script>),这类页面往往没走模板,容易成为反射型 XSS 入口
最麻烦的其实是混合场景:模板 + AJAX + 第三方 SDK + 用户上传富文本。这时候没有银弹,得一层层确认每个数据流动节点是否做了对应防护——比如上传后的 HTML 内容,不能只靠前端 sanitize,后端入库前必须用 bluemonday 之类库做白名单清洗。


















