直接用c.HTML渲染富文本会XSS,因c.HTML仅插值不清洗;须用bluemonday白名单过滤后转template.HTML,并在模板中用{{.content | safeHTML}}输出,同时校验img src协议与域名。

为什么直接用 c.HTML 渲染富文本会 XSS?
因为 c.HTML 仅做模板变量插值,不进行任何 HTML 内容清洗。如果用户提交了 <script>alert(1)</script>,它会被原样插入页面并执行。
用 bluemonday 预处理富文本再传入模板
Go 生态中推荐的方案是:在服务端接收富文本后、存入数据库前或渲染前,用 bluemonday 做白名单过滤,而不是依赖前端或模板引擎“转义”。
- 安装:
go get github.com/microcosm-cc/bluemonday - 定义策略(例如允许
、、及其常用属性):import "github.com/microcosm-cc/bluemonday" p := bluemonday.UGCPolicy() p.AllowAttrs("src", "alt", "width", "height").OnElements("img") p.AllowAttrs("class").OnElements("p", "ul", "li") - 在 handler 中清洗后再传给模板:
cleaned := p.Sanitize(formData.Content) c.HTML(http.StatusOK, "detail.tmpl", gin.H{ "content": template.HTML(cleaned), // 注意:必须显式转为 template.HTML 才不被自动转义 })
模板里不能写 {{.content}},得写 {{.content | safeHTML}}
Gin 默认使用 Go 标准 html/template,它会对所有 .xxx 插值自动 HTML 转义。即使你传的是已清洗过的字符串,若不显式声明“这是安全 HTML”,它仍会被当成纯文本渲染成
。
- 正确写法(在模板中):
{{.content | safeHTML}} - 错误写法:
{{.content}}或{{.content | html}}(后者是双重转义,会把变成) - 注意:
safeHTML是 Gin 内置函数,无需额外注册
别漏掉图片上传的 src 域名白名单校验
bluemonday 允许 不代表允许任意 URL。攻击者可能填入 javascript:alert(1) 或内网地址(如 http://192.168.1.100/admin)。
- 清洗前先检查
src是否符合预期协议和域名:if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { u, _ := url.Parse(src) if !slices.Contains([]string{"example.com", "cdn.example.com"}, u.Host) { src = "" // 拒绝非法域名 } } - 或者用
bluemonday的AllowURLSchemes限制协议:p.AllowURLSchemes("https", "http") - 更稳妥的做法:富文本中只存相对路径或 CDN 路径,后端渲染时统一拼接 base URL
Gin 模板安全输出富文本的关键不在“怎么渲染”,而在于“谁负责清洗”——必须由服务端在进入模板前完成可信清洗,且清洗策略要覆盖标签、属性、URL 协议与域名三重边界。漏掉任一环,safeHTML 就只是个危险的开关。











