只要不用字符串拼接构造SQL,就拦住了95%的SQL注入;应始终用参数化查询(如?或$1)传递用户输入,表名列名等结构部分须白名单校验,IN子句需安全展开,空切片要特殊处理,且参数化不解决类型匹配等后续风险。

只要不用字符串拼接构造 SQL,就拦住了 95% 的 SQL 注入;过滤危险字符串不是防注入的核心手段,反而容易制造虚假安全感。
用 db.Query 或 db.Exec 传参,别拼 string
Go 标准库 database/sql 本身不防注入,但它把“防注入”的责任交给了你——只要你把用户输入当参数传进去,而不是塞进 SQL 字符串里,驱动(如 go-sql-driver/mysql 或 lib/pq)就会在协议层隔离结构与值。
- MySQL/SQLite 用
?占位符:db.Query("SELECT * FROM users WHERE id = ?", userID) - PostgreSQL 用
$1、$2:db.Query("SELECT * FROM users WHERE id = $1 AND status = $2", userID, status) - 混用占位符会报错:
sql: expected 1 arguments, got 0或 panic - 别写
fmt.Sprintf("WHERE id = %d", userID),也别"WHERE id = '" + username + "'"—— 这类写法哪怕加了单引号或转义,照样被绕过
表名、列名、ORDER BY 字段不能参数化,必须白名单校验
? 和 $1 只能替代表达式中的“值”,不能替代 SQL 结构。数据库在解析阶段就要确定查哪张表、按哪个字段排序,这些无法运行时绑定。
-
db.Query("SELECT * FROM ? WHERE id = ?", tableName, id)—— 直接 panic - 排序字段必须限制范围:
validSortFields := map[string]bool{"created_at": true, "score": true},查不到就拒掉 - 排序方向(
ASC/DESC)也要白名单:if dir != "ASC" && dir != "DESC" { return errors.New("invalid sort direction") } - 分表场景建议用常量映射:
switch input { case "prod": table = "users_prod"; case "test": table = "users_test" },别拼"users_" + input
IN 子句和动态条件要小心展开,别手拼逗号列表
用户传来的 ID 列表(如 []int64{1,2,3})不能直接 strings.Join 成 "1,2,3" 塞进 SQL —— 这等于开放注入入口。
立即学习“go语言免费学习笔记(深入)”;
- GORM 正确写法:
db.Where("id IN ?", ids),它会自动展开为IN (?, ?, ?)并绑定参数 - 原生
database/sql推荐用sqlx.In:query, args, _ := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids),再db.Query(query, args...) - 手动拼
"IN (" + strings.Join(idsStrs, ",") + ")"是高危操作,idsStrs若含"1, (SELECT password FROM admins)"就崩了 - 空切片要特殊处理:
len(ids) == 0时应跳过该条件,避免生成IN ()语法错误
别把 sql.NullString 或 interface{} 当“安全包装”乱用
sql.NullString 解决的是数据库 NULL 值映射问题,跟防注入完全无关。把它当参数传进拼接的 SQL 里,照样裸奔。
- 从 JSON 解析出的
map[string]interface{}不能直接丢给db.Query—— 必须先断言类型:if v, ok := data["id"].(int); ok { ... } -
sql.RawBytes是底层字节切片,不是可插占位符的值,需先转成string或[]byte - 日志中打印原始 SQL 时,确认它来自预编译语句;如果
log.Printf("SQL: %s", query)的query是拼出来的,日志系统可能成为注入出口 - ORM 如 GORM 的
Raw()不是免死金牌:db.Raw("SELECT * FROM " + tableName)和db.Raw("WHERE name = ?", name)安全性天壤之别
最易被忽略的一点:参数化查询只管执行前的安全,不管执行后的数据处理。比如用 int 接 BIGINT 字段,在 32 位系统上会丢高位;用 string 接可能为 NULL 的列,Scan 会直接 panic —— 这些不会导致注入,但会让业务逻辑在静默中出错,掩盖真实风险。


















