☰
目录遍历漏洞详解:从路径穿越原理到检测修复实战
2026/9/28 12:48:50 网站建设 项目流程

有一次做授权测试,目标是一个内部管理系统的文件下载功能。下载接口长这样:/download?file=report_2024.pdf,我顺手把file参数改成../../../etc/passwd,结果浏览器直接返回了 passwd 文件内容。那一次测试,前后没超过三分钟。这就是目录遍历漏洞,也叫 Path Traversal、任意文件读取或路径穿越。它常年挂在 OWASP Top 10 里,多数时候归在越权访问或安全配置不当一类。很多人觉得它“简单到不值一提”,但实际渗透中,它往往是撬开内网的第一根杠杆。这篇文不打算堆高大上的理论,就讲讲它的成因、绕过思路、怎么测、怎么修,以及我在实际项目里踩过的那些坑。适合刚入门的安全新手,也适合写代码写到一半被逼着自查漏洞的开发同学,运维同事务必看看修复部分。

1. 目录遍历漏洞的成因与本质

1.1 从“路径拼接”说起

先看一个典型的 PHP 老代码:

<?php $file = $_GET['file']; $path = '/var/www/downloads/' . $file; header('Content-Type: application/octet-stream'); readfile($path); ?>

这段代码的问题一眼就能看出来:$file完全由用户控制,程序拿它和基础目录拼接出一个完整路径,然后直接 readfile。当你传../../../../etc/passwd,实际拼出来的是/var/www/downloads/../../../../etc/passwd,系统解析路径时会一路向上跳,最终落在/etc/passwd上,文件内容就出来了。

这类问题的本质是信任了不该信任的输入。开发者的本意是“只能访问下载目录里的文件”,但没有在代码层面落实这个限制。操作系统本身并不觉得这有什么问题——/var/www/downloads/../../../etc/passwd就是一个合法的绝对路径,系统会正常执行。换句话说,漏洞不是你传了一个带..的字符串造成的,而是程序对传入字符串缺少约束校验造成的。

在 OWASP Top 10 里,目录遍历通常被归到 A01:2021-Broken Access Control(越权访问)或者 A05:2021-Security Misconfiguration(安全配置不当)。我更倾向于把它理解为一种“访问控制失效”:应用需要限制用户能访问的资源范围,但没有做到。

1.2 为什么黑名单过滤总是“防不胜防”

很多开发同学第一反应是:把..过滤掉不就行了?理论上可以,但实践里几乎每次都翻车,原因在于过滤规则永远追不上解析差异。

同一段../,在不同场景下可以打扮成各种形态:

原始形式说明
../../etc/passwd经典 Unix 路径穿越
..\..\windows\win.iniWindows 路径穿越,反斜杠
%2e%2e%2fetc/passwdURL 编码,%2e是点,%2f是斜杠
%252e%252e%252fetc/passwd双重编码,服务器和框架各解一次码
....//etc/passwd部分正则只过滤../,....//中间被系统解析成../
/etc/passwd直接传绝对路径,不看基础目录
..%2f..%2f..%2fetc/passwd斜杠编码,点不编码
%c0%ae%c0%ae/早期 IIS 的 Unicode 编码绕过,现在已经很少见

这里的关键点在于“解码次数”。Web 请求从客户端到服务器,再到应用框架,每一层都可能做一次 URL 解码。如果开发者只在自己这一层过滤一次../,那双重编码的 payload 就能穿透。更麻烦的是,过滤../不一定能挡住..\,Windows 服务器上反斜杠也是合法路径分隔符。有些正则考虑到了反斜杠,但漏了 URL 编码后的斜杠。

我做过一个比喻:这就像小区门口只设了一道门禁,保安只认一种工牌,但攻击者手里的工牌有无数种材质、颜色、印刷方式。黑名单的宿命就是如此——你列出的永远是一份有限清单,而攻击者的变形方式几乎是无限的。所以真正靠谱的修复方案从来不是“过滤”,而是“验证”,后面第四节会详细说。

2. 目录遍历漏洞能造成多大影响

2.1 从 /etc/passwd 到数据库配置

