必须使用参数化查询防止SQL注入:外部输入须用?或$1等占位符,表名列名需白名单校验,ORM调用也要确保参数化,日志中禁止打印敏感参数。

用 database/sql 的 Query 和 Exec 必须带参数占位符
Go 原生 database/sql 包本身不拼接 SQL,但很多人误以为传入字符串就能“动态拼进去”。只要用了 + 或 fmt.Sprintf 拼接用户输入到 SQL 字符串里,就等于开门揖盗。
正确做法是:所有外部输入(包括 URL 参数、表单字段、JSON body)必须走参数化查询,用 ?(MySQL/SQLite)或 $1, $2(PostgreSQL)占位,由驱动底层安全绑定。
-
Query("SELECT * FROM users WHERE name = ? AND age > ?", name, age)✅ -
Query("SELECT * FROM users WHERE name = '" + name + "'")❌(哪怕加了strings.ReplaceAll(name, "'", "''")也防不住 Unicode 绕过) - PostgreSQL 要严格对应序号:
Query("SELECT * FROM logs WHERE level = $1 AND ts >= $2", level, since),不能写成$2在前$1在后
别在 WHERE 条件里用参数化处理表名或列名
SQL 参数只能替换值(value),不能替换标识符(identifier)——比如表名、列名、ORDER BY 字段、GROUP BY 表达式。这是协议层限制,不是 Go 的锅。
如果你需要动态表名(如分表场景),必须白名单校验 + 显式拒绝非法字符:
立即学习“go语言免费学习笔记(深入)”;
- 先定义允许的表名集合:
validTables := map[string]bool{"logs_202401": true, "logs_202402": true} - 再检查:
if !validTables[userInput] { return errors.New("invalid table name") } - 然后拼接:
"SELECT * FROM " + userInput + " WHERE id = ?"—— 此时拼的是可信值,不是用户原始输入 - 绝对不要用正则“过滤掉单引号”来放行列名,
regexp.MustCompile(`^[a-zA-Z0-9_]+$`)也不够,比如user; DROP TABLE users;还是能绕过
用 sqlx 或 gorm 时,别被结构体自动绑定惯坏了
第三方库如 sqlx.StructScan 或 gorm.Model(&u).Where("name = ?", name).First() 看似省事,但容易让人忽略底层是否真走了参数化。尤其 gorm 的链式 Where 如果传入 map 或 struct,默认会把 key 当列名、value 当参数值 —— 这没问题;但一旦写成 Where("name = " + name),就又掉坑里了。
-
sqlx.Select(&users, "SELECT * FROM users WHERE status = ? AND created_at > ?", status, cutoff)✅ -
gorm.Where("email = '" + email + "'").Find(&user)❌(哪怕 email 是strings.TrimSpace(email)也没用) -
gorm.Where("email = ?", email).Find(&user)✅ - 注意
sqlx.In查询(如IN (?))需要额外展开参数,直接传 slice 会报错:sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)错,得用sqlx.In("SELECT * FROM users WHERE id IN (?)", ids...)并配合sqlx.Rebind
日志里打印 SQL 时,别把参数值一起打出来
调试时习惯性 log.Printf("executing: %s with args: %+v", query, args),看似方便,实则可能泄露密码、手机号、身份证等敏感值到日志系统。
- 开发环境可用,但上线前必须关掉或脱敏:
log.Printf("executing: %s with %d args", query, len(args)) - 某些驱动(如
pgx)支持自定义QueryEvent钩子,可拦截并过滤参数值 - 如果用了 ORM,确认其日志配置是否默认打印参数(
gorm.Config.Logger的LogMode默认会打完整 SQL+参数)
参数化查询本身很稳,真正出问题的永远是人绕过它的方式。最危险的不是不会写,而是写了半截——比如 WHERE 用了参数,ORDER BY 却拼了字符串,或者事务里前几条语句参数化了,最后一条手抖用 fmt.Sprintf 补了个条件。


















