SQL 注入为什么在 ORM 项目里还发生
上了 ORM,用了占位符,禁了字符串拼接,报告里照样出注入。原因不复杂:参数化保护的是值,不保护语句结构。
一条"做过防护"的查询
一个典型现场。列表接口支持排序,前端给字段名,后端把字段名直接传给查询层;其余条件都做了参数化,团队自查过一遍,结论是安全。
审计报告给出的位置在排序字段上。团队的第一反应是"这里做过校验",第二反应是"排序字段用户选不了"。
两句话都不成立。校验覆盖的是值,排序字段属于结构;"用户选不了"是界面约束,请求可以被直接构造。
分歧不在有没有防护,在防护覆盖了哪一部分。
ORM 保护的是值,不是结构
预编译做一件事:把值与 SQL 结构分开。语句模板先固定,值作为独立参数传给数据库。绑定发生在驱动/协议层,不同驱动实现不同,但保证同一件事——值不参与 SQL 结构。
由此得出一条判据:凡是拼进结构的部分,都绕过这层保护。
占位符本身不能成为结构的一部分。? 不能变成列名,不能变成排序方向,也不能决定 in 里有几个元素。写法上把变量放进引号里,看起来像参数化,实际仍然是拼接。
七个位置,两类处理
常见位置七处(4 类结构位 + 3 类值位),按性质分成两类,比笼统叫"注入点"更有用。
| 类型 | 位置 | 处理 |
|---|---|---|
| 标识符 / 结构位 | order by 字段与方向;表名与列名;in 的列表形态;原生片段 ${} | 不能绑定,只能枚举白名单 |
| 值位但语义敏感 | limit 偏移、like 前后缀、JSON 路径 | 先绑定,再校验取值范围或格式 |
这张表有个前提要先说清:like 的 % 位置、limit 的取值、JSON 路径,在注入层面仍然是值位。绑定能挡住注入,挡不住语义被改——limit 传负数、like 被加上前导通配、JSON 路径指向另一个键,这些是业务语义问题,不是语法位问题。把它们硬说成"语法位",修复方案就会写歪。
两类的处理方式也不同:结构位靠枚举,值位靠绑定加校验。混在一起谈,修法一定偏。
order by 的现场
下面这条语句里,两种写法同时出现:
<select id="list" resultType="OrderRow">
select id, amount, created_at
from t_demo
order by ${sort} limit #{size}
</select>
${sort} 是字符串替换:MyBatis 在拼 SQL 之前就把变量的字面值填进去,既不转义,也不绑定。#{size} 是占位符:值走绑定参数,不进入语句结构。
同一个语句、同一个映射文件、同一次请求,一个安全一个不安全。差别不在框架,在"这个值有没有参与语法构建"。
排序字段的白名单要连方向一起枚举。created_at desc 里的 desc 也是结构的一部分,只校验字段名,就等于漏掉一半。写法三步:trim、转小写、去表别名,再和映射表比较。不用黑名单,因为方向只有升与降,枚举得完。
排序字段常被当成"前端下拉框,用户选不了别的",于是不做校验。但下拉框只是前端的约束,请求可以被直接构造。判断可控性看服务端,不看界面。
PHP 侧同一处边界也存在:orderByRaw、Laravel 的 whereRaw 如果只把值拼进第一个参数,就不再受绑定保护;把值放进第二个参数,仍然是绑定。写法不同,判据相同。
这部分的判据与代码审计册第 11 章 11.4 一致:预编译只保护值位,结构位只能靠白名单枚举,且白名单必须在规范化之后比较。
审计时看什么
三件事,按顺序。
一、搜结构位,不搜函数名。 在映射文件里搜 ${,在仓储层搜排序与分页参数的来源。函数名搜不出来,因为缺陷常常长成"传了一个字段名"。
二、看参数从哪来。 排序字段、分页大小、表名,是不是从请求直接透传到查询层。中间有没有类型收敛、有没有枚举。
三、看覆盖率。 同一个映射文件里 ${} 与 #{} 的比例。比例失衡往往说明有人图省事,把该绑定的也拼了。
再加一条容易被忽略的:白名单有没有做规范化。没有 trim、没有统一大小写、没有去表别名,映射表再全也能被绕过。
怎么修
四句话,各自对应一类问题。
值走绑定:所有值位参数用占位符,不用字符串拼接。
结构位走枚举:排序字段与方向、表名、列名,全部落进映射表,规范化后比较。in 的元素值仍走绑定,列表形态由代码决定。
原生 SQL 收口:把拼接集中到一层,其他层不允许出现 ${}。
语义敏感的值单独处理:limit 校验为正整数并设上限,like 的 % 由服务端加,JSON 路径按白名单校验键名。
参数化不是"用了 ORM",是"值没有进入语法"。 这句话可以当验收标准用:改完之后,凡是被拼进结构的位置,都应该能指到一个枚举表。
本文仅用于安全学习与授权环境下的测试。文中所有示例均为本地或授权靶场环境,不构成对任何真实系统的测试指引。
仅限授权测试,严禁非法滥用。
读到这里,说明你在认真看。加入社群,与我们一起深挖底层。
或点此直接进入「星巡底层研习」 →
扫码加入交流社群