直接拼sql字符串等于主动交出数据库权限,因fmt.sprintf等拼接用户输入会导致sql注入,占位符是唯一安全方式,且需匹配驱动语法,in查询需用sqlx.in或gorm展开,表名字段名等标识符必须白名单校验。

直接拼SQL字符串等于主动交出数据库权限
只要用了 fmt.Sprintf、+ 或 strings.Join 把用户输入塞进 SQL 字符串里,就不是“可能被注入”,而是“已经裸奔”。哪怕参数当前来自配置文件或枚举,只要未来有扩展为用户输入的路径,这个位置就是漏洞。Go 的 database/sql 不做任何拦截,驱动只负责把整条字符串发给数据库——攻击者传入 admin' OR '1'='1,最终执行的就是 WHERE name = 'admin' OR '1'='1'。
占位符是唯一安全入口,但必须匹配驱动语法
MySQL 驱动认 ?,PostgreSQL 驱动认 $1、$2,SQLite 也用 ?。混用会直接报错:sql: expected 0 arguments, got 1 或 ERROR: syntax error at or near "123"。这不是配置问题,是驱动协议强制要求。
-
db.Query("SELECT * FROM users WHERE id = ? AND status = ?", id, "active")(MySQL/SQLite) -
db.Query("SELECT * FROM users WHERE id = $1 AND deleted = $2", id, false)(PostgreSQL) - 别写
db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))—— 这行代码在编译期合法,运行时危险
IN 查询不能直接传 slice,得靠 sqlx.In 或 GORM 自动展开
WHERE id IN ? 这种写法在原生 database/sql 下根本无效,? 只能对应单个值。传 []int{1,2,3} 过去,结果是 IN '[]int{1,2,3}',不是你想要的 IN (1,2,3)。
- 用
sqlx:先调query, args, _ := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids),再db.Rebind(query)转占位符,最后db.Queryx(reboundQuery, args...) - 用
GORM:直接db.Where("id IN ?", []uint{1,2,3}).Find(&users),它内部自动展开 - 别手写
fmt.Sprintf("IN (%s)", strings.Join(strs, ","))—— 白名单也救不了这种拼接
表名、字段名、ORDER BY 不能参数化,白名单是唯一防线
SQL 标准不支持对标识符(identifier)做参数化,所有驱动都会拒绝 ORDER BY ? 或 SELECT ? FROM users,轻则 panic,重则静默忽略。这时候动态性必须靠白名单校验,而不是“转义”或“过滤”。
- 允许排序字段仅限
["created_at", "updated_at", "name"],收到请求后先if !isValidSortField(input) { return err } - 分表查询如
logs_202409,表名后缀必须由时间生成,不能由用户传入字符串拼接 - 别信
strings.ReplaceAll(input, "'", "''")或正则过滤——这些在多字节字符、注释绕过、编码混淆面前完全失效
真正难防的不是技术点,是开发时那句“这次只是内部用,不用太严格”的自我安慰。一旦某处开了 fmt.Sprintf 的口子,后续所有人就会默认“这里可以拼”,直到某次上线新接口,用户输入流经此处,数据库就亮了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











