go数据库防注入核心是参数化查询:必须用?或$1占位符,禁用字符串拼接;order by/表名/字段名需白名单校验;gorm raw()不免疫拼接风险;scan须严格匹配字段顺序、类型及null处理。

db.Query 和 db.Exec 必须用 ? 或 $1,不能拼字符串
Go 本身不防注入,防注入靠的是你有没有把用户输入当“值”传进去。只要写 db.Query("SELECT * FROM users WHERE name = '" + name + "'"),就等于把数据库钥匙塞给攻击者。
正确做法是让驱动接管参数绑定:db.Query("SELECT * FROM users WHERE name = ?", name)(MySQL/SQLite)或 db.Query("SELECT * FROM users WHERE name = $1", name)(PostgreSQL)。驱动会把 SQL 模板和参数分开发送,用户输 admin' OR '1'='1 也只会被当做一个字符串值,不会触发语法解析。
- 占位符写错(比如 MySQL 里写
$1)不会报错,但参数会被忽略,查不到数据或意外匹配字面量$1 -
fmt.Sprintf、strconv.Itoa、+拼接一律禁止,哪怕加了单引号也没用 - 传
nil给非空字段、用string接可能为NULL的列,会导致Scanpanic,不是注入但一样让服务崩
ORDER BY、表名、字段名不能用 ?,必须白名单校验
db.Query("SELECT * FROM users ORDER BY ?", "name") 会直接 panic:sql: expected 0 arguments, got 1。SQL 标准规定这些属于查询结构,数据库编译阶段就要确定,没法参数化。
真正该做的,是提前定义合法值并严格比对:
- 排序字段:用
map[string]bool{"created_at": true, "score": true, "name": true},查不到就拒掉 - 排序方向:
if dir != "ASC" && dir != "DESC" { return err },别信strings.ToUpper(dir)后直接拼 - 表名映射:用
switch或常量,比如tenantID == "prod"→ 查users_prod;别写"users_" + tenantID
GORM 的 Raw() 不是免死金牌,拼表名照样中招
db.Raw("SELECT * FROM users WHERE name = ?", name) 是安全的;但 db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName)) 就是裸奔。GORM 不会拦截你塞进去的拼接字符串,它只保证占位符部分被转义。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
常见翻车点:
-
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)——idsStr若来自用户且未校验,idsStr = "1, (SELECT password FROM admins)"就触发注入 - 正确做法:用
sqlx.In或 GORM 的Where("id IN ?", idsSlice),让框架自动展开占位符 - 动态表名必须走
db.Table(tableName),且tableName必须来自枚举或配置,不能是用户直传
Scan 字段顺序和类型错位,比注入还容易挂
Scan 不参与防注入,但它错配会导致 panic 或数据错位,间接暴露结构、引发逻辑异常,甚至被用来探测字段是否存在。
关键细节:
- SELECT 列顺序必须和
Scan(&u.ID, &u.Name, &u.Email)严格一致;别用匿名变量堆着扫 - 遇到可能为
NULL的字段,必须用sql.NullString、sql.NullInt64等类型接收,普通string扫NULL会 panic - 字段别名要和结构体字段名对齐:
SELECT id, name AS username得配Username string,否则扫不上
白名单校验和 Scan 类型匹配这两块,最容易被当成“次要问题”跳过,但它们实际影响稳定性比注入更直接——一个没处理 NULL 的字段,上线当天就 panic;一个漏校验的排序字段,可能第二天就被拖库。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










