必须使用占位符防sql注入,order by/表名/字段名等需白名单校验,null字段须用sql.null*类型接收,raw/where拼接仍需严格校验。

database/sql 的 Query/Exec 必须带占位符,别拼字符串
Go 本身不防注入,database/sql 包只是个接口层,真正起作用的是你传进去的 SQL 字符串是否干净。只要用了 fmt.Sprintf、+ 或 strings.Join 拼接用户输入,就等于把数据库交出去了。
- MySQL/SQLite 驱动只认
?:比如db.Query("SELECT name FROM users WHERE id = ? AND status = ?", id, "active") - PostgreSQL 驱动只认
$1、$2:比如db.Query("SELECT name FROM users WHERE id = $1 AND deleted = $2", id, false) - 混用会直接报错:
sql: expected 0 arguments, got 1或ERROR: syntax error at or near "123" - 常见错误现象:
sql: no rows in result set看似查不到数据,其实是参数没进占位符,被当字面量处理了
ORDER BY、表名、字段名不能用 ? 或 $1
SQL 标准不允许参数化标识符(identifier),驱动根本不支持。试图写 ORDER BY ? 会 panic,而有人接着改成 fmt.Sprintf("ORDER BY %s", input),这就彻底裸奔了。
- 排序字段必须白名单校验:比如只允许
[]string{"created_at", "name", "score"},用map[string]bool快速判断 - 表名映射建议用常量或
switch:用户输入"prod"→ 实际查"users_prod",而不是"users_" + input - 排序方向(
ASC/DESC)同样要白名单,不能靠参数传 -
LIMIT、OFFSET、GROUP BY同理,不支持参数化,必须走校验+拼接
Scan 接收时类型和 NULL 必须显式处理
Scan 不参与防注入,但它出错会暴露结构、引发 panic,甚至间接导致逻辑异常。这不是注入,但危害不亚于注入。
- 数据库字段可能为
NULL,却用普通string接收 →panic: sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string</nil> - 所有可能为
NULL的字段,必须用sql.NullString、sql.NullInt64等类型接收 - 扫描到结构体时,字段顺序必须和
SELECT列严格一致;用SELECT id, name AS username就得对应结构体字段Username string - 别堆匿名变量:
row.Scan(&id, &name, &email)—— 改表后极易漏,人眼难对齐
GORM 的 Raw 和 Where 拼接是高危区
ORM 不是免检金牌。Raw()、Session()、手动拼接的 Where 都可能绕过参数化保护。
-
db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id)—— 表名未校验,照样中招 -
db.Where("name = '" + name + "'")或db.Where("age > " + ageStr)—— 后半段崩了 - 安全写法:
db.Table(tableName).Where("id = ?", id).Find(&u),前提是tableName来自枚举或配置项 - 即使开了
PrepareStmt: true,Raw()也退化为字符串拼接,防护失效
动态部分最难兜住的不是值,而是结构——表名、字段名、排序方向这些地方,白名单校验一旦松动,参数化就形同虚设。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











