go的html/template默认转义可防xss,但template.html、手动拼接html或前端innerhtml等操作会绕过防护;需全程控制输入过滤、输出编码、csp头及敏感数据处理。

Go 的 html/template 默认已转义,但手动拼接 HTML 仍会绕过防护
Gin 使用 Go 标准库的 html/template 渲染模板时,{{.FieldName}} 语法默认执行 HTML 转义,这是防止 XSS 的第一道防线。但一旦你用 template.HTML 类型、template.Must 或 strings.Replace 等方式“手动拼 HTML”,转义就失效了。
- 错误写法:
c.HTML(http.StatusOK, "page.tmpl", gin.H{"content": template.HTML("<script>alert(1)</script>")})—— 直接注入可执行脚本 - 安全写法:始终让数据走
{{.content}}模板变量,不包装成template.HTML,除非你明确控制内容且已做净化 - 若必须渲染富文本,用专用库如
bluemonday做白名单过滤,而不是简单strings.Replace替换<script></script>
c.Query、c.PostForm 等获取的用户输入,不能直接塞进 HTML 或 JS 上下文
前端传来的 name=John%3Cscript%3Ealert(1)%3C/script%3E,用 c.Query("name") 拿到的是原始字符串,Gin 不会自动过滤它。如果你把它插进 innerHTML、document.write 或内联 <script></script>,就等于开了 XSS 后门。
- 服务端渲染时:一律走
{{.name}},别用{{printf "%s" .name | safeJS}}这类自定义函数,除非你清楚每一步的转义规则 - 返回 JSON 给前端时:Gin 的
c.JSON默认不转义 HTML 实体,但浏览器解析 JSON 不会执行脚本;风险在于前端用eval()或innerHTML = data.name - 真正危险的是把用户输入拼进 JS 字符串字面量,比如:
var name = "{{.name}}";—— 此时.name中的"或\会破坏结构,应改用JSON.stringify包裹
Cookie 和 Session 数据也要防 XSS,尤其 HttpOnly=false 时
如果设置了 HttpOnly=false 的 Cookie(比如存 token 或用户昵称),而前端 JavaScript 又读取并插入 DOM,那攻击者只要能 XSS 就能窃取或篡改这些值。这不是 Gin 的错,但它是常见漏洞链的一环。
- 敏感 Cookie 必须设
HttpOnly=true,例如登录态:c.SetCookie("session_id", sid, 3600, "/", "example.com", true, true) - 非敏感但需前端读取的字段(如语言偏好),避免存 HTML 片段;若必须存,前端读取后也要走
textContent而非innerHTML - Session 中存储的用户输入(如昵称、签名),渲染前同样要过
template.HTMLEscapeString,不要依赖“只存一次、不验证”
前端配合不可少:CSP 头比任何框架层防护都更硬核
Gin 本身不生成 CSP(Content Security Policy)响应头,但你可以用中间件加。没有 CSP,就算模板转义再严,攻击者也能通过 <img src="https://img.php.cn/?x-oss-process=image/resize,p_40" alt="Gin框架防止跨站脚本攻击"> 或劫持外链资源触发 XSS。
- 最简有效配置:
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'") - 禁用
'unsafe-inline'后,所有内联<script></script>和onclick都失效,逼你用外部 JS 文件和事件委托,大幅降低风险 - 搭配
nonce或hash支持必要内联脚本,但 Gin 不自动管理 nonce,需你自己生成并透传到模板
{{.x}},管不住你手写的 template.HTML、前端乱拼 DOM、或者漏掉 CSP。真正难的不是写对一行代码,而是整条链路上每个环节都守住边界。











