go中sql注入高危操作是拼接sql字符串,必须用参数化查询(如?或$1占位符),严格校验输入,禁用orm全局操作,并在测试中覆盖恶意输入场景。

Go 里用 database/sql 拼接 SQL 字符串就是高危操作
只要看到 fmt.Sprintf 或 + 拼接 SQL 查询,基本等于在入口处敞开了 SQL 注入大门。Go 的 database/sql 原生支持参数化查询,但不强制——你不用,它就真不拦。
- 常见错误现象:
sql.ErrNoRows看似正常,但实际请求中带' OR 1=1 --却返回了全部用户数据 - 使用场景:登录验证、搜索接口、ID 查询(如
SELECT * FROM users WHERE id = %s) - 正确做法:一律用
?占位符 +db.Query/db.Exec的变参传入,例如db.Query("SELECT * FROM users WHERE name = ?", username) - 注意:PostgreSQL 驱动(
lib/pq)用$1,MySQL(go-sql-driver/mysql)用?,别混用;SQLite 也认?
自定义 HTTP handler 中未校验输入就透传到 SQL 就是漏洞温床
很多 Go Web 服务直接把 r.URL.Query().Get("id") 或 json.Unmarshal 后的字段塞进 SQL,中间零过滤、零类型断言、零长度限制。
- 常见错误现象:请求
/api/user?id=1%20UNION%20SELECT%20password%20FROM%20users返回密码字段 - 使用场景:REST API 的 GET 查询参数、POST JSON body、URL 路径变量(如
/user/:id) - 实操建议:
– 对数字 ID 强制转strconv.Atoi,失败即拒;
– 对字符串字段做白名单校验(如用户名只允许[a-zA-Z0-9_])或长度截断(string[:50]);
– 别信html.EscapeString,它对 SQL 注入完全无效
ORM 如 GORM 默认开启 AllowGlobalUpdate 会放大风险
GORM v2 默认允许全局更新/删除,配合构造的 map 输入,一条 db.Where(input).Delete(&User{}) 可能删光整表。
- 常见错误现象:
curl -X POST /admin/delete -d '{"status": "active"}'导致所有 active 用户被删 - 参数差异:
db.Session(&gorm.Session{AllowGlobalUpdate: false})才是安全基线;v2.2+ 还需显式关掉Config.SkipDefaultTransaction = true避免隐式事务绕过检查 - 性能影响:禁用全局操作几乎无开销;但若业务真需要批量操作,请改用明确主键列表 +
IN查询,而非开放 where 条件 - 兼容性注意:GORM v1 的
Unscoped()和 v2 的Session行为不一致,升级时务必重审所有Delete/Update调用点
写单元测试时只 mock DB 层会漏掉注入逻辑
mock sqlmock 或 gomock 很容易只验证「SQL 字符串是否匹配预期」,却忽略「这个字符串是怎么拼出来的」。真正的注入路径藏在参数组装逻辑里。
立即学习“go语言免费学习笔记(深入)”;
- 实操建议:
– 测试用例必须覆盖恶意输入:传"admin' --"、"1 OR 1=1"、"test%; DROP TABLE users;";
– 不要只断言 error 是否为 nil,要检查最终执行的 SQL 是否含未转义单引号或注释符;
– 在集成测试中启用sqlmock.ExpectQuery并开启ExpectationsWereMet,确保没漏掉任何非参数化调用 - 容易踩的坑:用
reflect.DeepEqual比较 struct 输入时,忽略嵌套 map 或 interface{} 字段可能被恶意污染;这类字段应单独做 schema 校验
最麻烦的不是不会写参数化查询,而是有些老代码里 SQL 构造分散在 3 个 helper 函数 + 1 个模板里,还混着 fmt.Sprintf 和 strings.ReplaceAll。这种地方没法靠 lint 工具扫出来,得一行行看执行流——尤其注意那些“只是读配置”的函数,它们很可能悄悄拼了 SQL。









