Gin接口安全关键在于避免SQL注入,核心是严格使用参数化查询而非拼接用户输入;GORM链式方法默认安全,但db.Raw()需手动确保占位符使用,ORDER BY等动态字段必须白名单校验,并配合最小权限、输入校验和日志脱敏。

用 Gin 写接口时,只要没手动拼接 SQL 字符串,基本不会中招。真正危险的不是 Gin 本身,而是你调用数据库时绕过 GORM 或 database/sql 的预编译机制——比如用 db.Raw() 直接插变量、用字符串格式化拼 SELECT、或者把用户输入塞进 ORDER BY / GROUP BY 子句里。
为什么 GORM 默认安全,但 db.Raw() 是高危入口
GORM 所有链式方法(Where、First、Find)底层都走参数化查询,SQL 结构和值严格分离。但 db.Raw() 不做任何拦截,传进去什么就发给数据库什么。
- ✅ 安全写法:
db.Raw("SELECT * FROM articles WHERE status = ? AND boss_id = ?", status, bossID).Scan(&list) - ❌ 危险写法:
db.Raw("SELECT * FROM articles WHERE status = " + statusStr).Scan(&list)(statusStr来自c.Query("status")) - ⚠️ 隐蔽陷阱:
db.Raw("SELECT * FROM articles ORDER BY " + sortBy + " LIMIT ?", limit)——sortBy若为"id; DROP TABLE articles; --",就直接执行了
哪些地方容易漏掉参数化,却以为“用了 GORM 就安全”
开发者常误以为“用了 GORM 就万事大吉”,但在这些场景下仍可能裸奔:
-
SELECT中的动态字段名(如SELECT {{.Field}} FROM ...),GORM 不支持占位符替换字段名,必须白名单校验 -
ORDER BY、GROUP BY、HAVING后的列名或表达式,不能用?占位,需提前判断是否在允许列表中(如map[string]bool{"id": true, "created_at": true, "title": true}) - 原生 SQL 中带子查询或
UNION,若子查询部分拼接了用户输入,整个语句就失效 - 用
sql.NullString或自定义类型接收参数时,忘记检查其Valid字段,导致空值被当作字符串""参与拼接
除了参数化,还有三处必须卡死的防线
光靠预编译不够,攻击者可能绕过 SQL 层,在应用逻辑或数据库权限上找突破口:
- 数据库账号必须遵循最小权限原则:禁止
DROP、ALTER、CREATE、LOAD DATA等高危权限,只给SELECT、INSERT、UPDATE、DELETE和必要索引操作 - 对所有用户输入做基础校验:长度限制(如邮箱 ≤ 254 字符)、格式正则(如
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)、整型字段强制转strconv.Atoi并捕获错误 - 开启 GORM 日志并过滤敏感字段:
logger.Default.LogMode(logger.Info)能暴露原始 SQL,但上线前务必关闭或脱敏password、token类字段输出
最易被忽略的是动态排序和分组字段的校验——它不触发 SQL 报错,也不违反语法,但一旦放行非法列名,就等于把表结构和索引信息主动交出去。别信“前端传过来的肯定可控”,所有 c.Query、c.PostForm、c.Param 都得过白名单。


