目录遍历最直接的影响就是任意文件读取。攻击者可以顺着路径一路往上跳,读取服务器上的敏感文件。按我在项目里的经验,下面这些文件优先级最高:

  • /etc/passwd:确认系统用户和路径结构,存在性最好的探测目标。
  • /etc/shadow:一般读取不到(权限受限),但值得一试。
  • /var/www/html/config.php、.env、application.yml、web.config:数据库账号、API 密钥、密钥串全在里面。
  • 应用源码文件:拿到源码后做白盒审计,往往能发现第二个漏洞。
  • /root/.ssh/id_rsa:如果应用运行权限足够高,私钥直接泄露。
  • 日志文件:Nginx/Apache 访问日志、应用日志,有时包含管理员会话或者 SQL 语句。

举一个实际场景。有一次测试电商系统,前端有一个导出订单的接口,参数是?export=order_20240101.csv。我把参数改成../../../../var/www/html/config.php,返回的是 PHP 源码。在源码里发现了数据库地址、账号密码,还是 root 权限。虽然服务器数据库没对公网开放,但当时的内网里,这台数据库是很多系统的共用的。顺着这份配置,后面又打了两台机器。一个目录遍历,生生变成了内网横向的起点。

单纯“读文件”已经够严重,但更怕的是这个漏洞出现在一些特殊接口上。比如文件下载接口存在路径穿越,同时服务器上又有用户上传的文件,那攻击者可以把/etc/crontab读走;再比如读到了备份文件/var/backups/backup.tar.gz,里面可能打包了整个站点的源码和数据库,那就不是“读一个文件”了,是“端走一整个系统”。

2.2 不止于“读文件”

目录遍历常常不是孤立存在的,它的杀伤力经常通过组合其他漏洞体现。

第一类组合是文件上传 + 路径穿越。现在的上传功能普遍限制后缀名,比如只允许 jpg、png,脚本文件传不上去。但如果上传的文件名可控,而且服务器是 Apache 或者 Nginx,攻击者可以构造shell.php%00.jpg之类的手法(虽然空字节截断在现在的主流环境不好用了),更常见的是利用路径穿越覆盖目录:上传接口的保存路径写成uploads/,加一个../../shell.php的文件名,如果后端没有对文件名做过滤,文件会被写到网站根目录下,配合 Apache 多后缀解析,就有机会执行。热词里提到的“后端正则限制了很多后缀,但是服务器是 Apache2”,说的就是这类场景。

第二类组合是文件包含 + 目录遍历。本地文件包含(LFI)很多情况下就是目录遍历的升级版——前者只是把文件读出来,后者把文件当成代码执行。如果应用里有include($_GET['page'])这类写法,配合路径穿越可以直接包含/etc/passwd或者日志文件,讲究一点还能通过包含日志写入 PHP 代码,完成 RCE。

第三类组合是框架/中间件自身的历史 CVE。老 PHP 框架出现过路径处理缺陷,Java 中间件也有过路径穿越类型的漏洞。这类问题本质上还是同一个根因:用户输入没有经过充分校验就拼进了路径操作。2024 年曝出的某几个 PHP 框架文件上传类漏洞,分析到最后还是绕过了路径过滤逻辑。

所以目录遍历的 CVSS 评分往往在中高危区间,但实际利用里,它能串起的链路远比自己单独的评分更高。碰到这种漏洞,不要写一句“可读取任意文件,建议修复”就完事,一定要顺着文件内容继续往下想:读到了什么?还能做什么?这是漏洞报告中真正有价值的部分。

3. 如何系统性地检测目录遍历漏洞

3.1 手工测试的“套路”

手工测目录遍历,核心是“找参数、改参数、看响应”三步,但每一步都有细节。

先找参数。目录遍历漏洞一般出现在带路径的 GET 参数上,常见参数名有:file、path、filename、download、img、url、page、route、template、doc。凡是后端可能拿这个参数拼文件路径的,都值得测。POST 请求体的参数同样要测,尤其是一些 JSON 接口,字段名如filePath、fileName都可能成为突破口。热点里提到的“php+cookies漏洞”,意思是不要忽略 Cookie 里的路径类字段,有些应用会把用户身份、语言、皮肤主题存在 Cookie 里,取值后拼路径读取资源,同样可能存在穿越。

