go语言防sql注入核心是:值必须用占位符(mysql用?、postgresql用$1)、标识符(表名/字段名/order by)必须白名单校验,database/sql本身不防注入,安全取决于开发者是否严格分离sql结构与用户输入。

database/sql 的 Query/Exec 必须带占位符,否则就是裸奔
Go 本身不防注入,database/sql 只是接口层,真正起作用的是你有没有把用户输入当“值”传进去。写错占位符或漏传参数,等于没防。
- MySQL 驱动(
github.com/go-sql-driver/mysql)只认?,写$1会报sql: expected 0 arguments, got 1 - PostgreSQL 驱动(
github.com/lib/pq)只认$1、$2,用?直接语法错:pq: syntax error at or near "?" - SQLite 虽支持两者,但行为不一致:用
?是位置绑定,用$1是命名绑定,混用容易漏参 - 哪怕查固定字符串,比如
"WHERE status = 'active'",只要这个值来自 URL 参数或表单,就必须走占位符——"WHERE status = ?"+status
GORM 的 Raw() 不是安全开关,Table() 才是白名单入口
GORM 默认开启 PrepareStmt,但一调 Raw() 就退出防护区。很多人误以为 “用了 GORM 就安全”,结果在 Raw() 里拼表名,等于亲手拆掉防火墙。
-
db.Raw("SELECT * FROM users WHERE name = ?", name)安全;db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id)危险 - 动态表名必须走
db.Table(tableName),且tableName必须来自枚举、配置 map 或 switch 判断,比如:switch tenant { case "prod": return "users_prod"; case "dev": return "users_dev" } -
db.Table()本身不校验表名是否存在,它只是把字符串塞进 SQL 模板;真正校验要靠前置白名单,不是靠 ORM 替你兜底 - 别信
strings.ReplaceAll(tableName, "'", "")这类“过滤”,users; DROP TABLE accounts --这种 payload 会直接绕过
ORDER BY / 字段名 / GROUP BY 不能参数化,只能白名单硬控
SQL 标准规定标识符(表名、字段名、排序方向)属于查询结构,在数据库编译阶段就要确定,没有运行时绑定机制。试图用 ? 或 塞进去,必然 panic:sql: expected 0 arguments, got 2。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 排序字段白名单示例:
validSortFields := map[string]bool{"created_at": true, "score": true, "name": true},查不到就拒掉 - 排序方向只允许
"ASC"或"DESC",别信strings.ToUpper(dir)后直接拼,"ASC; DROP TABLE users --"会原样执行 - 多字段排序需拆解:
ORDER BY created_at DESC, score ASC→ 用strings.Builder拼,但每个字段和方向都独立校验,不是整体校验字符串 - JSON 配置里的排序字段也得校验——后端解析
{"sort": [{"field": "email", "dir": "ASC"}]}时,field和dir都要过白名单
multiStatements=true 是隐形炸弹,生产环境必须关死
MySQL 默认关闭多语句执行,但有些旧项目或迁移脚本会在 DSN 里显式加 multiStatements=true。一旦开启,用户输入里塞分号就能一条命令干多件事,Raw() 或拼接 SQL 会立刻变成高危路径。
- 检查 DSN 是否含
multiStatements=true,生产环境务必设为false(默认就是false,除非你手动开了) - 别依赖驱动自动过滤;
mysql.ParseDSN解析后要显式覆盖:cfg.MultiStatements = false - 日志打印 SQL 时,只打模板(如
"SELECT * FROM users WHERE id = ?"),别打拼接后的完整字符串——否则你会误判“没拼接就安全” -
sql.RawBytes不是 SQL 构造工具,它只该出现在Scan接收 BLOB 字段时;拿它拼 SQL 和用fmt.Sprintf没区别
真正的防注入模块不是封装一个“安全查询函数”,而是建立三层控制:值走占位符、结构走白名单、权限走最小化。最容易被忽略的点是——动态标识符校验必须发生在 SQL 构造之前,而不是之后;且白名单必须硬编码或从可信配置加载,不能从数据库读、不能从用户输入推导。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










