SQL注入是用户输入被当作代码执行,防御关键在于用参数化查询分离数据与SQL结构,禁用字符串拼接,表名字段名等动态部分须白名单校验。

直接用 c.Query 或 c.PostForm 拿到的字符串拼进 SQL,等于把数据库大门钥匙交给用户——SQL 注入风险几乎 100%。防御的关键不是“怎么校验参数”,而是“根本别让参数进 SQL 字符串”。
用 GORM 或 database/sql 的参数化查询代替字符串拼接
SQL 注入的本质是用户输入被当作代码执行。只要把数据和 SQL 结构分开,就破不了。
- ✅ 安全写法:
db.Where("email = ? AND status = ?", email, status).First(&user)—— GORM 自动做参数绑定 - ✅ 原生 SQL 安全写法:
db.Query("SELECT * FROM users WHERE id > ? AND name LIKE ?", minID, "%"+keyword+"%") - ❌ 危险写法:
db.Query("SELECT * FROM users WHERE email = '" + email + "'")—— 任何转义、正则、长度限制都防不住绕过 - ❌ 更危险:
db.Raw("SELECT * FROM " + tableName + " WHERE id = " + id).Scan(&res)—— 表名、字段名不能参数化,必须白名单校验
Query 参数本身不需“防注入”,但必须做类型与范围校验
c.Query("limit") 返回的是 string,它不会导致 SQL 注入,但若你把它直接塞进 fmt.Sprintf("LIMIT %s", limit) 就会。所以校验目标不是“这个字符串干不干净”,而是“它能不能安全转成 int 并用于 LIMIT/OFFSET”。
- 先取值:
limitStr := c.Query("limit"),空字符串表示未传 - 再转整:
limit, err := strconv.ParseInt(limitStr, 10, 64),检查err != nil - 后约束:
if limit 100 { c.AbortWithStatusJSON(400, gin.H{"error": "limit must be between 1 and 100"}) } - 最后才用:
db.Limit(int(limit)).Find(&users)(GORM)或db.Query("... LIMIT ?", limit)(原生)
必须禁用 raw SQL 中的字符串拼接,尤其涉及表名、字段名、ORDER BY
占位符 ? 只能代入值,不能代入标识符(如表名、列名、ASC/DESC)。这类动态部分一旦来自用户输入,就必须走白名单机制。
- 比如排序字段:
sort := c.DefaultQuery("sort", "created_at"),但不能直接拼:"ORDER BY " + sort - 正确做法是预设允许值:
validSorts := map[string]bool{"created_at": true, "name": true, "score": true},再判断if !validSorts[sort] { ... } - 同理,表名、字段前缀、
GROUP BY子句都得走白名单或配置映射,不能靠正则“过滤掉分号”来蒙混过关
最常被忽略的一点:即使用了 GORM,手动调 db.Raw() 时仍会退回到裸 SQL 模式;而哪怕只在一个 handler 里写了一次字符串拼接,整个服务的安全水位就塌了一角。防御不是加一层“校验中间件”,而是从 ORM 调用习惯、SQL 构建方式、甚至团队 Code Review 清单里堵死所有拼接路径。











