group by字段不能用?占位符,必须白名单校验;having、order by中的字段名、函数名、方向等结构部分均不可参数化,仅数值/字符串等值可用占位符,所有动态sql结构须严格白名单校验。

Group By字段不能用?占位符,必须白名单校验
SQL 标准不允许 GROUP BY 后跟参数化占位符,GORM 也不支持。写成 db.Group("?").Find(&users) 或 db.Group("$1").Find(&users) 会直接 panic 或被忽略——数据库协议层根本不允许运行时绑定标识符。
常见错误现象:程序不报错但结果异常(如全表聚合、空结果、字段名被当字面量),或日志里出现 ERROR: syntax error at or near "?"。
- 正确做法是预先定义允许的分组字段列表,例如
validGroupFields := map[string]bool{"status": true, "category": true, "created_at": true} - 收到用户输入后先校验:
if !validGroupFields[groupField] { return errors.New("invalid GROUP BY field") } - 再拼接进查询:
db.Group(groupField).Find(&users)
别把用户输入塞进Having子句字符串里
HAVING 是结构部分,不是值,db.Group("status").Having("count(*) > ?", threshold) 安全,但 db.Group("status").Having("status = '" + userInput + "'") 就立刻失守。
攻击者传入 "active' OR '1'='1",整个 HAVING 条件就变成恒真,可能泄露聚合统计结果。
- 所有出现在
HAVING中的字段名、函数名(如COUNT、AVG)、操作符都不可参数化,必须白名单 - 只允许数值、布尔、字符串等**值**走占位符,例如
db.Having("COUNT(*) >= ?", minCount) - 若需动态字段(如
HAVING AVG(price) > ?),字段price必须提前校验,不能来自用户直传
组合排序+分组时,ASC/DESC也要校验
很多人忘了 ORDER BY 和 GROUP BY 常一起用,而排序方向 ASC/DESC 同样属于 SQL 结构,无法参数化。
写成 db.Group("status").Order("created_at " + sortDir).Find(&users),若 sortDir 是 "DESC; DROP TABLE users --",就完了。
- 只接受硬编码值:
if sortDir != "ASC" && sortDir != "DESC" { return errors.New("invalid sort direction") } - 字段名和方向必须分开校验,不能合并成一个字符串再拼
- GORM 的
Order方法只对值部分做转义,对结构部分零防护
Raw()里用Group By更要加倍小心
一旦进了 db.Raw(),GORM 所有 ORM 层防护失效。哪怕你只在 GROUP BY 部分拼接,整条语句就已高危。
错误示例:db.Raw("SELECT status, COUNT(*) FROM users GROUP BY " + groupByField).Scan(&result) —— 表名、字段名、分组逻辑全裸奔。
- 如果非要用
Raw(),必须对groupByField进行白名单校验,且不能依赖任何字符串拼接 - 更稳妥的做法是改用
db.Table("users").Group(groupByField).Select("status, COUNT(*)").Scan(&result) -
Raw()中的GROUP BY字段若含表达式(如DATE(created_at)),表达式本身也得是白名单里的固定模板,不能由用户构造
status 分组,但没拦住 status, (SELECT password FROM admins) 这种嵌套子查询。白名单必须覆盖所有参与 SQL 结构生成的片段,一个都不能少。











