ssti在gin中非默认风险,但误用html/template(如template.html、动态parse、未校验子模板名、危险funcmap)会绕过自动转义导致xss或任意go表达式执行;需通过前置数据净化、禁用动态解析、函数白名单等中间件策略防御。

模板注入(SSTI)在 Gin 的服务端渲染中不是默认风险,但一旦你用 html/template 渲染用户可控数据且未正确转义,就可能触发 XSS 或更隐蔽的模板执行——尤其当开发者误用 {{.RawHTML}}、template.HTML 或拼接字符串后调用 template.Parse 时。
为什么 html/template 默认不防住所有注入场景
Gin 的 c.HTML() 底层依赖 Go 标准库 html/template,它确实会自动转义变量插值(如 {{.Name}}),但以下情况会绕过保护:
- 显式调用
template.HTML("...")并传入用户输入,等同于告诉模板“这段是可信 HTML”,浏览器直接执行 - 用
template.New(...).Parse(...)动态解析用户提交的模板字符串(例如 CMS 后台允许编辑模板) - 在模板中使用
{{template "xxx" .}}加载未校验的子模板名,导致路径遍历或加载恶意定义 - 将用户输入拼进
template.FuncMap中的函数逻辑(比如自定义urlEncode函数内部 eval 了参数)
这些都不是 Gin 框架本身的问题,而是模板使用方式越过了安全边界。
c.HTML() 调用前强制净化中间件
最稳妥的做法,是在进入 HTML 渲染前对所有待渲染的数据做白名单过滤。这不是靠模板引擎,而是靠提前清理上下文数据:
- 不要在
c.Set()或结构体字段里存原始 HTML 字符串;改用c.Set("safe_content", template.HTML(sanitized)),其中sanitized是经bluemonday或golang.org/x/net/html清洗后的结果 - 避免把
c.PostForm("content")直接塞进模板;先走一遍policy.Sanitize() - 若必须支持富文本,用专用字段区分:例如
ContentHTML(已清洗) vsContentRaw(禁止渲染) - 在中间件里检查
c.Keys和绑定结构体字段,发现含html、raw、unsafe等关键词的字符串值时打日志并拒绝(开发期可 panic,生产期 warn + 降级)
禁用动态模板解析的硬性拦截
如果你的应用不需要运行时加载/解析用户提交的模板(绝大多数业务都不需要),应在中间件中彻底封死这类行为:
- 拦截所有含
template.Parse、template.New、template.Must的调用栈(仅限调试环境启用) - 检查
c.Request.URL.Path是否匹配管理后台的模板编辑接口(如/admin/templates/edit),对该路由强制要求二次认证 + IP 白名单 - 在
init()阶段用template.New("base").Funcs(...)预定义全部合法函数,禁止后续用Funcs注册新函数 - 若用了
gin-contrib/multitemplate,确保Render.HTML()的name参数只接受预注册的模板名(如用 map 做白名单校验)
容易被忽略的关键点
真正危险的不是「模板能不能执行 JS」,而是「模板是否成为攻击者构造任意 Go 表达式的入口」。比如:{{.User.Data | printf "%s"}} 若 .Data 是用户可控字符串,printf 可能触发格式化漏洞;又比如自定义函数返回 reflect.Value 并暴露 Call 方法,就等于开放了反射执行。这些都发生在模板渲染阶段,但防御必须前置到数据准备和函数注册环节——中间件能拦的,永远是调用链起点,不是模板引擎内部。