拿到参数后,先请求一个正常的文件,比如file=readme.txt,观察正常响应是什么样:状态码、Content-Type、响应体大小、缓存头,记录下来当基线。然后再用穿越 payload 替换,比如file=../../../etc/passwd。怎么判断有没有穿越成功:

  • 响应体里出现root:x:0:0:之类的 passwd 内容,铁定中招。
  • 响应状态码从 200 变成 403 或 500,说明文件存在但权限不够,或者路径被拼错了,需要调层级。
  • 响应体是 PHP 报错信息,显示了路径拼接后的完整路径,这本身也是信息泄露,能帮你调整穿越层数。
  • 响应时间异常,比如某些日志文件很大,读取慢,也是信号。

层级数量是个常见问题。服务器上站点根目录通常是/var/www/html,Web 应用项目目录可能再深几层,比如/var/www/html/application/controllers。穿到系统根目录../../../../etc/passwd一般是四个层级,但不确定时就多试几组:../加 3 到 8 层,或者直接传绝对路径/etc/passwd。很多应用在拼接路径时根本不关心前缀是什么,绝对路径往往一击即中。

手工测试时我习惯先测 3 到 5 个关键参数,每个参数跑一组常见 payload,而不是对着单个参数无穷无尽地试。效率和覆盖率之间要平衡。

3.2 借助工具提升效率

手工测试能确认漏洞,但覆盖不了全站所有参数。这时候就得靠工具批量跑。

我常用的几款:

  • Burp Suite Intruder:抓到一个带路径参数的请求后,把参数值设成 payload 位置,加载一个路径穿越字典,跑一遍就能看到哪些 payload 返回了非正常响应。Burp 的响应对比功能很好用,能自动标记出长度或者状态码不同的响应。
  • dirsearch:虽然主要用于目录扫描,但它的字典里包含一些常见穿越路径,也可以扫/download/这类接口,发现隐藏文件。
  • ffuf:灵活度最高,可以把file参数设为变量,配合-w指定字典,开多线程快速跑。命令大概是:
ffuf -u http://target.com/download.php?file=FUZZ -w lfi.txt -mc 200 -fs 12345

-mc 200只看 200 响应,-fs 12345过滤掉正常响应长度,减少噪音。

字典方面,比较经典的是SecLists里的LFI-Jhaddix.txt,覆盖了../、编码形式、绝对路径、Windows 风格等,找目录遍历够用。工具能用是一回事,别忘了一条铁律:只在授权目标上扫描。未经授权跑扫描器,不管出于什么目的,都可能给自己惹麻烦。练习环境推荐 Pikachu、DVWA 这类自带靶场的平台,CTFHub 上也有一整个目录遍历专题,可以随便练手。

3.3 自动化扫描与 AI 挖洞的一点看法

最近“AI 自动化挖漏洞”的话题很热,但是对目录遍历这类漏洞,AI 和自动化脚本更多的价值在于“发现可疑点”,而不是“直接确认漏洞”。市面上很多扫描器会报出一堆“疑似路径穿越”,点进去一看全是误报——可能的场景包括:参数值里确实带../,但那是合法的资源路径;或者响应里包含root:字样,但其实只是在渲染一个示例文本。真正的确认环节,还得靠人来看响应内容、看应用上下文。

所以我的态度是:可以用自动化工具做全站摸排,但确认漏洞和评估影响永远要人工复核。AI 生成一个“漏洞报告”很容易,但报告里的复现步骤和影响分析是否真实可落地,才是决定它有没有价值的关键。对新手来说,与其迷信“AI 挖漏洞赚钱”,不如先把目录遍历这种经典漏洞的原理和判断方法搞清楚——这恰恰是 AI 做不好的部分。

4. 修复与加固:从代码到基础设施

4.1 代码层:白名单与规范化

修复目录遍历,正确的姿势是“白名单校验 + 路径规范化检查”,而不是“过滤黑名单”。

先说最省心的方案:如果业务场景是下载文件,而且文件名就那么固定几种,直接用白名单。比如:

<?php $allowed = ['report_2024.pdf', 'report_2025.pdf']; if (!in_array($_GET['file'], $allowed)) { http_response_code(403); exit; } readfile('/var/www/downloads/' . $_GET['file']); ?>

