gorm中sql注入防护需严格使用?占位符而非字符串拼接,动态表名、排序字段、limit/offset等须经白名单校验,null字段用sql.null类型接收,业务参数仍需逻辑校验。

db.Where 里别拼字符串,一律用 ? 占位符
只要写 db.Where("name = '" + name + "'"),就等于把 SQL 控制权交出去。GORM 的 Where 方法本身不防注入,它只负责把传进去的字符串原样塞进 SQL 模板——你拼了,它就执行。
正确做法是让 GORM 绑定参数:db.Where("name = ?", name) 或 db.Where("status IN ?", statuses)(statuses 是 []string,GORM 会自动展开成 IN ('a','b'))。
- 别信
strings.ReplaceAll(name, "'", "''")这类“转义”——Unicode 引号、注释符/**/、换行都能绕过 -
db.Where(fmt.Sprintf("name = '%s'", name))和直接拼接没区别,只是多了一层函数调用假象 - 多个条件用链式调用更安全:
db.Where("age > ?", minAge).Where("city = ?", city),避免手写 AND/OR 拼接
GORM Raw() 不是免检通道,占位符必须显式写出
db.Raw() 不自动过滤、不拦截拼接。它只保证你写的占位符部分被安全绑定,其余全是裸奔区。
错:db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id) —— 表名未校验,tableName 来自用户?立刻中招。
对:db.Table(tableName).Where("id = ?", id).Find(&u),前提是 tableName 来自硬编码常量或白名单映射(比如 map[string]string{"prod": "users_prod", "dev": "users_dev"})。
-
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)是典型翻车点:若idsStr是用户输入且未校验,"1, (SELECT password FROM admins)"就能触发子查询注入 - 真要动态表名,先查
INFORMATION_SCHEMA.TABLES确认存在,再比对白名单,别省这一步 -
db.Raw("SELECT * FROM users WHERE name = ?").Scan(&u)安全;但db.Raw("SELECT * FROM users WHERE name = '" + name + "'").Scan(&u)直接放弃防御
ORDER BY / 字段名 / 排序方向不能参数化,必须白名单硬控
写 db.Order("?" + sortField) 或 db.Order(sortField + " " + sortDir) 都无效。SQL 标准不允许参数化标识符,驱动会 panic:sql: expected 0 arguments, got 1,或者静默当字面量处理,查不到数据。
排序字段、方向、分组字段、LIMIT/OFFSET 值,全部属于查询结构,必须在拼 SQL 前就确定好。
- 定义白名单:
validSortFields := map[string]bool{"created_at": true, "score": true, "name": true},查不到就拒掉 - 方向只接受
"ASC"或"DESC",别信strings.ToUpper(dir)后直接拼——攻击者输DESC; DROP TABLE users--会被当字符串值,但拼完就是完整语句 - 表名映射用
switch或常量:case "prod": table = "users_prod",别写"users_" + tenantID
Scan 接收 NULL 和字段顺序错位,不是注入但一样致命
Scan 不参与防注入,但它出错会暴露结构、panic、甚至间接导致逻辑异常。这不是 SQL 注入,但危害不亚于注入。
例如:SELECT name, email FROM users 返回两列,但你用 row.Scan(&id, &name),id 会收到 name 值,name 收到 email,字段错位肉眼难发现。
- 所有可能为
NULL的字段,必须用sql.NullString、sql.NullInt64等接收,不能直接用string - 结构体字段名要和
SELECT别名严格一致:SELECT name AS username对应Username string - 避免匿名扫描:
row.Scan(&id, &name, &email)—— 表结构一变,代码就 silently 错乱;优先用结构体 +Find()
真正难防的从来不是语法层面的注入,而是业务字段本该校验却漏掉——比如前端传来的 status 值,没走白名单就直接进了 WHERE status = ?,哪怕用了占位符,也等于放行非法状态变更。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











