gorm的where等方法默认安全,因其强制参数化查询,sql结构与用户数据严格分离;而raw直接执行字符串,若拼接用户输入(如db.raw("where name = '"+name+"'"))则完全暴露sql注入风险,必须用占位符或白名单校验动态部分。

为什么 GORM 的 Where 默认安全,但 Raw 一用就出事?
GORM 的 Where、First、Find 等方法默认走参数化查询,SQL 结构和用户数据完全分离。数据库先编译 SELECT * FROM users WHERE email = ?,再把 email 值当纯参数填入——无论你传 ' OR 1=1 -- 还是 ; DROP TABLE users;,它都只是字符串值,不会被解析成 SQL 指令。
Raw 则相反:它直接把字符串扔给数据库执行。如果你写 db.Raw("SELECT * FROM users WHERE email = '" + email + "'"),Go 会先拼接字符串,再交给数据库。这时候攻击者输入的单引号、分号、注释符全都会参与 SQL 解析。
- ✅ 安全写法:
db.Raw("SELECT * FROM users WHERE email = ? AND status = ?", email, status) - ❌ 危险写法:
db.Raw("SELECT * FROM users WHERE email = '" + email + "'") - ⚠️ 注意:
?占位符只在Raw中起作用,不能混用$1(PostgreSQL)或:name(SQLite),GORM 会按驱动自动适配,别手动改
哪些地方最容易偷偷拼接 SQL?
不是只有 Raw 才危险。任何把用户输入直接塞进字符串的地方,都可能成为注入入口:
- 动态表名或字段名:GORM 不支持参数化表名,
db.Table("users_" + tenantID).Where(...)是高危操作 - ORDER BY / GROUP BY 动态字段:
db.Order("created_at " + sortDir)——sortDir若来自 query 参数且未校验,可注入ASC; DROP TABLE logs; -- - JSON 查询(MySQL 5.7+):
db.Where("data->'$.name' = ?", name)安全;但db.Where("data->'" + path + "' = ?", value)就不安全 - 第三方库封装的“便捷查询”函数,如果内部用了
fmt.Sprintf拼 SQL,也要查源码确认
光靠 ORM 就能高枕无忧?
不能。GORM 安全是默认行为,但可以被绕过:
- 显式关闭预处理:
db.Session(&gorm.Session{PrepareStmt: false})会让所有查询退回到字符串拼接模式 - 使用
Scan配合Rows时,如果自己调rows.Scan之前没做参数化,仍可能暴露 - 数据库账号权限过大:即使注入成功,若连接用户只有
SELECT权限,最多读数据;但若拥有DROP、CREATE,后果严重 - 日志泄露:开启 GORM 日志后,
Raw的完整 SQL 可能打到 stdout,含敏感参数,需过滤或关掉生产日志
实际开发中必须加的三道检查
防御不是靠某一个函数,而是贯穿整个数据流:
- 所有来自
c.Query、c.PostForm、c.Param的值,在进 SQL 前必须过白名单校验:比如 ID 只允许数字,邮箱用mail.ParseAddress或正则^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$ - 动态 SQL 片段(如排序字段)必须硬编码映射:
map[string]string{"created_at": "created_at", "title": "title"},拒绝任何不在列表里的输入 - 数据库连接配置里明确限制权限:
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app_user'@'%';,不给DROP、ALTER、CREATE
最常被忽略的是动态字段和权限控制——它们不出现在 ORM 文档里,却恰恰是线上事故的高频源头。











