database/sql的Query和Exec默认防注入,因其将SQL模板与参数分离传递给驱动,用户输入仅作为值绑定不参与解析;但须严格使用?或$1占位符、禁用字符串拼接,表名等动态标识符必须白名单校验。

database/sql 的 Query 和 Exec 为什么默认就防注入
因为 Go 标准库的 database/sql 不拼 SQL 字符串,它把 SQL 模板和参数分开传给驱动;只要用对占位符(? 或 $1),驱动(如 go-sql-driver/mysql 或 lib/pq)就会走预编译路径——用户输入只作为值绑定,不参与 SQL 解析。
常见错误现象:sql: converting argument $1 type: unsupported type string(参数类型不对)、ERROR: syntax error at or near "123"(字符串被直接拼进去了)、查不到结果但无报错(类型不匹配导致退化为文本替换)。
- MySQL 驱动必须用
?,PostgreSQL 必须用$1、$2,混用会报sql: expected 1 arguments, got 0 - 参数必须是具体类型变量或切片元素,不能是
"'"+name+"'"这种拼接结果 -
fmt.Sprintf拼任何部分(哪怕只是表名)都会让整条语句失去预编译保护 - 所有日志里打印的原始 SQL,如果来自拼接,就是潜在注入入口(尤其远程日志系统)
GORM 的 Where 和 Raw 哪些写法会裸奔
GORM 不自动防注入,它只保证你“用了占位符”的那部分安全;一旦你传单参数字符串,它就原样透传,驱动不干预。
危险写法示例:
立即学习“go语言免费学习笔记(深入)”;
-
db.Where("name = '" + name + "'")→ 单参数,字符串拼接,完全裸奔 -
db.Where("age > ?", age).Where("status = '" + status + "'")→ 前半段安全,后半段崩盘 -
db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName))→ 表名拼接,Raw不做任何拦截 -
db.Raw("UPDATE users SET status = ? WHERE id IN ("+idsStr+")", status)→IN子句拼接,idsStr若未白名单校验,可注入
安全替代:
-
db.Where("name = ?", name)✅ - 动态表名:先白名单判断
validTables := map[string]bool{"users": true, "orders": true} -
IN多值:用db.Where("id IN ?", ids),GORM 会自动展开成$1, $2, $3并绑定
动态字段(ORDER BY、分表、UNION)为什么不能用占位符
数据库协议层不允许参数化标识符(identifier),ORDER BY ? 或 SELECT * FROM ? 在语法上非法,多数驱动直接报错或静默忽略。
这不是 Go 的限制,而是 PostgreSQL/MySQL 协议本身不支持。占位符只作用于值(value),不作用于结构(structure)。
- 排序字段来自 URL?只允许
"created_at"、"name"、"id"等白名单字符串 - 分表路由?用
userID % 4计算后映射到固定表名,再校验是否在[]string{"logs_0", "logs_1", "logs_2", "logs_3"}中 - 排序方向(
ASC/DESC)?只能用if dir == "DESC" { query += " DESC" },且dir必须严格枚举校验 - 别信
strings.ReplaceAll(input, "'", "''")或正则过滤——PostgreSQL 支持 Unicode 注释、多字节绕过,黑名单无效
db.Prepare 和 db.Query 安全性一样,但复用有坑
两者都走预编译,安全效果完全一致;区别只在执行计划是否复用,不是“更安全”,而是“更快”(万级 QPS 下才明显)。
容易踩的坑:
-
*sql.Stmt绑定到单个连接,连接池切换可能导致缓存失效,反而降低性能 - 必须调用
stmt.Close(),否则连接泄漏(Go 1.19+ 仅警告,不 panic) -
*sql.Stmt非并发安全,不能跨 goroutine 复用;高频短生命周期 handler(如 HTTP)中,db.Query更轻量 - 驱动按 SQL 字符串缓存预编译语句,
WHERE id = ?和WHERE id = ? AND status = ?被视为两条不同语句
最易被忽略的一点:预编译只保值,不保结构。表名、列名、排序方向、UNION 结构这些,漏掉白名单校验,前面所有参数化都白做。


















