必须用参数化查询或白名单校验修复sql注入漏洞;gorm链式api默认安全,但raw/exec/order等需严防注入,表名列名须白名单,in查询用where("x in ?", slice)自动绑定。

用链式调用 + 条件分支拼接 WHERE
最直接、最可控的方式是复用 GORM 的 Where 链式能力,每个条件独立判断后追加。它不依赖额外封装,也避免字符串拼接风险。
常见错误现象:写成 db.Where("name = '" + name + "'"),或在 Where 里混用字段名和值(如 db.Where(field + " = ?", value)),前者导致 SQL 注入,后者字段名无法参数化,一旦传入恶意字段名就崩。
正确做法是只对「值」用占位符,字段名必须硬编码或白名单校验:
- 所有用户输入的值一律走
?占位,例如tx = tx.Where("name = ?", name) - 空值/零值需显式跳过,否则
WHERE status = 0可能查出意外数据;可用if name != ""或if !reflect.ValueOf(name).IsNil()判断 - 多个条件共用一个
*gorm.DB实例,避免重复初始化开销
IN 查询必须传 slice,不能手动展开
IN 是动态条件高频场景,但 GORM 对它的处理有明确规则:必须把整个 slice 作为单个参数传入 Where,不能拆成多个 ? 或拼字符串。
错误写法:db.Where("id IN (?)", ids[0], ids[1]) 会 panic;db.Where("id IN (" + strings.Join(idsStr, ",") + ")") 直接引入注入漏洞。
正确写法只有一种:
-
db.Where("id IN ?", []uint{1, 2, 3})—— GORM 自动展开为IN (1,2,3),且每个值都参数化 - 若
ids是[]interface{}或泛型切片,需确保元素类型一致,否则可能触发反射失败 - 空 slice 传入会导致生成
IN (),MySQL 报错;务必提前判空,如if len(ids) > 0
子查询必须用 SubQuery + (?) 包裹
当条件依赖另一个查询结果(比如“查未被标记为高危的用户”),必须用子查询,但绝不能字符串拼接。
典型错误:db.Where("id NOT IN (SELECT user_id FROM flags WHERE type = 'high_risk')") —— 字段值没参数化,且子查询无上下文隔离,容易被绕过。
安全写法分两步:
- 先构建子查询对象:
subQuery := db.Table("flags").Select("user_id").Where("type = ?", "high_risk") - 再嵌入主查询:
db.Where("id NOT IN (?)", subQuery)—— 注意括号(?)是语法必需,不是可选符号 - 子查询本身也要遵守参数化规则,不能在其中拼接用户输入
泛型版 gorm.G[T] 强制类型绑定
传统 *gorm.DB 是泛型擦除的,db.First(&order) 却可能去查 User 表而不报错;升级到 GORM v1.30+ 后,用 gorm.G[User](db) 能在编译期卡住类型错位。
这不是锦上添花,而是关键防线:
- 所有查询必须带
context.Context,否则First/Find会 panic,这是设计强制项 - 关联预加载也受保护:
gorm.G[User](db).Preload("Orders").Find(ctx, &users),字段名写错直接编译失败 - 旧项目迁移时,所有
db.Where(...).First(&v)都得改成gorm.G[T](db).Where(...).First(ctx, &v),漏掉 ctx 或类型参数都会中断
最易被忽略的是:泛型 API 不兼容裸 db.Raw(),自定义 SQL 必须走 GEN 或 Scopes 封装,否则类型安全形同虚设。











