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

database/sql 本身不导致 SQL 注入,拼接才导致——Go 1.21 没改变这个事实,它只是让开发者更容易“以为自己安全”而已。
db.Query 和 db.Exec 不校验 SQL 字符串内容
database/sql 是个接口层,底层驱动(如 github.com/lib/pq 或 github.com/go-sql-driver/mysql)才真正执行语句。只要传进去的是拼好的字符串,驱动就照单全收。
- Go 1.21 没新增任何自动转义、自动拦截或运行时检测机制
-
fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)这种写法在 1.20、1.21、1.22 都一样危险
常见错误现象包括:
- 用户输入
' OR '1'='1→ 查询变成WHERE name = '' OR '1'='1',条件恒真 - 输入
admin'; DROP TABLE sessions; --→ 两条语句被执行(取决于驱动是否支持多语句) - URL 中传
?sort=created_at; SELECT pg_sleep(5),若拼进ORDER BY,直接拖慢甚至阻塞数据库
QueryRow 等方法的占位符不是可选功能,是唯一安全路径
Go 1.21 继续要求你显式使用占位符:?(MySQL)、(PostgreSQL)、:1(SQLite3)。不这么用,就没有隔离。
- 占位符必须出现在 SQL 字符串字面量中,不能由变量拼出来(比如
"ORDER BY " + sortField) - 参数必须作为独立参数传入,不能塞进字符串里再
db.Query(sqlStr, ...)
示例对比:
// ❌ 危险:拼接后传入 Query,占位符失效
sql := "SELECT * FROM logs WHERE level = '" + level + "'"
rows, _ := db.Query(sql) // level='error' -- 看似正常,但 level="' OR 1=1 --" 就崩了
// ✅ 安全:占位符 + 独立参数
rows, _ := db.Query("SELECT * FROM logs WHERE level = ?", level)
表名、字段名、ORDER BY 方向这些无法参数化的地方最常被忽略
Go 1.21 没提供语法糖来参数化标识符,所以开发者容易“折中”:
- 用
map[string]string{ "created": "created_at", "name": "user_name" }做白名单映射 - 用正则
^[a-zA-Z<em>][a-zA-Z0-9</em>]*$校验字段名(注意:不能只校验字母数字,下划线合法但点号、括号、反引号非法) - 把排序方向硬编码为
"ASC"或"DESC",而不是拼"ORDER BY created_at " + dir
容易踩的坑:
- 认为 ORM(如 GORM)的
Where("status = ?", status)安全,就放心用它的Raw("SELECT * FROM "+tableName)——tableName仍是拼接 - 在日志里打印拼接后的 SQL 字符串,等于把攻击 payload 直接写进日志文件
- 使用
sql.NullString接收字段时没处理,导致空值逻辑错乱,间接影响权限判断
真正的分水岭不在 Go 版本,而在你写每一行 SQL 时,是否问过自己:这个字符串里有没有哪怕一个字节来自用户输入? 如果有,又没走占位符,那 Go 1.21 和 Go 1.0 没区别——都是开门放人。











