go中防sql注入唯一可靠方式是严格使用参数化查询,where等值条件必须用占位符;order by、表名等结构部分需白名单校验;scan须匹配类型与顺序,orm raw方法不自动防护。

Go 里防 SQL 注入,唯一靠谱的方式就是参数化查询——不是“尽量用”,是“必须用”,而且得用对地方。拼字符串、转义单引号、删分号,全都没用;ORM 也不能自动兜底,尤其你写原生 SQL 的时候。
db.Query 和 db.Exec 必须带占位符,不能拼字符串
用户输入一旦进 SQL 字符串,就等于把数据库的控制权交出去了。哪怕只是 WHERE status = 'active',只要这个 active 来自 URL 参数或表单,就必须走参数化。
- ✅ 正确:
db.Query("SELECT * FROM users WHERE email = ? AND deleted = ?", email, false)(MySQL/SQLite) - ✅ 正确:
db.Query("SELECT * FROM users WHERE email = $1 AND deleted = $2", email, false)(PostgreSQL) - ❌ 危险:
db.Query("SELECT * FROM users WHERE email = '" + email + "'") - ❌ 更危险:
fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)——看似加了引号,但email是"admin' -- "就直接注释掉后面逻辑
database/sql 不解析 SQL,只原样发给驱动。你拼出来的字符串,数据库就照单全收。
ORDER BY、表名、列名不能用 ? 或 $1
SQL 标准规定:这些属于查询“结构”,不是“值”,参数化机制根本不支持绑定。试图 db.Query("ORDER BY ?", "name") 会直接 panic:sql: expected 0 arguments, got 1。
- 排序字段只允许
"name"、"created_at"、"id"等预设值,用map[string]bool{"name": true, "created_at": true}快速校验 - 方向只允许
"ASC"或"DESC",且要strings.ToUpper()统一后比对 - 表名同理:
isValidTable := map[string]bool{"users": true, "orders": true},不在里面就拒掉 - 别信
strings.Replace(email, "'", "''", -1)就安全了——绕过方式太多,Unicode、多字节编码、驱动差异都能打穿
Scan 时类型和顺序错位,不是注入,但一样致命
Scan 不查字段名,只按 SELECT 后的列位置和类型硬匹配。表结构一变,代码没同步,轻则数据错位,重则 panic:sql: Scan error on column index 2: unsupported Scan, storing driver.Value type <nil> into type *string</nil>。
- ✅ 用具名变量:
err := row.Scan(&u.ID, &u.Name, &u.Email) - ✅ 遇到可能为 NULL 的字段,必须用
sql.NullString、sql.NullInt64等类型接收 - ❌ 别堆匿名变量:
row.Scan(&id, &name, &email)——人眼难对齐,改表后极易漏 - ❌ 用普通
string接NULL值,运行时报错,服务可能挂掉
ORM 如 GORM 的 Raw 方法不自动防注入
很多人以为 ORM 天然免疫 SQL 注入,结果栽在 Where("name = '" + name + "'") 或 Raw("SELECT * FROM " + tableName + " WHERE id = ?", id) 上——表名未校验,照样中招。
- ✅ 安全写法:
db.Table(tableName).Where("id = ?", id).Find(&u),前提是tableName来自枚举或配置项 - ✅
db.Raw("SELECT * FROM users WHERE email = ?", email).Scan(&user) - ❌ 危险写法:
db.Raw(fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)).Scan(&user) - JSON 字段里的 SQL 片段不是“免检区”:有些服务把查询条件存成 JSON,后端解析后拼成 SQL,这时候容易误以为“JSON 是数据,不是代码”,放松校验
最常被忽略的是日志和错误信息——比如 log.Printf("executing: %s", query),如果 query 是拼出来的,日志就成了注入入口。所有日志里的 SQL 必须确认来自预编译语句,或已手动过滤/脱敏。











