转义、黑名单、前端校验:为什么这三样都挡不住注入
网络安全实战 90 天 · Week 3 注入风险 | 难度 ★★★ 进阶 | 关键词:输入转义 黑名单缺陷 前端校验 纵深防御 类型约束
前面几篇的验证在低防护级别下全部成立。把防护级别调高再试,payload 会大面积失效------但这不等于安全。这篇逐层拆解三类常见防护为什么失效,以及真正应该落在哪一层。全程在自建靶场进行,只做只读验证。
一、三类防护的定位
先说结论:转义、黑名单、前端校验,都属于「在错误的位置上做正确的事」。它们各自挡得住一部分输入,但都挡不住「同一件事换一种表达」。
| 防护手段 | 作用位置 | 典型失效原因 |
|---|---|---|
| 输入转义 | 应用代码层 | 只对字符串生效,数字型参数不受影响 |
| 关键字黑名单 | 应用或网关层 | 规则基于字符串匹配,语义等价写法数量无限 |
| 前端校验 | 浏览器层 | 前端从来不是安全边界,请求可以直接构造 |
说明 文中所有验证都在本机 Docker 中的 DVWA 靶场完成,属代码审计与自查练习。真实系统上的同类验证必须在书面授权范围内进行,并且只做只读判断,不修改任何数据。
二、第一层失效:转义只对字符串生效
把防护级别调高,再跑一遍前面用过的写法。页面不再报错,但这不是修好了,而是字符被处理了。
bash
text
1' OR '1'='1
$id = mysqli_real_escape_string($GLOBALS['___mysqli_ston'], $id);
单引号前面被加了反斜杠,引号再也闭合不了,整段 payload 退化成普通字符串。字符型注入在这里确实被掐断了。

2.1 但这不代表这个点安全
转义函数处理的是「字符串里的特殊字符」。如果参数最终被当作数字使用,字符串层的处理完全不参与------这就是下一节要看的入口。这是我这一周最大的认知转变:看到页面不报错就以为没有漏洞,是最容易犯的错。
三、第二层失效:前端限制不构成边界
高防护级别下前端做成了一个下拉框,只能从 1 到 5 里选。看着像限制,但这层限制运行在浏览器里。请求是客户端发出的,客户端可以任意构造。
bash
bash
# 用拦截工具修改请求体后重放
id=1 → id=1 OR 1=1
数字型参数不需要引号闭合,转义对它完全没有作用。改完转发,一次返回全部记录。

-
前端校验的价值是改善体验------提前提示用户输入有误,减少无效请求。
-
前端校验的安全价值是零。任何依赖前端的校验,等价于没有校验。
-
后端必须独立做一遍完整校验,且不假设前端已经做过。
四、第三层失效:黑名单的写法数量是无限的
很多自研规则的本质是一张关键字黑名单,用字符串匹配去拦。问题在于 SQL 语法本身对写法很宽容:大小写不敏感、注释可插入、空白可替代、字符串有多个等价表示。这四条加起来,等价写法的数量是无穷的。
4.1 大小写
bash
text
1 uNiOn SeLeCt 1,2 --
关键字本身不区分大小写,规则如果只匹配全大写或全小写,混写就能通过。
4.2 注释插入
bash
text
UN/**/ION SEL/**/ECT 1,2 --
/*!UNION*/ /*!SELECT*/ 1,2 --
把关键字从中间打断,解析器会忽略注释、拼回完整关键字,而字符串匹配看到的是一个不完整的词。第二行的内联注释更特殊------注释里的内容仍会被执行。

4.3 编码层级
bash
text
' → %27 (一次编码)
' → %2527 (两次编码)
SELECT * FROM users WHERE name=0x61646D696E
编码的关键是「解码次数对齐」:防护只看一次解码结果,那就在原始请求里多编一层。十六进制是另一条路------数据库把 0x 开头的十六进制当作字符串字面量处理,引号这个字符根本不会出现。
4.4 空白替代
bash
text
UNION/**/SELECT
UNION%09SELECT
UNION%0aSELECT
UNION(SELECT(1),(2))
空格是最容易被过滤的字符,但 SQL 对空白的要求很宽松:注释、制表符、换行、乃至括号分组都能起到分隔作用。

五、变形是有代价的
看到这里容易产生一个误解:变形越多越强。实际上每一次变形都在增加「应用自己解析不了」的风险------编码层数太多,参数在业务逻辑里就变成了乱码;tamper 规则挂太多,请求形态已经不像正常流量,触发拦截的概率反而上升。
这条规律反过来也是防护思路 只要把「同一语义的写法数量」压到很少,变形就没得选。参数化查询之所以有效,是因为它把「SQL 语法」和「数据」彻底分开了------数据变成数据之后,无论怎么变形都只是字符串内容,语法结构不会因为输入而改变。
六、按投入产出比排的整改顺序
| 优先级 | 措施 | 落地位置 | 能挡住什么 |
|---|---|---|---|
| P0 | 参数化查询 / 预编译绑定 | 数据访问层 | 从根上消除语法注入,与输入写法无关 |
| P0 | 参数类型强约束 | 接口入参层 | 数字型参数无论怎么变形都进不了 SQL |
| P1 | 数据库账号最小权限 | 数据源配置 | 即使注入成立,影响半径被压缩 |
| P1 | 错误信息统一处理 | 全局异常层 | 减少回显位,抬高推断成本 |
| P2 | 统一编码规范与规范化 | 网关/框架层 | 减少同一语义的多种字节表示 |
| P2 | 基于语义的检测规则 | WAF | 比字符串黑名单更难被等价写法绕开 |
七、踩坑记录
| 坑点 | 症状 | 解决办法 |
|---|---|---|
| 单引号被转义 | payload 全部失效但不报错 | 换数字型入口,或直接构造请求参数 |
| 以为绕过了其实没通 | 页面无变化 | 可能存在多层过滤,需要组合,且要能稳定复现才算数 |
| 变形规则挂太多 | 请求被自身逻辑判为非法 | 精准选一两个,够用就停 |
| 编码层数过多 | 参数变成乱码 | 逐层试,确认服务端实际解码了几次 |
| 请求被直接拒绝 | 无业务响应 | 降低速率、缩小改动幅度,如实记录受阻情况 |
-
「不报错」和「安全」是两件事,混为一谈会直接漏掉漏洞。
-
变形能否通过,必须稳定复现才算结论。一次成功可能只是缓存或抖动。
-
被拦截时如实记录受阻,比强行变形更有价值------受阻本身就是一条防护到位的证据。
八、小结
-
转义、黑名单、前端校验三类防护,失效原因各不相同,但共同点是都在处理「输入的形状」,而没处理「输入的身份」。
-
SQL 语法的宽容度决定了等价写法的数量是无限的,所以黑名单注定是军备竞赛。
-
参数化查询把语法与数据彻底隔离,是唯一从根上解决问题的做法,其余都是纵深。
-
变形有代价------越绕越容易被解析失败或触发拦截。这个代价是防守方可以主动利用的。
下一篇把这一周的方法串起来,在一个综合靶场里从识别走到验证,最后落成一份可复现的记录。