必须检查二进制内容:读前1024字节确认含<?xml或<svg,用xml.NewDecoder解析并捕获SyntaxError,禁用<script、onload等关键词,重命名存储并返回时设Content-Type: image/svg+xml。

上传时怎么判断文件真是SVG而不是伪装的JS
Gin默认只读Content-Type,但攻击者在BurpSuite里把Content-Type: text/html改成Content-Type: image/svg+xml就能骗过基础校验。真判断必须看二进制内容——SVG文件开头一定是XML声明或<svg标签,且必须符合XML语法结构。
实操建议:
- 用
http.Request.Body读取前1024字节,检查是否包含<?xml或<svg(注意命名空间可能带xmlns=) - 调用
xml.NewDecoder尝试解析,捕获xml.SyntaxError——合法SVG能过,恶意拼接的JS会直接报错 - 禁止
<script、onload=、onerror=、<foreignObject等关键词出现在XML文本中(注意大小写不敏感匹配) - 别依赖
file.Header里的mime.TypeByExtension,.svg后缀可被伪造,且某些CDN会重写Content-Type
怎么安全地保存和返回用户上传的SVG
存下来不等于安全。很多团队把SVG当图片处理,用img标签加载,结果服务端返回时用了text/html响应头,浏览器就把它当HTML执行了。
实操建议:
- 存储前重命名:用
uuid.NewString()生成随机名,去掉原始.svg后缀,存为xxx.bin或直接存入对象存储(如MinIO),避免Web服务器误解析 - 返回时强制指定
Content-Type: image/svg+xml,并加Content-Disposition: inline,禁用X-Content-Type-Options: nosniff(防止浏览器MIME sniffing) - 前端加载必须用
<img src="...">,禁用<object>、<iframe>、innerHTML = svgString——后者是XSS高发区 - 如果业务必须内联SVG(比如图表渲染),先用
DOMParser解析,再遍历移除所有on*属性和<script>节点,最后用element.innerHTML = sanitizedSVG
Gin中间件里怎么过滤SVG中的危险内容
不能只在路由 handler 里做校验,得在文件解析前就拦截。Gin 的c.Request.MultipartForm是惰性加载的,等你调c.FormFile时文件才真正读入内存——这时候再过滤已经晚了。
实操建议:
- 写一个自定义中间件,在
c.Request.ParseMultipartForm(32 之后、<code>c.FormFile之前介入 - 用
c.MultipartForm.File["file"][0].Open()拿到multipart.File,用io.LimitReader限制最多读1MB,避免OOM - 对读出的内容做三重过滤:
strings.Contains扫关键词、xml.Unmarshal验证结构、正则/on\w+=/i匹配事件处理器 - 发现任意一项不合规,立即
c.AbortWithStatusJSON(400, gin.H{"error": "invalid svg content"})并return,不进后续handler
为什么getimagesize()在Gin里根本不能防SVG XSS
PHP的getimagesize()在Gin里没有对应物,但有些开发者会自己实现类似逻辑:读文件头、提取宽高、判断是不是“图像”。这完全无效——SVG的宽高信息藏在XML属性里,而攻击者可以把<svg width="100" height="100" onload="alert(1)">照常写进去,getimagesize()照样返回[100, 100, 2](2代表SVG类型)。
更危险的是,Go里有人用image.DecodeConfig去解析SVG,但标准库image/svg包压根不存在,image.DecodeConfig对SVG文件会直接返回unknown format错误,导致校验流程中断或被绕过。
关键点在于:SVG不是位图,它是XML文档。任何试图用“图像解析”思路来校验SVG的行为,本质就是错把富文本当二进制资源处理。真正的防线只能落在XML结构解析和语义过滤上,而不是宽高、魔数或MIME头。


















