GORM安全取决于使用方式:用户可控值必须参数化,动态结构(字段名、表名、ORDER BY、GROUP BY等)必须白名单校验;混用拼接与参数化、误用?占位符于结构部分、Raw()裸拼接均会导致注入风险。

GORM 本身不自动防注入,安全取决于你怎么用——所有用户可控的值必须走参数化,所有动态结构(字段名、表名、排序方向、GROUP BY 字段)必须白名单校验。
Where 条件里别混用拼接和参数化
常见错误是前半段用 ?,后半段却对用户输入做字符串拼接,比如把 types 切片用 strings.Join 拼进 SQL。攻击者传 public', 'admin' UNION SELECT password FROM admins -- 就能拖库。
- IN 子句直接传切片:
db.Where("type IN ?", types),GORM 自动展开且兼容空切片 - 字段名、操作符不能来自用户:像
"status " + op + " ?"中的op(如LIKE、!=)必须白名单校验 - 空字符串、nil 值要提前拦截,避免生成
WHERE status = ''这类逻辑漏洞
Order、Group By、Table 名不能用 ? 占位
db.Order("name ?") 或 db.Group("?") 不会生效,数据库协议根本不允许运行时绑定标识符。GORM 会忽略占位符或 panic,结果可能是全表扫描、空结果或语法错误。
- 排序字段白名单:
validSortFields := map[string]bool{"name": true, "created_at": true} - 排序方向也得校验:
if sortDir != "ASC" && sortDir != "DESC" { return err } - GROUP BY 字段同理,
db.Group(groupField)前必须确认groupField在白名单中 - 表名映射建议用
switch或常量,禁止"users_" + tenantID这类拼接
Raw() 不是安全出口,而是高危入口
很多人以为用了 db.Raw() 就等于“自己掌控”,其实 GORM 只保证占位符部分被转义,其余全是裸奔。哪怕只在 FROM 或 GROUP BY 处拼接一个字段名,整条语句就不可信。
- 安全写法:
db.Raw("SELECT * FROM users WHERE status = ?", status).Scan(&u) - 危险写法:
db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName)).Scan(&u) - 更隐蔽的坑:
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)——idsStr若是用户传的逗号分隔字符串,没走sqlx.In或 GORM 的Where("id IN ?", ids),立刻失守
Limit/Offset 和 Scan 也有隐性风险
Limit 和 Offset 虽然接收整数,看似没法注入,但负数、超大值可能触发 DoS 或越界读取;Scan 若字段顺序与 SELECT 不一致,或未用 sql.Null* 处理 NULL,会导致静默失败或数据错位。
-
limit和offset必须范围校验:if limit 100 { limit = 20 } - 复杂查询用
Scan时,确保结构体字段顺序、类型、NULL 容忍度与 SQL 严格匹配 - 泛型版 GORM(
gorm.G[User])可提前暴露字段名拼错问题,但要求 GORM >= v1.30.0 且调用带ctx
最易被忽略的是业务字段白名单——比如前端传 status=deleted,后端没校验就塞进 Where("status = ?", status),哪怕用了参数化,照样查出不该看的数据。结构安全只是底线,业务语义校验才是最后一道门。


















