gorm不自动防注入,安全取决于写法:where需全参数化、order/table/字段名须白名单校验、raw中仅占位符安全、limit/offset需范围控制、null字段用sql.null类型。

GORM 本身不自动防注入,安全与否全看你怎么写 Where、Order、Raw 这些链式调用——拼错一个字段名、漏校验一个排序方向、混用一次字符串拼接,整条查询就裸奔。
Where 条件里别混用字符串拼接和参数化
常见错误是前半段用 ?,后半段却对用户输入做 strings.Join 或 + 拼接。比如:
db.Where("status = ?", status).
Where("type IN ('" + strings.Join(types, "','") + "')"). // ❌ types 是 URL 参数?攻击者塞个 <code>public', 'admin') UNION SELECT password FROM admins --</code> 就拖库
Order("created_at DESC").Find(&items)
正确做法只有一条:所有值走参数化,IN 子句直接传切片,GORM 自动展开且兼容空切片。
-
db.Where("type IN ?", types)✅ types 是[]string,GORM 生成IN ('a','b') - 字段名、操作符(如
LIKE、BETWEEN)不能来自用户,必须硬编码或白名单校验 - 若需动态字段(如按
name或email搜索),先过白名单:if !validSearchFields[field] { return err }
Order、Group By、Table 名不能靠 ? 防注入
db.Order("created_at ?") 或 db.Group("?") 不会把 ? 当占位符处理,而是原样塞进 SQL —— 等于亲手关掉防火墙。
- 排序字段必须白名单:
validSortFields := map[string]bool{"name": true, "created_at": true} - 排序方向也得校验:
if order != "ASC" && order != "DESC" { order = "ASC" } - 表名同理:
db.Table(tableName)中的tableName绝不能来自用户,必须查白名单或硬编码映射(如switch tenantID { case "prod": return "users_prod" }) -
db.Raw("SELECT * FROM " + table + " ...")—— 表名拼接即失守,GORM 不拦截你塞进去的字符串
Raw() 不是免死金牌,占位符只保它那一小段
db.Raw("SELECT * FROM users WHERE name = ?", name) 安全;但 db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName)) 就是裸奔。gosec 规则 G201 会标记所有 Raw() 调用,必须人工逐行确认是否只在占位符位置用了用户输入。
- 危险示例:
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)——idsStr若是用户拼的"1, (SELECT password FROM admins)",直接触发注入 - 正确做法:
db.Where("id IN ?", idSlice).Update("status", status),让 GORM 自动展开IN (?, ?, ?) - 哪怕用了
db.Session(&gorm.Session{DryRun: true}),也不代表 SQL 安全 —— DryRun 只模拟执行,不改变拼接逻辑
Limit/Offset 和 NULL 字段容易被忽略的坑
Limit 和 Offset 接收整数,看似没法注入,但恶意构造可导致 DoS 或数据越界。NULL 字段不显式处理会 panic,间接暴露结构。
-
Limit/Offset必须范围校验:if limit 100 { limit = 20 } - 数据库字段可能为 NULL,却用普通
string接收 →panic: sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string</nil> - 所有可能为 NULL 的字段,必须用
sql.NullString、sql.NullInt64等类型接收 - Scan 到结构体时,字段顺序必须和
SELECT列严格一致;用SELECT id, name AS username就得对应结构体字段Username string
最危险的不是语法漏洞,而是业务字段没校验——比如前端传来的 status 值,没做白名单就直接进 WHERE status = ?,哪怕用了占位符,恶意值仍可能触发越权或逻辑绕过。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











