gin+gorm项目中必须禁用字符串拼接sql,一律使用参数化查询(如db.where("name = ?", username))、动态字段白名单校验、最小权限数据库账号,并启用gorm泛型api与gen增强类型安全。

直接用 db.Where 拼字符串、db.Raw 里塞 fmt.Sprintf、把用户输入当表名或排序字段用——这三类操作在 Gin + GORM 项目里,等于给攻击者开了数据库后门。安全不是加个中间件就能解决的事,得从 SQL 构造那一刻就掐死风险。
别碰字符串拼接的 WHERE 条件
这是最常见也最致命的习惯。比如写成 db.Where("name = '" + username + "'"),攻击者传 admin' OR '1'='1 就能绕过验证,查出所有用户。
- ✅ 正确写法只有一种:
db.Where("name = ?", username)或db.Where("status IN ?", statuses),GORM 会自动展开 slice - ⚠️ 注意
IN子句不能传单个?然后塞字符串,比如"1,2,3"—— 必须传[]string{"1","2","3"} - ❌ 即使加了单引号、做了
strings.Replace也不行,驱动层根本没机会转义,SQL 解析器已经把它当语法的一部分了
Raw SQL 必须用占位符,动态字段名必须走白名单
db.Raw 不是“免检通道”,它只保证占位符部分安全,其余全是裸奔区。比如 db.Raw("SELECT * FROM " + tableName + " WHERE id = ?", id),表名一拼就炸。
- ✅ 安全用法只有:
db.Raw("SELECT * FROM users WHERE name = ? AND deleted_at IS NULL", name) - ✅ 动态排序字段(如
ORDER BY)必须白名单校验:validSorts := map[string]bool{"created_at": true, "score": true},查不到就拒掉 - ✅ 表名映射用硬编码分支:
switch tenantID { case "prod": table = "users_prod"; case "dev": table = "users_dev" } - ⚠️
db.Raw("UPDATE users SET status = ? WHERE id IN (" + ids + ")", status)是高危写法——ids若来自用户且未校验,"1, (SELECT password FROM admins)"就能触发注入
GORM 泛型 API 和 GEN 能提前拦截多数错误
传统 *gorm.DB 是泛型擦除的,db.First(&order) 却查 User 表也不会报错;而 gorm.G[User](db) 把模型和查询强绑定,类型错直接编译失败。
- ✅ 必须带
context:gorm.G[User](db).Where("name = ?", name).First(ctx, &user),否则不支持超时控制 - ✅ 关联预加载也受保护:
gorm.G[User](db).Preload("Orders").Find(ctx, &users),字段名写错会编译报错,不会静默查错表 - ✅ GEN 把注释里的 SQL 模板(如
// select * from users where name = {{.name}})编译成类型安全方法,连字段名拼错都过不了编译 - ⚠️ 注意:泛型 API 要求 GORM >= v1.30.0;GEN 默认启用
WithContext,忘记传ctx会 panic
数据库账号权限和 NULL 字段处理常被忽略
参数化查询防得住语法注入,但挡不住业务逻辑漏洞。比如前端传来的 status 值没做白名单校验,直接进 WHERE status = ?,攻击者就能查出所有状态的数据。
- ✅ 数据库连接账号禁用
DROP、ALTER、CREATE权限,即使注入成功也删不了表 - ✅
NULL字段必须用sql.NullString等类型接收,否则 Scan 会 panic;结构体字段顺序必须和SELECT列一致,否则值错位 - ⚠️ 最难防的不是
' OR 1=1,而是“本该校验却没校验”的字段——比如租户 ID、角色级别、软删除标记,这些往往在业务层被跳过校验
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











