go中sql注入可防,关键在禁用字符串拼接、仅对值使用?/$1占位符;表名、字段名、order by等结构化部分必须白名单校验。

Go 里 SQL 注入不是“防不住”,而是“写错才中招”——只要不字符串拼接 SQL,用对 Query、Exec、QueryRow 等带占位符的接口,底层驱动就把参数和语句彻底隔离了。真正的风险全集中在表名、字段名、ORDER BY 这些无法参数化的地方。
为什么 db.Query("SELECT * FROM users WHERE id = ?", id) 天然安全
数据库驱动(如 go-sql-driver/mysql 或 lib/pq)在执行时,会把 SQL 模板和参数值分两路送到底层:模板走解析器,参数走绑定通道。攻击者输进来的 id 值哪怕带单引号、UNION、换行或 Unicode 空格,都不会被当语法解析。
-
?(MySQL/SQLite)和$1(PostgreSQL)是唯一被驱动识别的占位符,命名参数(如:name)直接报错:sql: expected 0 arguments, got 1 -
db.Query和db.Prepare安全性完全一致,区别只在是否复用执行计划;高频查询用Prepare省点开销,普通场景用Query更直白 - 传
int64、string、time.Time都没问题,但别传nil给非空字段,也别用string接可能为NULL的列——得用sql.NullString
ORDER BY、GROUP BY、表名这些为啥不能用 ?
SQL 标准规定:这些属于查询结构(query structure),不是运行时值。数据库在编译阶段就要确定排序字段、分组依据或访问哪张表,根本没留参数绑定入口。你写 db.Query("SELECT * FROM users ORDER BY ?", "name"),驱动立刻 panic:sql: expected 0 arguments, got 1。
- 排序字段必须白名单校验,比如
validFields := map[string]bool{"created_at": true, "score": true, "name": true},查不到就拒掉 - 排序方向(
ASC/DESC)也得白名单,别拼"ORDER BY " + field + " " + dir——dir是用户可控的,"DESC; DROP TABLE users"就能绕过 - 表名映射建议用常量+
switch,例如输入"prod"→ 查"users_prod";别写"users_" + input,否则input = "prod; --"就崩了
GORM 的 Raw() 不是免死金牌
db.Raw("SELECT * FROM users WHERE name = ?", name) 是安全的,但 db.Raw(fmt.Sprintf("SELECT * FROM %s", tableName)) 就等于裸奔。GORM 不会拦截你塞进去的拼接字符串,它只保证占位符部分被转义。
- 常见翻车点:
db.Raw("UPDATE users SET status = ? WHERE id IN (" + idsStr + ")", status)——idsStr若来自用户且未校验,idsStr = "1, 2, (SELECT password FROM admins)"就触发注入 - 正确做法:用
sqlx.In或 GORM 的Where("id IN ?", idsSlice),让框架自动展开占位符 - 动态字段名(如
SELECT {{field}} FROM users)必须先走白名单,再拼进 SQL 字符串,不能靠strings.ReplaceAll过滤单引号——Unicode 空格、/**/注释都能绕过
Scan 出错不是注入,但会让服务变脆
Scan 不参与防注入,但它错位或类型不匹配,会导致数据错乱、panic 甚至间接泄露。比如把 NULL 扫进 string 变量,直接报 sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string</nil>。
- 所有可能为
NULL的字段,必须用sql.NullString、sql.NullInt64等接收,别依赖指针(*string在Scan时仍会 panic) - 扫结构体时,字段名要和
SELECT列别名严格一致:写SELECT name AS username,结构体就得有Username string字段 - 避免匿名变量扫描:
row.Scan(&id, &name, &email)—— 表加一列、改顺序,人眼很难发现漏对齐
最易被忽略的点:占位符只保值,不保结构。表名、字段名、ORDER BY、UNION 子句、LIMIT 数值……这些地方没有“参数化”这回事,全靠代码层白名单兜底。漏掉任意一环,前面所有 ? 都白搭。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