白名单意味着攻击者只能选你允许的东西,路径穿越无从谈起。但这种方案局限性大,很多业务确实需要动态传路径。那就得规范化校验。

PHP 的正确做法是realpath()之后再比对前缀:

<?php $base = '/var/www/downloads/'; $input = $_GET['file']; $fullPath = realpath($base . $input); if ($fullPath === false || strpos($fullPath, $base) !== 0) { http_response_code(403); exit; } readfile($fullPath); ?>

realpath()的作用是把..、符号链接全部解析成最终的绝对路径。比如realpath('/var/www/downloads/../../../etc/passwd')返回/etc/passwd,然后strpos()检查发现它不以/var/www/downloads/开头,直接拒绝。注意realpath()对不存在的文件会返回false,所以如果业务里文件可能不存在,得先处理好这个分支。

Java 里对应的方案是Path.normalize()和startsWith():

Path base = Paths.get("/var/www/downloads").toRealPath(); Path target = base.resolve(request.getParameter("file")).normalize(); if (!target.startsWith(base)) { response.sendError(403); return; }

Python 的处理逻辑类似:

from pathlib import Path base = Path("/var/www/downloads").resolve() target = (base / user_input).resolve() if base not in target.parents and target != base: raise PermissionError

这里有个容易被忽略的坑:resolve()和realpath()都会解析符号链接。如果下载目录里有软链接指向系统目录,校验也可能被绕过。排查时记得检查下载目录里有没有可疑的软链接。

4.2 服务器与中间件加固

代码修完了,服务器层也不能裸奔,尤其是文件上传目录这种高风险位置。

第一件事:上传目录禁用脚本执行权限。比如上传目录是/var/www/html/uploads,Nginx 就指定这个目录不解析 PHP:

location /uploads/ { location ~ \.php$ { deny all; } }

Apache 的话用<Directory>块配合php_admin_flag关闭执行权限,或者用.htaccess写php_flag engine off。这样即使上传了脚本,也执行不了。

第二件事:应用运行账户的最小化权限。大部分 Web 服务跑在www-data或nginx用户下,系统关键文件(如/etc/shadow、应用配置文件)的读取权限要收掉。很多目录遍历漏洞能读到/etc/shadow,不是因为穿越多厉害,而是服务账户权限太大。

第三件事:Nginx 层面对请求 URI 做一些基础拦截。虽然不能根治,但能挡掉一批无脑扫描:

if ($request_uri ~* "\.\.") { return 403; }

注意这种正则拦截只是缓解手段,不能当成修复方案。攻击者通过编码绕过就能打穿正则,它是防线之一,不是最后一道。

部署层面,有条件就上容器或者 chroot 隔离,把应用锁在固定目录里。即使某个应用被穿出 Web 根目录,也穿不出容器。这个思路在微服务架构下尤其现实,每个服务一个容器,攻击面天然缩小。

4.3 漏扫与 SDL 流程

代码修复和服务器加固是一次性的“止血”,但长期来看,把目录遍历这类漏洞挡在发布之前才是治本。我建议在开发流程里加两道检查:

第一道是静态代码扫描。Semgrep、CodeQL 这类工具能直接扫出“用户输入拼接到文件路径”的可疑模式。比如 Semgrep 有一条规则专门匹配$_GET或request.getParameter直接进readfile、File、open这类调用。开发者在本地就能跑,提交代码前扫一遍,成本极低。

第二道是动态测试。每次发版前用漏扫工具对测试环境跑一遍全站扫描,重点关注文件下载、文件上传、图片预览这几个入口。Xray、AWVS、OpenVAS 都能做基础检测,工具搜出来的“疑似路径穿越”,再人工复核一轮,基本能拦住。

另外,漏洞修复报告这件事值得多说两句。行业内很多漏洞报告写得非常敷衍:“存在目录遍历漏洞,建议修复”,复现步骤缺失,影响范围含糊,开发根本没法下手。一份合格的修复报告至少要有四块内容:漏洞描述与危害,完整的复现步骤(包括请求包和响应包),影响范围(哪个接口、哪些参数、可能读取哪些文件),修复方案(代码示例或配置修改)。把报告写到这个程度,开发和安全的沟通成本能降低一半——这是我真实项目里的体会,不是套话。

