GORM默认不防注入,安全关键在于禁用字符串拼接+白名单校验结构部分;Where字符串拼接是最高危操作,所有值必须用占位符(如db.Where("name = ?", name)),表名、字段名、ORDER BY等须严格白名单校验。

GORM 默认不防注入,只要拼字符串就破防;安全的关键不是“用了 GORM”,而是“没拼字符串 + 白名单校验结构部分”。
Where 字符串拼接是最高危操作
几乎所有线上 SQL 注入都源于 db.Where("name = '" + username + "'") 这类写法。GORM 不会拦截或警告,它直接把拼好的字符串当 SQL 发给数据库。
- ✅ 正确:所有值一律走占位符,
db.Where("name = ?", username)或db.Where("status IN ?", statuses) - ❌ 错误:
db.Where("name = '" + username + "'")、db.Where("age > " + ageStr)、db.Where(fmt.Sprintf("category = '%s'", cat)) - ⚠️ 注意:
IN子句必须传 slice,db.Where("id IN ?", []uint{1,2,3})才会被自动展开;传单个字符串或手动拼IN (1,2,3)就失效
ORDER BY / 表名 / 字段名无法参数化,必须白名单
? 和 $1 只能代入值(value),不能代入标识符(identifier)。试图 db.Order("?" + sortField) 或 db.Raw("SELECT * FROM " + table) 会 panic 或静默失败,且完全暴露注入面。
- 排序字段:用
map[string]bool{"created_at": true, "score": true, "name": true}校验,不匹配直接拒掉 - 排序方向:只接受
"ASC"或"DESC",别信strings.ToUpper(dir)后直接拼 - 表名/租户表:用
switch tenant { case "prod": table = "users_prod" },禁止"users_" + tenantID - GORM 的
Raw()不是例外——db.Raw("SELECT * FROM users WHERE name = ?", name)安全,但db.Raw(fmt.Sprintf("SELECT * FROM %s", table))就裸奔
泛型 GORM 和 GEN 能从源头降低出错概率
传统 *gorm.DB 是泛型擦除的,查错表、字段写错、类型不匹配都不会编译报错;而 gorm.G[User](db) 和 GEN 生成的 query 方法,把模型、字段、参数类型全绑定在编译期。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- ✅
gorm.G[User](db).Where("name = ?", name).First(ctx, &user):字段名错、模型错、ctx 缺失都会编译失败 - ✅ GEN 把注释
// select * from users where name = {{.name}}编译成类型安全方法,连.namme拼错都过不了编译 - ⚠️ 注意:GEN 生成的代码默认要求传
ctx,忘记会 panic;gen.WithoutContext可关但不推荐 - ⚠️ 泛型 API 需 GORM >= v1.30.0,升级后所有
First/Find调用必须补全ctx和类型参数
分页和 COUNT 是最容易被忽略的注入温床
分页本身不引入注入,但错误的分页写法会让 ORDER BY 失效、COUNT 错乱,进而引发数据重复/遗漏,甚至暴露业务逻辑漏洞——比如通过 page=-1 触发 MySQL 的负偏移绕过限制。
-
page和size必须手动校验:page 强制设为 <code>1,size硬限制在1–100并截断(不返回错误) -
ORDER BY是分页稳定的前提,必须带确定性顺序:.Order("id ASC")或.Order("created_at DESC, id DESC");单独.Order("created_at DESC")在时间相同时结果不可控 -
Count()必须隔离上下文:db.Session(&gorm.Session{NewDB: true}).Model(&User{}).Where(...).Count(&total),否则Limit(10).Offset(20).Count()返回的永远 ≤ 10 - 动态表名用于分库分表时,同样要白名单校验,不能靠
"logs_" + year拼接
最危险的不是不会写安全代码,而是写了“看起来像安全”的代码——比如用了 ? 却在同一条查询里混了字符串拼接,或者对 Raw() 有错误的安全感。真正的防护边界在“值”和“结构”的分界线上,越过去一步,整条链就断了。

















