gorm中sql注入风险源于动态拼接字符串,安全方案是所有动态值必须使用参数化占位符(如?或$1),字段名、表名、排序字段等元信息需白名单校验,raw/scan需严格防范。

用 GORM 的 Where 和 First 等方法时,字符串拼接就是埋雷
直接把用户输入塞进 SQL 查询条件里,比如 db.Where("name = '" + r.URL.Query().Get("name") + "'").First(&user),GORM 会原样拼进去,根本不管单引号、分号或 OR 1=1。这不是“可能被注入”,是“必然被注入”。
真正安全的做法只有一条:所有动态值必须走参数化占位符。GORM 默认支持 ?(SQLite/MySQL)和 $1(PostgreSQL),底层由驱动自动处理转义和类型绑定。
- ✅ 正确:
db.Where("name = ?", name).First(&user)或db.Where("age > ? AND status = ?", minAge, status).Find(&users) - ❌ 错误:
db.Where(fmt.Sprintf("name = '%s'", name)).First(&user)—— 即使加了strings.ReplaceAll删单引号也没用,绕过方式太多 - ⚠️ 注意:
db.Where("name = ?", name).Order("id DESC")中,Order的参数不接受参数化,如果排序字段来自用户输入(如r.URL.Query().Get("sort")),必须白名单校验,不能直接拼
Scan 和 Raw 是高危区,别以为用了 GORM 就自动免疫
db.Raw() 和 db.Scan() 完全绕过 GORM 的查询构建器,直接交由数据库驱动执行字符串。这时候你写的 SQL 就是最终发送的 SQL,没有任何拦截或转义。
- ✅ 安全写法:
db.Raw("SELECT * FROM users WHERE id = ? AND deleted_at IS NULL", userID).Scan(&user) - ❌ 高危写法:
db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Scan(&user)—— 和手写database/sql一样危险 - ⚠️ 特别注意:
db.Table("users").Select("id, name").Where("status = ?", status).Scan(&result)看似用了Where,但Select的字段名如果动态生成(比如从 URL 取fields参数),也得白名单控制,SELECT *最安全
结构体绑定 + Where 混用时,字段名没过滤等于敞开大门
很多人用 r.URL.Query().Get("field") 动态构造查询字段,比如 db.Where(fmt.Sprintf("%s = ?", field), value).First(&u)。GORM 不校验字段名是否合法,只要数据库认,它就发过去。
立即学习“go语言免费学习笔记(深入)”;
- ❌ 常见漏洞点:
field是"email'; DROP TABLE users; --",拼出来就是email'; DROP TABLE users; -- = ?,MySQL 会执行前半句 - ✅ 解决方案只有两个:
– 字段名硬编码(如switch field { case "name": ... case "email": ... })
– 或用白名单 map:validFields := map[string]bool{"name": true, "email": true, "status": true},查不到就拒掉 - ⚠️
db.Where("created_at > ?", time.Now().AddDate(0, 0, -7)).Find(&users)这类时间范围没问题,但一旦字段名变成变量,就得立刻警觉
事务里嵌套 Raw 查询,错误处理不到位会导致残留风险
在 db.Transaction() 中混用 Raw 和普通 GORM 方法,如果 Raw 出错但没 return err,后续 GORM 操作可能照常提交,而前面的 Raw 已经改了数据——这种组合让 SQL 注入后果更难追溯。
- ✅ 必须显式检查:
err := db.Raw("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, userID).Error; if err != nil { return err } - ❌ 错误示范:
db.Raw("...").Error后没判断,接着调db.Create(...),万一前者失败后者成功,账就对不上 - ⚠️ PostgreSQL 下
Raw的$1占位符必须严格按顺序,错一位就会报sql: expected 1 arguments, got 2,但这个错误不会阻止 SQL 发送,只是驱动拒绝执行——别把它当安全网
GORM 的参数化不是魔法,它只保护你明确交给它的值;任何落到 SQL 字符串里的动态片段,无论多短,都得自己兜底校验。最常被忽略的,是字段名、表名、排序字段、GROUP BY 表达式这些“元信息”——它们从来不在参数化覆盖范围内。









