gorm自动防护仅覆盖参数化查询,db.raw()和db.exec()拼接sql时失效;中间件应检查query、form中高危关键字并短路拦截,但无法替代开发规范与最小权限原则。

为什么不能只靠GORM自动防护?
GORM 默认使用参数化查询,db.Where()、db.First() 等方法是安全的,但一旦用到 db.Raw() 或 db.Exec() 拼接 SQL 字符串,防护就失效了。很多业务场景绕不开原生 SQL:动态排序字段、复杂 UNION 查询、权限下推等。这时候 GORM 不再帮你做参数绑定,拼接 fmt.Sprintf("ORDER BY %s", c.Query("sort")) 就等于给攻击者开后门。
拦截中间件该检查什么?
中间件没法替你重写 SQL,但它能快速识别高危模式,在请求进入业务逻辑前截断明显恶意输入。重点不是“拦住所有注入”,而是“拦住批量扫描和低级 payload”。以下字段必须检查:
-
c.Query("sort")、c.Query("order"):禁止出现UNION、SELECT、FROM、WHERE、OR 1=1、; -
c.PostForm("search"):过滤'、"、--、#、/*、*/(除非业务明确允许) -
c.Param("id"):若本应为数字,却含字母或符号,直接c.AbortWithStatusJSON(400, gin.H{"error": "invalid id"})
怎么写一个轻量但有效的中间件?
别搞正则全家桶,也别试图解析 SQL 语法——那属于 WAF 范畴。用白名单 + 关键字短路即可:
func SQLInjectMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 检查 Query 参数
for key, values := range c.Request.URL.Query() {
for _, v := range values {
if strings.Contains(strings.ToLower(v), "union") ||
strings.Contains(strings.ToLower(v), "select") ||
strings.Contains(strings.ToLower(v), "insert") ||
strings.Contains(strings.ToLower(v), "drop") ||
strings.Contains(v, "'") || strings.Contains(v, "\"") {
c.AbortWithStatusJSON(400, gin.H{"error": "possible SQL injection detected"})
return
}
}
}
// 检查表单数据
c.Request.ParseForm()
for key, values := range c.Request.PostForm {
for _, v := range values {
if strings.Contains(strings.ToLower(v), "union") ||
strings.Contains(strings.ToLower(v), "exec") ||
strings.Contains(v, "--") || strings.Contains(v, "/*") {
c.AbortWithStatusJSON(400, gin.H{"error": "possible SQL injection detected"})
return
}
}
}
c.Next()
}
}
注意:strings.Contains 是粗粒度检测,它会误杀合法含 “union” 的英文名,所以只用于非业务关键字段(如搜索、排序)。对用户昵称、文章标题这类字段,应改用输入校验而非中间件拦截。
容易被忽略的盲区
中间件只管 HTTP 层输入,但真正危险的是业务代码里那些 db.Raw("SELECT * FROM users WHERE name = '" + name + "'")。Gin 中间件无法阻止这种写法,它只能降低攻击面。真正的防护重心仍在开发规范:
- 禁止在任何地方用
+或fmt.Sprintf拼接 SQL 字符串 - 所有
db.Raw()必须搭配sql.Named或?/arg占位符,例如db.Raw("SELECT * FROM articles WHERE status = ?", status) - 数据库账号权限严格限制,Web 应用账号禁用
DROP、ALTER、CREATE
中间件是兜底,不是保险丝。它拦不住精心构造的盲注或编码绕过,但能挡住 80% 的自动化扫描器——前提是,你得让它运行在所有可能触发原生 SQL 的路由上。











