gorm的where方法不自动防sql注入,安全取决于是否使用参数化查询;where(string, interface{})安全,where(string)等同裸拼sql,混用或raw()中拼接用户输入均会导致注入。

GORM 的 Where 方法本身不防注入,安全与否只取决于你传的是参数化表达式还是拼接字符串——用错一次,整条查询链就失守。
Where(string) 和 Where(string, interface{}) 的区别必须分清
这是最常踩坑的点:Where 有两个签名,行为天差地别。
-
db.Where("name = ?", name)✅ 安全:GORM 将其转为参数化查询,name值由驱动绑定,无论内容是"admin' OR 1=1 --"还是"'; DROP TABLE users; --",都只是字符串值 -
db.Where("name = '" + name + "'")❌ 危险:等同于裸 SQL 拼接,单引号、分号、注释符全部参与 SQL 解析 -
db.Where("age > ?", age).Where("status = '" + status + "'")⚠️ 混合写法也崩盘:前半段安全,后半段已完全暴露
Raw() 不是“绕过 ORM 就等于危险”,而是“你塞什么它就执行什么”
Raw() 本身不带防护,也不带风险,风险全在你构造的字符串里。它不校验、不拦截、不重写。
-
db.Raw("SELECT * FROM users WHERE id = ? AND email = ?", id, email)✅ 安全:占位符被底层驱动正确绑定 -
db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id)❌ 危险:表名拼接无法参数化,tableName是"users; DROP TABLE admins --"就直接执行 -
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)⚠️ 高危混合:idsStr若来自用户且未校验(如含"1, (SELECT password FROM admins)"),即触发子查询注入
IN 子句、空切片和字段类型匹配这些细节容易静默翻车
IN 看似简单,但写错会 panic 或查出错误数据,不是报错而是静默失效。
-
db.Where("id IN ?", []uint{1, 2, 3}).Find(&users)✅ 安全且健壮:GORM 自动展开为IN (1, 2, 3),空切片时生成IN (?)并跳过条件 -
db.Raw("SELECT * FROM users WHERE id IN (" + idsStr + ")").Scan(&u)❌ 危险:若idsStr是用户输入且含恶意 payload,无任何防护 - 接收可能为
NULL的字段必须用sql.NullString或*string,普通string遇到NULL会 panic -
Scan不防注入,但字段顺序/类型错配会导致静默数据错乱——比如SELECT id, name却用struct { Name string; ID uint }接收,ID 值会被填进 Name 字段
动态表名、字段名、ORDER BY 必须白名单校验,没有例外
这些属于 SQL 结构,数据库协议根本不允许参数化,? 或 $1 塞进去只会 panic 或被忽略。
-
db.Table("logs_" + tenantID)❌ 危险:tenantID 若为"prod; DROP TABLE logs --",就完了 -
db.Order("created_at " + sortDir)⚠️ 危险:sortDir 若为"ASC; DROP TABLE users --",语句仍可能被解析执行 - 正确做法是预定义合法集合:
validTables := map[string]bool{"users_prod": true, "users_dev": true},查不到就拒掉 - 排序方向也得白名单:
if sortDir != "ASC" && sortDir != "DESC" { return err },别信strings.ToUpper()后直接拼
真正难防的不是语法错误,而是那些看起来“运行正常”的查询——字段错位、NULL panic、空切片逻辑跳变、白名单漏项,这些不会报错,却在生产环境悄悄腐蚀数据一致性与服务稳定性。











