gin本身不防sql注入,因其仅为http路由框架,不处理数据库逻辑;真正防护依赖数据库层(如gorm参数化查询或database/sql的prepare机制)及开发者是否避免字符串拼接。

为什么Gin本身不防SQL注入
Gin 是一个 HTTP 路由框架,它不处理数据库逻辑,也不干预你如何拼 SQL。它只负责把 c.PostForm("email") 或 c.Query("id") 这类方法拿到的字符串交给你——至于你是拿去直接拼进 db.Raw(),还是喂给 GORM 的 Where(),Gin 完全不管。所以“Gin 防注入”是个常见误解,真正起作用的是你用的数据库层(比如 GORM、database/sql)以及你写代码的方式。
GORM 默认安全,但 Raw() 是高危出口
GORM 的 Where()、First()、Find() 等方法默认走参数化查询,输入值不会参与 SQL 解析,这是安全的。但一旦你调用 db.Raw(),就等于绕过所有保护,直连数据库引擎——这时候如果还用字符串拼接,就等于把门钥匙交给攻击者。
- ✅ 安全写法:
db.Raw("SELECT * FROM users WHERE status = ? AND role = ?", status, role).Scan(&users) - ❌ 危险写法:
db.Raw("SELECT * FROM users WHERE email = '" + email + "'").Scan(&users) - ⚠️ 注意:MySQL 的
?占位符不能用于表名、字段名、ORDER BY 子句——这些必须靠白名单校验或硬编码,不能由用户输入动态决定
database/sql 的 Prepare 机制才是底层防线
即使不用 GORM,Go 原生的 database/sql 也支持预编译语句,这是数据库协议级防护,比任何应用层过滤都可靠。只要用 db.Prepare() + stmt.Query() 模式,用户输入就永远只是参数值,不会变成语法的一部分。
- 不要写:
rows, _ := db.Query("SELECT * FROM articles WHERE title = '" + title + "'") - 要写:
stmt, _ := db.Prepare("SELECT * FROM articles WHERE title = ?"); rows, _ := stmt.Query(title) - 注意:
Prepare返回的Stmt可复用,但需手动Close(),否则可能泄漏连接 - 在 Gin handler 中,建议把
Prepare提前做好(比如 init 阶段或依赖注入),避免每次请求都重复编译
权限控制和输入校验是不可跳过的补位措施
参数化查询能防注入,但不能防业务逻辑错乱。比如用户传来负数 ID、超长邮箱、非法状态码,这些虽不导致注入,却可能暴露数据或触发异常路径。更关键的是:就算注入成功,数据库账号没删库权限,攻击者也干不了大事。
- 数据库账号只授予
SELECT、INSERT、UPDATE,禁用DROP、ALTER、EXECUTE - 对
c.Query("id")这类参数,先做类型转换:id, err := strconv.ParseUint(c.Query("id"), 10, 64),失败就直接c.AbortWithStatusJSON(400, ...) - 对模糊搜索字段(如
LIKE查询),允许通配符但需转义:strings.ReplaceAll(input, "%", "\%"),并在 SQL 中加ESCAPE '\'
真正的防护不是靠某一行代码,而是从输入解析、参数绑定、SQL 构建、数据库权限到日志监控的整条链路都默认不信任用户输入。最容易被忽略的,其实是表名/字段名动态化和权限配置——这两处没卡死,前面再严也没用。











