go原生database/sql防注入关键在于正确使用占位符(mysql/sqlite用?,postgresql用$1)、杜绝字符串拼接、严格匹配占位符数量顺序及参数类型;表名、列名、order by等结构部分必须白名单校验,gorm同理,错用where字符串即破防。

只要用对占位符、不拼字符串、不乱传 interface{},Go 原生 database/sql 就能防住绝大多数 SQL 注入;GORM 默认也安全,但一写错 Where 字符串就破防。
database/sql 的 ? 和 $1 到底怎么用才不崩
MySQL/SQLite 驱动必须用 ?,PostgreSQL 驱动必须用 $1、$2,混用会直接报错或静默退化成字符串替换。占位符数量、顺序、参数类型三者必须严格一致。
-
db.Query("SELECT * FROM users WHERE id = ? AND status = ?", 123, "active")✅(MySQL) -
db.Query("SELECT * FROM users WHERE id = $1 AND status = $2", 123, "active")✅(PostgreSQL) -
db.Query("SELECT * FROM users WHERE id = ? AND status = $1", 123, "active")❌ 混用,驱动可能忽略第二个参数 -
db.Query("SELECT * FROM users WHERE id = ?", "123 OR 1=1")✅ 安全,值被当字符串绑定,不会触发逻辑绕过
哪些地方死活不能用 ? —— 白名单校验绕不开
? 只能代入**值(value)**,数据库协议根本不允许它出现在表名、列名、ORDER BY 字段、GROUP BY 表达式或 UNION 子句里。强行塞进去不是报错就是被忽略。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
-
db.Query("SELECT * FROM " + tableName + " WHERE id = ?", id)❌ 危险,tableName必须白名单过滤 -
db.Query("SELECT * FROM users ORDER BY ?", sortField)❌ 多数驱动直接 panic 或返回语法错误 - 正确做法:
if !slices.Contains([]string{"name", "created_at", "updated_at"}, sortField) { return errors.New("invalid sort field") },再拼进 SQL -
IN (?)在 MySQL 驱动里不支持多值展开,得手动拼IN (?, ?, ?)并传对应长度的参数切片
GORM 看似自动防注入,但这些写法会悄悄破防
GORM 的 Where、First、Update 等方法默认走参数化,但一旦你混进字符串拼接,整条链就失效了。
-
db.Where("name = ?", name).Where("age > " + ageStr)❌ 后半段是字符串拼接,ageStr被直接插进 SQL -
db.Where("name = '" + name + "'")❌ 典型崩溃点,等同于裸拼 -
db.Raw("SELECT * FROM users WHERE name = ?", name)✅ 安全,Raw也支持占位符 -
db.Session(&gorm.Session{DryRun: true})不影响参数绑定逻辑,但别误以为“DryRun 就等于安全”
预编译 stmt 是性能优化,不是防注入补丁
db.Prepare 返回的 *sql.Stmt 在万级 QPS 场景下可省掉每次解析开销,但它本身不增加额外安全性——只要原始 SQL 字符串没拼接用户输入,db.Query 和 stmt.Query 的防护等级是一样的。
- 必须调用
stmt.Close(),否则连接泄漏(Go 1.19+ 有警告但不 panic) -
*sql.Stmt不是并发安全的,不能跨 goroutine 复用 - 动态生成的 SQL(比如带不同
WHERE条件组合)不适合提前Prepare,缓存命中率低,反而拖慢 - 驱动按 SQL 字符串缓存预编译语句,
"WHERE a = ?"和"WHERE a = ? AND b = ?"被视为两条不同语句
最易被忽略的是:所有用户输入——哪怕只是 URL path 中的 ID、Header 里的 token、JSON body 里的字段名——都得先过白名单或类型断言,再进占位符;参数类型不匹配(比如把 interface{} 直接塞进 Query)会让驱动退化为字符串替换,且不报错。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










