GORM不自动防SQL注入,安全取决于使用方式:用户可控值必须用?占位符参数化,字段名、表名、ORDER BY等结构部分须白名单校验,混用拼接与参数化或误用?于结构位置均会导致注入。

所有用户输入都必须走 ? 占位符,表名、字段名、ORDER BY、GROUP BY 等结构部分绝不能参数化——GORM 不会帮你校验这些,也不会报错,只会静默失效或 panic。
db.Raw() 里只允许 ? 出现在值的位置
很多人误以为 db.Raw() 是“自己掌控 SQL”的安全出口,其实它只是把字符串原样交给驱动,GORM 只对 ? 占位符做参数绑定,其余全是裸奔。
- ✅ 安全:
db.Raw("SELECT * FROM users WHERE status = ? AND created_at > ?", status, time.Now()).Scan(&u) - ❌ 危险:
db.Raw(fmt.Sprintf("SELECT * FROM %s WHERE id = ?", tableName)).Scan(&u)——tableName拼进去了,整条语句不可信 - ❌ 更隐蔽的危险:
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)——idsStr若是用户传的逗号分隔字符串,没走Where("id IN ?", idsSlice),就直接失守
ORDER BY / GROUP BY / LIMIT 这些子句不能用 ?
SQL 标准规定这些属于查询结构,数据库协议层不支持运行时绑定标识符。GORM 遇到 db.Order("name ?") 或 db.Group("?") 会忽略占位符、返回空结果,或直接 panic,不会报明确错误。
- 别写:
db.Order("name " + sortDir),哪怕加了strings.ToUpper(sortDir)也拦不住sortDir = "name; DROP TABLE admins --" - 正确做法:白名单校验字段和方向:
validSortFields := map[string]bool{"created_at": true, "score": true, "name": true};if sortDir != "ASC" && sortDir != "DESC" { return err } - 表名映射也一样:用
switch或常量,比如tenantID == "prod"→ 查users_prod,而不是"users_" + tenantID
Where 条件里混用拼接和参数化是最常见翻车点
前半段用 ?,后半段却对用户输入做字符串拼接(比如 strings.Join(types, ",")),攻击者传 public', 'admin' UNION SELECT password FROM admins -- 就能拖库。
- ✅ IN 子句直接传切片:
db.Where("type IN ?", types)—— GORM 自动展开,兼容空切片 - ❌ 别手动拼:
db.Where("type IN (" + strings.Join(types, ",") + ")") - ❌ 字段名/操作符不能来自用户:
"status " + op + " ?"中的op(如LIKE、!=)必须白名单校验,不能靠strings.ReplaceAll(op, "'", "")这类过滤 - 空字符串、
nil值要提前拦截,避免生成WHERE status = ''这类逻辑漏洞
最易被忽略的是:占位符必须匹配驱动——MySQL/SQLite 用 ?,PostgreSQL 用 $1、$2;写错不会编译失败,而是静默失效,线上查不到数据却难以定位。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











