go-zero的sqlx使用?或$1占位符且不拼接字符串即可防SQL注入;表名、字段名等需白名单校验;IN子句用sqlx.In配合Rebind;NULL字段须用sql.Null类型接收。

go-zero 的 sqlx 用 ? 占位符就安全,但只限值传参
go-zero 底层用的是 database/sql + sqlx,所以防注入逻辑和标准库完全一致:只要参数走 ?(MySQL/SQLite)或 $1(PostgreSQL),且不拼字符串,就不可能被注入。比如:
err := r.conn.QueryRow(&user, "SELECT id,name FROM users WHERE status = ? AND created_at > ?", status, time.Now())
这行代码里 status 和 time.Now() 都是纯值,驱动会把它们和 SQL 模板分开发送给数据库,哪怕 status 是 "active' OR '1'='1",也只会查状态字面量为那个字符串的用户。
- MySQL 驱动只认
?;写成$1不报错但参数被忽略,查不到数据 - PostgreSQL 驱动只认
$1、$2;写成?会 panic:sql: expected 0 arguments, got 1 -
sqlx.In可处理IN子句,但得配sqlx.In("id IN (?)", ids)+r.conn.Rebind,不能直接塞 slice 到单个?
表名、字段名、ORDER BY 必须白名单校验,go-zero 不帮你拦
go-zero 的 sqlx 封装不改变 SQL 协议限制:表名、字段名、ORDER BY、GROUP BY、LIMIT 这些属于查询结构,数据库编译阶段就要确定,没法参数化。你写:
r.conn.QueryRow(&user, "SELECT * FROM ? WHERE id = ?", tableName, id)
必然 panic:sql: expected 0 arguments, got 1。真正该做的是在进 SQL 前就卡死非法输入:
- 排序字段用 map 白名单:
validSort := map[string]bool{"created_at": true, "score": true},不在里面就直接 return error - 排序方向必须显式判断:
if dir != "ASC" && dir != "DESC" { return errors.New("invalid sort direction") } - 表名映射用 switch 或常量:
switch tenant { case "prod": table = "users_prod"; case "test": table = "users_test" },别写"users_" + tenant
GORM 混用时 Raw() 是重灾区,go-zero 项目里尤其要盯紧
很多 go-zero 项目为了复杂查询临时引入 GORM,结果在 db.Raw() 里拼接表名或字段名,等于把防护墙拆了。例如:
db.Raw(fmt.Sprintf("SELECT * FROM %s WHERE name = ?", tableName), name).Scan(&user)这里 tableName 是用户可控的,"users; DROP TABLE admins--" 就能触发注入。即使用了 ?,GORM 也只保证占位符部分安全,不拦截前面的 fmt.Sprintf。
- 所有
Raw()调用必须人工审计:确认无任何用户输入参与字符串拼接 - 动态字段名如
ORDER BY {{field}},必须先过白名单再拼进 SQL 字符串 - 别信 “我转义了单引号”,Unicode 空格、
/**/、换行都能绕过简单 replace
Scan 类型错位和 NULL 处理不当,也会导致静默错误或 panic
go-zero 的 sqlx QueryRow / QueryRows 返回的 rows 对象调用 Scan 时,不校验字段名,只按 SELECT 列顺序和类型硬匹配。一旦表结构变更、代码没同步,轻则数据错位,重则 panic:
panic: sql: Scan error on column index 0: unsupported Scan, storing driver.Value type <nil> into type *string
这类问题和注入无关,但危害不小,且常被忽略:
- 所有可能为 NULL 的字段,必须用
sql.NullString、sql.NullInt64等接收,不能直接用string或int - 结构体字段名要和 SELECT 别名严格一致,比如
SELECT name AS username就得对应Username string - 避免用匿名变量扫描:
row.Scan(&id, &name, &email)—— 表加字段后极易漏对齐,维护成本高
真正难防的不是语法层面的注入,而是业务字段本该校验却漏掉——比如前端传来的 status 值,没走白名单就直接进了 WHERE status = ?,这种漏洞不会报错,但会让攻击者自由切换任意状态值。

