5. 常见问题与排查心得

5.1 典型问题排查表

把我在项目中遇到过的、以及同行问得最多的几个问题整理成一张表,按图索骥排查会快很多:

问题现象可能原因排查思路
过滤了../还是被绕过黑名单不完整,编码/反斜杠/绝对路径被漏掉检查过滤逻辑是否覆盖编码形式;改用白名单 + realpath 方案
Linux 能读,Windows 读不了路径分隔符不同,payload 用了/Windows 用..\..\..\windows\win.ini测试
直接传/etc/passwd没反应应用拼接路径时强制加了前缀,绝对路径被覆盖用../逐层跳,或观察报错信息里拼出来的完整路径
realpath()返回 false目标文件不存在,或 PHP 权限不足先确认文件存在,再确认运行账户对目标目录有访问权限
响应一直是 403有 WAF 或中间件拦截了../换编码 payload 测试;WAF 拦截不代表代码安全,需看代码层修复
扫出来一堆“疑似”,人工复核全是误报工具字典过宽,响应内容匹配不严谨人工看响应体,确认是否真的包含目标文件内容和业务特征

目录遍历还有一个非常常见的混淆点:目录遍历(Path Traversal)和目录列表(Directory Listing)是两回事。目录列表是访问一个目录时,服务器自动展示文件列表,属于 Apache/Nginx 的 Indexes 配置问题,不存在越权;目录遍历是通过路径控制读取任意文件,是访问控制缺陷。两者修复方式完全不同,写报告时千万不要混用。

5.2 容易被忽略的几个细节

第一,不要只盯着 GET 参数。目录遍历可以出现在 POST 参数、Cookie、甚至 HTTP 头的自定义字段里。我就碰过一次:某个系统的语言切换功能把lang存在 Cookie 里,后端拿这个值拼模板路径,Cookie 里塞一个../../../../etc/passwd,模板渲染报错就把文件内容带出来了。所以测试时要把请求的每一个位置都当路径入口来看。

第二,不同软件的解码次数决定了绕过方式。Apache 默认会对 URL 做一次解码,PHP 再解一次,如果应用开发时又手动解了一次码,那%252e%252e%252f就变成了../。反过来,如果中间只有一层解码,双重编码的 payload 反而会被当成普通文件名。测试时要基于具体技术栈选择 payload,不能一套模板打天下。

第三,测试目录遍历要在授权范围里做,并且留意日志。路径穿越请求会非常显眼地出现在访问日志里,一串../想藏都藏不住。某些业务系统还有入侵检测,看到异常路径直接封 IP。我见过有人在生产环境下乱打 payload 导致被封机房 IP 的案例。靶场随便测,生产环境先写好范围再动手。

5.3 修复优先级与个人体会

如果让我给一个修复优先级,我会这么排:

  1. 立刻修代码层:所有文件操作入口改成白名单或 realpath 校验,这个是根治。
  2. 尽快做服务器加固:上传目录禁脚本执行,应用账户收权限。
  3. 短期防护:WAF 加临时规则,拦截../、编码穿越、绝对路径等特征。
  4. 长期治理:静态扫描接入 CI,漏扫纳入发版流程。

最后说一点个人体会。目录遍历这类漏洞之所以常年存在,不是因为技术多复杂,而是因为开发者太习惯“把用户输入当成字符串拼接进路径”,这是编程时最自然的写法,也是最危险的写法。我参与修复过好几个类似漏洞,最深的感受是:与其跟攻击者玩“谁过滤规则更全”的游戏,不如直接改变代码逻辑,从根上堵住路径拼接这条路径。哪怕只是把拼接路径改成“先拼接再 validate”,效果也比加十条黑名单强得多。这个思路不仅适用于目录遍历,也适用于文件上传、文件包含,甚至 SQL 注入——安全问题的根源往往不是某个字符,而是对输入的不信任。你要是想验证自己的系统有没有这个问题,也甭整花活,就拿一个../字符串,意识一下自己代码里有没有“直接拼接”的场景,答案基本就出来了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询