Web应用中的路径遍历(Path Traversal)漏洞详解
一、漏洞描述
路径遍历,也称为目录遍历,是一种攻击者通过操纵输入参数(通常是文件路径相关参数)来访问应用程序预期目录之外的文件或目录的安全漏洞。攻击者通过使用包含../(或类似序列)的特殊字符组合,能够突破应用程序设定的根目录限制,从而读取、写入或执行目标服务器文件系统上的任意文件。例如,攻击者可能通过构造类似../../../../etc/passwd的路径,访问敏感系统文件。
二、漏洞原理与成因
- 核心原因:应用程序在处理用户提供的文件路径参数时,未进行充分的验证、过滤或规范化。例如,一个提供文件下载功能的Web应用,其URL可能为
https://example.com/download?file=report.pdf,后端代码可能会直接使用用户输入的file参数值来拼接出文件系统上的完整路径,如/var/www/app/uploads/report.pdf。 - 攻击向量:攻击者可以修改
file参数值为../../../etc/passwd。如果后端程序简单地将其拼接为/var/www/app/uploads/../../../etc/passwd,经过操作系统路径解析后,就变成了/etc/passwd,从而成功访问到系统密码文件。 - 输入点:此漏洞不仅存在于查询参数(GET),也常见于POST请求参数、Cookie、HTTP头(如
X-Forwarded-For、User-Agent在某些特殊上下文中也可能被当作路径处理)以及上传文件时的文件名等位置。
三、漏洞利用步骤(循序渐进)
假设存在一个简单的文件下载服务,后端使用PHP编写,代码如下:
$file = $_GET['file'];
$filepath = '/var/www/html/uploads/' . $file;
readfile($filepath);
步骤1:基础探测
攻击者首先尝试访问正常文件,确认功能:https://example.com/download.php?file=user_guide.pdf
服务器会返回/var/www/html/uploads/user_guide.pdf文件的内容。
步骤2:尝试路径回溯
攻击者尝试跳出uploads目录,访问上一级目录的文件:https://example.com/download.php?file=../config.php
此时,后端构造的路径为/var/www/html/uploads/../config.php,解析后等价于/var/www/html/config.php。如果该文件存在且可读,其源码(可能包含数据库凭证)就会被泄露。
步骤3:多级回溯以访问系统文件
为了访问Web根目录之外的文件,攻击者需要更多的../。例如,尝试访问Linux系统的密码文件:https://example.com/download.php?file=../../../../etc/passwd
构造的路径为/var/www/html/uploads/../../../../etc/passwd。解析过程如下(假设Web根目录/var/www/html):
/var/www/html/uploads/../../-> 先回到uploads的上一级html,再回到html的上一级www,结果是/var/www- 再接上
../../etc/passwd-> 从/var/www再上两级到根目录/,然后进入etc,最终解析为/etc/passwd。
步骤4:绕过可能的过滤机制
- 过滤
../:如果程序简单地删除../,攻击者可能使用双重编码或嵌套序列。例如,..%2f是../的URL编码。如果程序在解码前过滤,攻击者可以尝试编码%2e%2e%2f(每个字符的编码),或者使用....//(过滤一次../后变成../)。 - 绝对路径:有时程序未限制相对路径,直接传入绝对路径也可能成功,如
file=/etc/passwd。 - 空字节注入(在旧版PHP等语言中):如果程序在动态语言(如PHP)中,使用字符串拼接并在之后调用类似
include的函数,攻击者可能在路径后添加空字节(%00)来截断后续程序添加的后缀。例如file=../../../etc/passwd%00,当代码是include($user_input . '.php');时,空字节会使系统在读取到.php之前就结束路径。
步骤5:写入文件(如果存在写操作)
如果应用还允许文件上传或修改,并且存在路径遍历,攻击者可能尝试将文件写入到非预期位置,例如写入Web Shell到Web目录:file=../../../var/www/html/shell.php(具体路径需探测)。
四、漏洞防御措施
- 输入白名单验证:最有效的方法。定义一个允许的文件名或标识符的白名单(例如,只有
guide.pdf,intro.txt等),只允许访问这些预定义的文件。避免直接使用用户输入作为文件系统路径的一部分。 - 路径规范化与验证:如果无法使用白名单,则必须对用户输入进行处理:
- 规范化路径:使用编程语言提供的函数(如Python的
os.path.normpath,Java的Path.normalize)来解析路径中的.和..,然后检查规范化后的路径是否仍在预期的基础目录内。 - 绝对路径检查:将用户输入与预设的安全基础目录(如
/var/www/app/uploads/)进行拼接,然后检查拼接后的完整路径是否以这个基础目录开头。例如:if (!fullPath.startsWith('/var/www/app/uploads/')) { throw error; }。
- 规范化路径:使用编程语言提供的函数(如Python的
- 使用文件ID或索引:不直接传递文件名,而是传递一个数据库ID,后端通过ID查询数据库获取对应的安全存储路径。
- 移除特殊字符:在验证逻辑中,应拒绝包含路径遍历序列(如
../,..\(Windows))或空字节的输入。注意,要在URL解码后进行过滤,并考虑各种编码和变体。 - 最小权限原则:运行Web服务器的操作系统用户(如
www-data,nobody)应仅具有对应用程序文件和必要目录的最小必要读取权限,绝不应有对关键系统文件(如/etc/shadow)的读取权限或对Web根目录之外的写权限。 - 安全框架函数:使用安全的API。例如,在Java中使用
Path接口及其resolve、normalize和startsWith方法;在Python中使用os.path.join(base, filename)并配合检查。
总结:路径遍历漏洞的核心在于对用户提供的路径参数信任过度。防御的关键在于永远不信任用户输入,并确保最终访问的文件路径被严格限定在应用程序预先定义的安全目录范围内。通过结合白名单、路径规范化、基础目录校验和最小权限原则,可以有效地防御此类攻击。