gorm 中 db.raw() 混用字符串拼接会导致 sql 注入,所有用户输入必须通过 ? 或 $1 占位符传参,表名、字段名、order by 等需白名单校验,gin 接收参数后须严格类型与值校验。

db.Raw() 里混用字符串拼接就是裸奔
很多人以为用了 GORM 就自动防注入,结果在 db.Raw() 里写 fmt.Sprintf("SELECT * FROM %s WHERE id = ?", tableName),立刻失效。GORM 不会拦截你塞进 Raw() 的拼接字符串,只保证占位符部分被参数化。
常见错误现象:db.Raw(fmt.Sprintf("UPDATE users SET status = ? WHERE id IN (%s)", ids)).Exec(status) ——如果 ids 来自用户且未校验,ids = "1, (SELECT password FROM admins)" 就直接触发子查询注入。
-
db.Raw()安全的前提是:所有用户输入都走?或$1占位符,不能出现在 SQL 模板字符串里 - 动态表名、字段名、
ORDER BY子句、GROUP BY、LIMIT都不支持占位符,必须白名单校验 - 白名单建议用
map[string]bool{"users": true, "articles": true}快速判断,别用strings.Contains或正则“过滤”
Where() 和 First() 传参方式错一个字符就失效
db.Where("email = '" + email + "'").First(&user) 是典型翻车写法,哪怕加了单引号也毫无意义。SQL 注入不是靠破坏字符串引号,而是改变语法结构——比如输入 admin' OR '1'='1,拼出来就是 email = 'admin' OR '1'='1',恒为真。
正确写法必须用问号占位:db.Where("email = ?", email).First(&user),GORM 底层会生成预编译语句,把 email 当纯值传给数据库,不参与 SQL 解析。
- MySQL/SQLite 驱动只认
?,写$1会报sql: expected 0 arguments, got 1 - PostgreSQL 驱动只认
$1、$2,用?会报pq: syntax error at or near "?" -
IN子句要传 slice:db.Where("id IN ?", []uint{1, 2, 3}).Find(&users),GORM 自动展开,别手动拼(1,2,3)
ORDER BY 和排序方向不能靠 strings.ToUpper() 救命
db.Order("name " + strings.ToUpper(dir)).Find(&users) 看似做了大小写统一,但只要 dir = "name; DROP TABLE users --",照样执行恶意语句。SQL 标准规定 ORDER BY 后的标识符属于查询结构,数据库编译阶段就要确定,没法参数化。
真正该做的是提前定义合法值并严格比对:
- 排序字段白名单:
validSortFields := map[string]bool{"created_at": true, "score": true, "title": true} - 排序方向白名单:
if dir != "ASC" && dir != "DESC" { return errors.New("invalid sort direction") } - 表名映射用常量或
switch:switch tenantID { case "prod": table = "users_prod"; case "dev": table = "users_dev" },别写"users_" + tenantID
Gin 接收参数后不校验就进 SQL 是最大盲区
Gin 的 c.Query("status") 或 c.PostForm("email") 返回的是原始字符串,不做任何过滤或类型检查。有人以为“前端传的是数字,后端拿过来肯定安全”,但攻击者根本不用走前端——直接 curl -X GET "http://localhost:8080/api?status=1%20OR%201%3D1" 就能绕过所有 UI 限制。
所以每一条来自 c 的输入,在进 db.Where() 前,必须明确其用途并做对应校验:
- ID 类型参数用
strconv.ParseUint(c.Query("id"), 10, 64),失败就拒掉 - 状态字段(如
status)必须白名单:if !validStatuses[c.Query("status")] { return err } - 邮箱字段用
mail.ParseAddress或正则^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$校验格式 - 永远不要相信
strings.Replace(email, "'", "''", -1)这类“转义”——它既不解决结构注入,也不防多语句攻击
最易被忽略的点是:业务逻辑里的字段校验缺失比语法层面的拼接更危险。比如 status 参数没设白名单,直接进 WHERE status = ?,攻击者就能查出所有状态记录,甚至配合 UNION SELECT 泄露管理员密码哈希。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











