接到这种标题,第一反应通常都是“又来一个卖课的”。但真在自己的项目里遇到过几次目录遍历绕过WAF之后,我得说:这个方向被讲烂了,真正讲透的人不多。很多人要么只会背payload,要么只会在靶场里打得不亦乐乎,换个真实应用的语义环境就抓瞎。这篇我不想整那些“从入门到放弃”的空话,直接把我自己在做路径穿越绕过、WAF规则对抗时总结的一套流程和库给你,重点说清楚为什么有些Payload在其他场景落地直接失效,以及怎么去体系化地组织和验证这些内容。
目录遍历本身不是新技术,但WAF绕过一直是攻防里比拼规则更新和解析差异的活。很多同学手里拿着网上扒下来的几百上千条Payload,打靶场没问题,上了真实授权项目就变成了“为什么我的POST发出去直接413,或者绕不过边缘节点的过滤规则”。问题往往不在Payload本身,而在于你根本不知道WAF和中间件之间到底谁在解码、解码了几次、解码顺序又是什么。
这篇内容适合刚接触Web安全的同学建立绕过思路,也适合已经在写脚本批量验证Payload的人参考一下怎么把800+条Payload按场景切成块,免得以后每次都被“Payload太多跑不完”“跑了半天全是误报”折腾到没脾气。我尽量少说废话,全部按我自己实测过、在授权环境下验证过的路径来讲。
1. 目录遍历与WAF绕过的底层逻辑:先搞清楚绕的是什么
目录遍历漏洞的根源,说白了就是程序拿着用户可控的路径参数去拼接文件路径时,没有做好规范化处理,导致../这类路径穿越符号能够跳出预期目录,读到/etc/passwd、Windows系统文件或其他敏感文件。这个漏洞从Web 1.0时代就有,到现在仍然频繁出现,原因就是很多开发对路径拼接这件事过度信任,总觉得框架会帮自己处理干净。
而WAF绕过,核心其实是“语义还原”问题。WAF要拦截的是“经过解码和规整后的最终请求路径里包含穿越特征”,但它看到的是原始HTTP报文。于是整个绕过过程就变成了:我能不能通过某种编码、某种协议特性、某种服务器解析差异,让WAF还原出来的语义和Web后端还原出来的语义不一致。
我举一个最典型的例子。你发送:
GET /download?file=..%252f..%252fetc/passwd HTTP/1.1WAF可能做了两次URL解码,把%252f还原成%2f,然后发现没有../或..%2f特征,直接放行。而真实后端如果只做了一次URL解码,拿到的就是..%2f..%2fetc/passwd,中间件在访问文件系统前还会再做一次路径规范化,把%2f还原成/,于是穿越成功。关键就在于两边解码次数不一致。
这就解释了为什么网上很多Payload抄过来就是打不了:因为你这个站点的WAF解码次数、后端框架的路径处理方式,和人家写Payload时的环境根本不一样。所以真正能打的Payload,不是背出来的,是测出来的。
1.1 三种角色在路径处理上的视角差异
在目录遍历绕过里,有四个角色在各自的规则里理解这段路径:
| 角色 | 理解方式 | 典型动作 |
|---|---|---|
| 客户端/攻击者 | 原始字符串 | 往参数里塞各种编码字符 |
| WAF/网关 | 按规则做解码还原 | 匹配../等特征后拦截 |
| Web中间件 | 按RFC做路径解析 | 合并反斜杠、压缩多余斜杠、处理.符号 |
| 应用框架 | 拿到最终路径参数 | 不做校验,直接把值拼到文件路径上 |
WAF和中间件只要对同一个字符的标准化结果不一样,就有绕过的空子。这就是全部原理,其他花里胡哨的技巧都是在这个基础上变种。
1.2 WAF检测目录遍历的常规规则
大多数WAF对目录遍历的检测分为三层:
- 静态特征匹配,比如正则
(\.\./|\.\.\\)、(\.\.%2f|\.\.%5c); - 解码后匹配,做一次URL解码、HTML实体解码,再匹配穿越特征;
- 语义分析,把路径还原成标准化路径后,再看是否越过了配置的基准目录。
第一层最好绕,多编码一次就没了;第二层要会判断解码次数;第三层就比较麻烦了,但真实WAF里第三层能做好的也不多,很多厂商设备仍然停留在前两层。不是他们不想做,而是做语义分析要牺牲性能、要维护大量应用规则,还要面对各种畸形HTTP报文的兼容性问题,做得越多误拦越多,用户投诉也就越多。
2. 编码绕过实战:从双层URL编码到Unicode陷阱
编码绕过是目录遍历WAF绕过里最基础也最实用的一类。我不是让你死记硬背编码表,而是要把“为什么编码能绕过”这个逻辑搞清楚,然后按场景取用。
WAF匹配的关键词一般是解码后的明文,比如../、..\、..%2f。如果你把一个字符用编码表示,让WAF的解码器没还原出来,而后端又能还原,就达成了绕过。核心在于“多一层编码”。
2.1 双层编码与多层编码的标准打法
当后端框架对参数做了两次URL解码时,你可以构造:
GET /file?name=..%252f..%252fetc/passwd HTTP/1.1WAF如果只解一层,看到的还是%2f或者是没有意义的字符串,直接放行。后端第一次解出..%2f..%2fetc/passwd,第二次解出../../etc/passwd。这里的关键是你要先确认后端到底解码几次。怎么确认?自建一个本地环境,对同一个参数做不同的编码次数,看返回内容差异,或者观察响应时间、错误信息。
实战里我一般先打这三种序列:
- 单层URL编码:
..%2f..%2fetc/passwd - 双层URL编码:
..%252f..%252fetc/passwd - 混合编码:
..%252f..%2fetc/passwd
每种都去观察返回包。如果单层被WAF拦截,双层可能就过了。如果双层还是被拦,那说明WAF至少做了两层解码,你就需要用更复杂的畸形结构,或者转向中间件解析差异。
2.2 Unicode编码和全角字符的绕过场景
Unicode编码是另一个常用场景。/的Unicode全角形式是/(U+FF0F),有些后端在做编码转换的时候会把全角斜杠还原成普通斜杠,而WAF的规则库如果没覆盖这种映射,就能打过去。
构造方式:
GET /file?name=../../etc/passwd HTTP/1.1注意,这个Payload的可靠性完全取决于目标站点的字符编码处理方式。Java的某些版本、老旧的Tomcat连接器配置、部分PHP的mb_convert_encoding场景都出现过类似解析问题。我遇到更多的情况其实是IIS和ASP.NET在处理%c0%af这类过时编码时的历史问题。现在Windows系统基本修复了大部分,但内网老系统里仍然可能碰到。
2.3 编码Payload怎么快速按场景切块
这就说到你关注的“800+ Payload直接用”了。我不建议你把800条Payload全塞进一个文件里盲目爆破,那样除了把自己网络打崩、日志刷爆、误报率飙升之外没有任何好处。我更建议你把Payload分成8个区,称为payload提取分区,每个区对应一种解码行为和一种后端特征:
| 分区 | 编码方式 | 适用后端特征 |
|---|---|---|
| A区 | 单层URL编码 | 几乎所有后端 |
| B区 | 双层URL编码 | Java、Python某些框架 |
| C区 | 三层URL编码 | 极少见,通常用于二次解码场景 |
| D区 | Unicode/全角字符 | IIS、老Java、特殊字符集配置 |
| E区 | 混合编码 | 前几项被WAF拦截后的后备方案 |
| F区 | 反斜杠与正斜杠混用 | Windows路径、Nginx反向代理场景 |
| G区 | 空字节截断 | 老版本PHP、Java |
| H区 | 语义规整类 | Node.js、Next.js等现代框架 |
每个分区里我一般保留100条左右变体,总共800条。但真正跑的时候不是全量跑,而是要结合目标指纹先缩小范围。比如通过响应头判断是Nginx+PHP-FPM,那就主跑A、E区,顺带跑H区的一些绝对路径Payload,其他区先放一放。
3. 路径规约与歧义结构:不靠编码也能绕
很多人以为绕过WAF只能靠编码,其实路径语义规整这个方向才是更高级的玩法。因为即使WAF把请求完整解码,也不一定知道后端到底会把这段字符串“理解”成什么路径。
3.1 绝对路径与点目录:绕过“必须包含..”规则
有些WAF规则认的死理是:只要没有..就不算目录遍历。这时候绝对路径反而好用。
GET /file?name=/etc/passwd HTTP/1.1 GET /file?name=C:/Windows/win.ini HTTP/1.1如果后端直接把参数拼进路径前缀,比如/var/www/files/+$name,你传/etc/passwd可能拼出来是/var/www/files//etc/passwd,Linux文件系统会把连续斜杠压缩成一个,等同于/var/www/files/etc/passwd,读不到passwd。但如果后端对拼接结果做了“去前缀”处理,或者拼接时用的字符串处理函数把前面的路径覆盖掉了,那就可能直接读取成功。
更常见的是配合./来吃掉前缀:
GET /file?name=./../../../../etc/passwd HTTP/1.1有些人疑惑为什么前面要加./,这是因为某些路径拼接库在处理相对路径时,会先把.解析成当前目录,随后..才能正常往上一层走。不带这个点,有些解析器会把首段的..当作非法路径直接拒绝。
3.2 嵌套穿越与路径压缩差异
另一种思路是利用后端路径规范化时对.和..的处理顺序。比如:
GET /file?name=....//....//....//etc/passwd HTTP/1.1某些服务器在规范化路径时会把....//解析成../(把后面的两个点加斜杠视为完整穿越标记,然后把前面多余的点归并掉),而WAF的正则可能只匹配了..//或../的精确形式,漏掉了这种变体。
这类Payload测试时不要贪多,同一个目录深度多套几个不同写法就行,因为真实后端往往是固定层数,你套十几层和套四五层,最终能读到的文件可能一样。但WAF的拦截日志会记录不同深度,跑得太多容易被封IP。
3.3 URL解析器对分号、逗号和参数混淆的处理
RFC 3986允许URL路径中包含分号,有些Web服务器会把;后面的内容当作路径参数,不影响文件路径。利用这一点可以在穿越序列中间插入干扰字符:
GET /file?name=..;/..;/..;/etc/passwd HTTP/1.1 GET /file?name=..%3b/..%3b/..%3b/etc/passwd HTTP/1.1WAF如果把分号后面的内容当作独立参数或者忽略掉了,就看不到完整的../序列。而Java的Servlet容器对路径参数的处理历史上有几次著名的绕过事件,直接导致CVE。虽然现在的新版本修了,但内网里存量系统用老版本的仍然不少。
4. 解码器与网络节点的“理解差”:413和超限请求带来的额外变量
前面讲的所有Payload,其实都建立在一个前提上:你的HTTP请求能被完整送到后端。但是真实攻防里,WAF前面可能还有CDN、反向代理、网关防护,每一个节点都可能因为请求本身异常而直接拒绝,根本不给你测试的机会。
4.1 什么是unexpected status 413 payload too large
这个词条看起来像报错,实际是你在用Burp或者自动化脚本跑Payload时特别常见的一个坑:Payload太长,或者请求头/请求体体积超过了中间件或上游Provider的限制,返回413 Request Entity Too Large。很多同学第一次看到413 payload too large: upstream provider rejected the request就直接懵了,以为是Payload打出了什么神奇效果。
其实这就是Nginx配了client_max_body_size,或者是API网关默认只接受小体积请求。你拿着动辄几十KB的超长Payload列表往一个只允许8KB请求体的接口上灌,自然被拒。
我实测下来,处理这种问题有几个办法:
- 把Payload进行分区,每次只测一个区,而不是全量拼接;
- 压缩请求体积,去掉所有和路径穿越无关的Cookie、冗余Header;
- 把路径穿越参数放到URL参数中,而不是POST body里,很多WAF和代理对URL参数的长度限制比body严格,但也有反过来的情况;
- 如果必须用POST,改用
multipart/form-data包装,有些网关对表单字段的解析和普通body不一样。
4.2 413本身会不会成为WAF绕过的机会?
这个问题有意思。当你发一个超大数据包时,有些WAF为了保证可用性,会启动“放行模式”或“快速失败模式”,不再做深度检测。但是这里我要泼冷水:靠把请求撑爆来绕过WAF的成功率非常低,而且极容易触发防护联动封禁。我更多是把413当成一个信息点,它帮我确认了中间件的版本和限制参数,接下来可以针对性调整Payload长度。
真正跟413有关的一个可操作方向是:当Payload过大被上游拒绝时,你在Burp的历史记录里能看到网关回包里的Server头、Via头、X-Cache头,这些指纹信息能帮你确认中间件类型,然后再决定下一轮分包里的Payload方向。比如确认了是Nginx,就重点测反斜杠混合和/规整问题;确认了是Tomcat,就重点测分号路径参数。
4.3 把Payload拆成小块跑的效率提升法
既然413是体积问题,那就从体积入手。建议你把整个Payload库按前面说的8个分区,每个分区再按“单包长度不超过4KB”的标准切成小组。每个小组内尽量保持同一类绕过思路,不要一会儿编码一会儿分号,否则命中了你都不知道是哪条规则起的作用。
我用自动化验证时,会预先给每个Payload打标签,类似于[enc-double][linux][url-param],跑完直接从报告里按标签汇总命中项,而不是打开几百条请求日志一条条看。这个习惯节省的时间,比你花十分钟写个复杂脚本多得多。
5. 中间件与框架的解析差异:针对特定目标精准绕过
到了这个环节,就不是通用Payload能解决的了。你要根据目标的中间件和框架特征选择对应的绕过方式。下面是我在授权测试中积累的几条比较典型的中间件判断方向,不代表某个产品现在还存在的漏洞,只是帮你理解解析差异。
5.1 Nginx与Apache的路径归一化差异
Nginx在处理URI时,默认会把%2f还原成/,但它对路径中连续斜杠、.和..的处理比较严格。Apache则有自己的MergeSlashes配置,默认会把多个斜杠合并。区别在于:同一个Payload,Nginx可能直接404,Apache可能就穿过去了。
经典的测试结构:
GET /file?name=..%2f..%2f..%2fetc/passwd HTTP/1.1如果Nginx场景下被返回400,但直接换成..%5c..%5c..%5cwindows/win.ini在某个Windows反代后面却正常返回200,说明后端是Windows+IIS类路径处理。这就是用差异本身去探测后端,而不是盲目堆Payload。
5.2 Tomcat与Java系的分号路径参数问题
Java Servlet规范里,分号在路径中用于分隔路径参数,比如/download/;/etc/passwd。有些老版本Tomcat在解析时会忽略分号后的内容,如果你把穿越序列放在分号后面,等于给WAF看了个寂寞:
GET /download?path=..;/etc/passwd HTTP/1.1注意这里的实际利用效果取决于具体接口怎么拼接路径,我不建议把它当通用武器,更像是一个在遇到Java应用时值得优先验证的探针。
5.3 Next.js这类现代框架的路径约束与绕过
这就要回应搜索热词里的“next.js payload教程”。其实Next.js本身对路径参数有比较严格的约束,尤其是App Router模式下,动态路由段不允许包含/和\。所以在Next.js应用里打目录遍历,更多是找历史遗留的pages/api接口、rewrites重写规则错误、或者服务端拿query参数直接拼fs.readFile的写法。
我之前在一个授权项目里遇到一个Next.js站点,它的/api/export接收filename参数,并且文档里只允许传入预设的模板名。但服务端代码直接做了字符串拼接:
const filePath = path.join(__dirname, '../templates/', fileName);看起来path.join已经做了路径规范化,似乎没问题。但如果文件名传一个../../secret.txt,path.join会返回/app/secret.txt。WAF如果只拦截/etc/passwd这类关键词,不拦截../穿越,那这个接口就是目录遍历。所以现代框架的绕过重点通常不在框架本身,而在于业务代码的拼接方式。
5.4 CDN节点和源站解析不一致的链路绕过
这个思路在真实场景里特别好用。假设你的目标站点使用了CDN,CDN节点做一层WAF检测,源站是另一个配置。CDN的URL规范化标准和源站不一定一致,比如CDN会把//规整成/,源站不一定;CDN把%2e%2e解码成..,源站也解码,但顺序不同。
如果手上正好有源站的IP或者某个不经过CDN的域名,那就直接在源站环境测绕过,绕过了再想办法让流量走源站。如果没有源站入口,也可以尝试用Host头混淆、用不同端口、或者直接对CDN节点本身的解析逻辑做黑盒探测。
这里要特别提示一下:凡是涉及多个网络的测试,务必先确认目标属于你本人或者已获得书面授权的系统。我文章里提到的所有方法,都不应该用在未授权的系统上,这是底线。
6. 构建可复用的Payload库:从CTF靶场到真实场景的落地
很多人在CTFHub这类靶场里做目录遍历做得很顺手,各种Payload都验证通过了,结果一到真实项目就拉胯。原因很简单:靶场的后端逻辑是固定的,WAF要么没有,要么是模拟的,而真实项目的解析链路五花八门。所以我才反复强调Payload库要有分区、有标签、有场景映射。
6.1 CTFHub里的目录遍历训练能给你什么
CTFHub的目录遍历关卡的目的是让你理解不同参数位置的注入形式,比如直接在文件路径参数上做穿越、在包含文件路径的Cookie字段上做穿越、在跳转参数上做穿越。它的价值不在Payload本身,而在于让你形成“先看参数语义,再决定往哪儿插入穿越序列”的思维习惯。
我建议你在靶场里做三件事:
- 把每个关卡的标准Payload记录下来,标注关卡所用的后端类型;
- 用同样的Payload去绕靶场自带的模拟WAF,观察哪些编码被拦截、哪些能过;
- 把测试结果按“后端类型 / 参数位置 / 编码方式 / 是否绕过”四个维度整理成表格。
这份表格往后就是你真实项目里最基础的查询字典。CTFHub覆盖不了所有场景,但至少能帮你把思路理顺。
6.2 如何让800+ Payload真正“适配所有场景”
我想先纠正一个观念:没有任何一份固定列表能适配所有场景。所谓“适配所有场景”,正确的做法是库里有足够的模块,而你能根据目标指纹快速组合出对应模块。我的库里大概有8个分区、20多个变体组,每组算上不同深度组合,超过800条很轻松。
举个例子,一组典型的结构化变体:
| Payload关键字 | 变体数 | 常见后端 |
|---|---|---|
../常规穿越 | 50 | 所有 |
..%2f单层编码 | 80 | 所有 |
..%252f二次编码 | 60 | Java |
..%c0%af畸形UTF-8 | 30 | IIS |
..\反斜杠 | 60 | Windows/IIS/部分Java |
....//点号混淆 | 90 | Nginx |
..%3b分号混淆 | 50 | Tomcat/Java |
/absolute绝对路径 | 40 | 特定拼接场景 |
| 混合变体 | 340 | 按指纹组合 |
| 合计 | 800 | 全场景 |
你不需要一次性跑完,而是先做指纹定位。指纹定位就三件事:
- 看响应头里Server、X-Powered-By、Set-Cookie的特征;
- 看报错页面的框架标记;
- 看URL上下文的参数命名和接口风格。
然后从对应分区里挑出50到100条先跑一轮,命中一条,马上顺着这条的结构做相近变体的深度扩展。这才是效率最高的方式。
6.3 自动化验证中的注意事项
用Burp Intruder、ffuf或者自写脚本批量验证时,我有几个亲测有用的建议:
- 把线程调低,尤其是目标站有明显WAF时,高并发只会换来封禁;
- 对每个请求设置独立的随机参数名,避免WAF对同一个参数名的连续异常请求做聚合封禁;
- 观察返回包时不要只看状态码,要同时看响应体长度和关键词,如果目标接口是API返回JSON,路径穿越成功往往表现为
code变化、message里泄露文件内容前缀; - 遇到所有请求都是413或其他统一状态码时,先停下来修正请求体积,而不是继续跑。
我自己的习惯是先用三个最基础的Payload确认接口确实存在目录遍历,再切到自动化批量跑。如果三个基础Payload全部被拦截,就先回去做WAF指纹识别和编码探测,而不是直接上800条全量。这一步能帮你省下大量时间,也减少对目标系统的无谓打扰。
7. 针对WAF规则演进的反制思路:如何维持绕过能力
写到这里,我再补一个很容易被人忽略但非常关键的维度:WAF规则是动态更新的。你今天验证过能打的Payload,可能过一个月就被规则覆盖了。所以长期做这块的人,不应该只靠存量Payload库,而是要建立自己的“绕过能力生成机制”。
7.1 从绕过记录里提炼新的变体规则
每一次测试结束,我会把命中的Payload和没命中的Payload都归档,并对命中Payload做一个逆向分析:它到底是破坏了WAF的哪个检测环节?是解码顺序、长度限制、正则盲区、还是协议解析差异?把这条原因抽象成一个规则模板,比如单参数二次URL解码且WAF仅做一次解码,下次遇到类似环境,就能快速套用。
我强烈建议你用表格记录,而不是散落在笔记里:
| 日期 | 目标类型 | WAF类型 | 命中Payload | 绕过原因 | 适用组件 |
|---|---|---|---|---|---|
| 日期 | Nginx+PHP | 某云WAF | ..%252f | WAF仅解一层,后端解两层 | 所有 |
| 日期 | Tomcat | 某硬件WAF | ..;/etc/passwd | Java分号路径参数未检测 | Java系 |
| 日期 | IIS | 某CDN | %c0%af | 畸形UTF-8解码差异 | IIS |
这个表的价值会随着时间积累越来越大,三个月后你就拥有一份自己实测过的、有针对性的绕过手册,而不是网上随便扒来的大杂烩。
7.2 源站与WAF规则的赛跑:持续追踪中间件补丁
WAF绕过另一个必须关注的层面是上游中间件的更新动态。每当Tomcat、Spring、Nginx、IIS发布安全公告,我都会第一时间去读CVE描述里的路径处理细节,因为这些漏洞描述里往往藏着解析器行为变化的蛛丝马迹。你不需要成为中间件源码专家,只需要能看懂“某个版本对路径中的分号不再忽略”这类信息,就能反过来更新自己的Payload适用表。
7.3 兜底手段:绕过不了就换攻击面
最后说句实在话:不是所有目录遍历都能绕过WAF。有些WAF把语义分析做得很到位,解码次数多、路径规整能力强,你再怎么折腾编码和混淆都没用。这时候与其死磕,不如换个攻击面。旁站、接口文档泄露、源码仓库泄露、备份文件扫描,任何一个方向都可能比你在目录遍历上死磕有价值。攻防不是单点突破的游戏,是路径规划的游戏。
我在实际项目里最大的体会就是:Payload只是弹药,对目标解析链路的理解才是真正决定成败的东西。希望这篇内容能帮你把目录遍历和WAF绕过这件事从“背Payload”提升到“理解绕过机制”的层面。下次再有人给你丢一个“800+ Payload通杀”的压缩包,先别急着跑,按我上面说的分区和指纹探测方法走一遍,你会发现能用的东西其实比想象中多得多。