Gin XSS过滤
XSS(跨站脚本攻击)是 Web 常见安全漏洞,Gin 框架需从输入、输出、响应头三方面综合防护。
XSS攻击原理
攻击者注入恶意脚本到网页,在用户浏览器执行,窃取 Cookie、Session 或进行恶意操作。
HTML
<!-- 攻击示例 -->
<script>alert(document.cookie)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
输入过滤
使用 validator 验证
Go
type CommentRequest struct {
Content string `form:"content" binding:"required,max=500"`
}
func PostComment(c *gin.Context) {
var req CommentRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(400, gin.H{"error": "参数错误"})
return
}
// 存储前进行清理
content := sanitizeInput(req.Content)
// 保存到数据库
}
HTML 标签过滤
Go
import "strings"
// 简单过滤危险标签
func sanitizeInput(input string) string {
// 移除 script 标签
input = strings.ReplaceAll(input, "<script", "<script")
input = strings.ReplaceAll(input, "</script>", "</script>")
// 移除事件属性
input = strings.ReplaceAll(input, "onerror", "data-onerror")
input = strings.ReplaceAll(input, "onclick", "data-onclick")
return input
}
使用专业库 bluemonday
Go
import "github.com/microcosm-cc/bluemonday"
func sanitizeHTML(input string) string {
// 严格策略:只允许纯文本
p := bluemonday.StrictPolicy()
return p.Sanitize(input)
}
func sanitizeHTMLAllowBasic(input string) string {
// 允许基本格式标签
p := bluemonday.UGCPolicy()
return p.Sanitize(input)
}
func PostComment(c *gin.Context) {
var req CommentRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(400, gin.H{"error": "参数错误"})
return
}
cleanContent := sanitizeHTML(req.Content)
// 保存清理后的内容
}
输出转义
JSON 响应自动转义
Go
func GetUser(c *gin.Context) {
user := User{
Name: "<script>alert(1)</script>",
}
c.JSON(200, user)
// 输出: {"name":"\u003cscript\u003ealert(1)\u003c/script\u003e"}
// Gin 的 JSON 方法自动转义特殊字符
}
HTML 模板转义
Go
import "html/template"
func RenderPage(c *gin.Context) {
data := gin.H{
"Content": "<script>alert(1)</script>",
}
c.HTML(200, "page.html", data)
}
<!-- 模板文件 page.html -->
<!-- 自动转义 -->
<div>{{.Content}}</div>
<!-- 输出: <div><script>alert(1)</script></div> -->
<!-- 需要原始输出时(确保内容安全) -->
<div>{{.Content | safe}}</div>
Content-Type 设置
Go
func GetAPI(c *gin.Context) {
// 明确设置 Content-Type,防止浏览器误解析
c.Header("Content-Type", "application/json; charset=utf-8")
c.JSON(200, gin.H{"data": "response"})
}
func DownloadFile(c *gin.Context) {
// 文件下载时设置正确类型
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", "attachment; filename=file.txt")
c.Data(200, "application/octet-stream", fileBytes)
}
Content-Security-Policy 头
Go
func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
// 禁止内联脚本和外部脚本
c.Header("Content-Security-Policy", "default-src 'self'; script-src 'self'")
// 防止 MIME 类型嗅探
c.Header("X-Content-Type-Options", "nosniff")
// 防止点击劫持
c.Header("X-Frame-Options", "DENY")
// XSS 保护
c.Header("X-XSS-Protection", "1; mode=block")
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(SecurityHeaders())
}
防护策略对比
| 层面 | 方法 | 作用 |
|---|---|---|
| 输入 | validator + bluemonday | 过滤危险标签和属性 |
| 输出 | JSON自动转义 + 模板转义 | 防止脚本执行 |
| 响应头 | CSP + X-XSS-Protection | 浏览器层面阻断 |
注意:XSS 防护需多层防御,不能只依赖单一措施。
要点总结
- 输入过滤:使用 bluemonday 清理 HTML 标签
- 输出转义:Gin JSON 响应自动转义,模板使用
{{.}} - 响应头:设置 CSP、X-Content-Type-Options、X-XSS-Protection
- 验证长度:限制输入长度减少攻击面
- 避免
| safe输出用户内容,除非明确安全
📝 发现内容有误?点击此处直接编辑