路径遍历(Path traversal)又称目录遍历(Directory traversal),允许攻击者通过应用程序读取或写入服务器上的任意文件,例如读取应用程序源代码和数据、凭证和操作系统文件,或写入应用程序所访问或控制的文件,改变应用程序的行为,甚至接管服务器。
原理
假设一个购物应用程序用下面的标签展示一个图片文件的内容:
<img src="/loadImage?filename=218.png">
通过 filename 参数指定读取 218.png 文件的内容,218.png 是一个文件的名称,既然是文件,那么就必然有一个目录,该文件所在的目录也许在代码中被定义为 /var/www/images/,那么实际读取就是 /var/www/images/218.png 文件的内容。
然而,fiilename 参数是用户可控的,如果这个参数没有做任何路径遍历的防御,那么攻击者可以将其篡改为 ../../../etc/passwd,此时读取的文件是 /var/www/images/../../../etc/passwd,即 /etc/passwd,这是包括一个 Linux 系统所有用户的文件。如此,攻击者便知道了该 Linux 服务器存在哪些用户。
不仅限于 Linux, Windows 可用 ../ 或 ..\ 来实现路径遍历,例如:
https://insecure-website.com/loadImage?filename=..\..\..\windows\win.ini
实验
实验说明:
其中说到产品图片展示存在一个路径遍历漏洞,利用它读取 /etc/passwd 就能完成实验。
进入实验场景:
随便点进一个产品,F12 查看网页源代码,发现图片展示的内容是通过操纵一个 filename 参数来实现:
把这个值复制出来访问,并修改 filename 参数值为 ../../../../etc/passwd 就能读取到 /etc/passwd 文件的内容:
常见的路径遍历防御和绕过
根目录开头
应用程序过滤 ../ 或 ..\,并且如果 filename 是相对路径,那么就在前面追加一个目录路径,如果是绝对路径,那么就不追加。此时,可尝试篡改 filename 为 /etc/passwd,也就是直接以根目录 / 开头,或许应用程序就不再在 filename 前面追加已定义好的工作目录,而是直接读取 /etc/passwd。
嵌套路径遍历序列
应用程序可能只过滤一次 ../ 或 ..\ 序列,那么可尝试类似于 ....// 或 ....\/ 的嵌套序列。
在测试的过程中,可以输入一次 ../ 加文件名看看是否读取到不加 ../ 同样的内容,比如 /image?filename=../30.jpg:
读取到的文件内容与 /image?filename=30.jpg 一模一样,那说明应用程序存在对 ../ 的过滤。此时,尝试嵌套 ../ 序列,比如 /image?filename=....//....//....//etc/passwd:
经过 URL 编码的路径遍历序列
有时候,应用程序会过滤 ../ 或 ..\,那么可以尝试先用 URL 编码处理一次或两次,比如 ../ 一次 URL 编码后是 %2e%2e%2f,两次编码是 %252e%252e%252f。
burpsuite 提供了两个路径遍历的 FUZZ 字典:Fuzzing - path traversal 和 Fuzzing - path traversal(single )
每个路径都有一个 {file} 占位符,可以利用下面的 Payload processing 功能把它替换为空或者想访问的文件,比如 etc/passwd。另外,因为这两个字典的路径已经包含经过 URL 编码的,所以还要取消 burpsuite 对 payload 的 URL 编码默认处理:
下图是字典攻击的效果:
字典中每个路径的 {file} 都被替换成 etc/passwd,有双重编码 / 的,也有不经过编码处理的。利用这两个字典 FUZZ 路径遍历漏洞可以囊括大部分的 payload 了。
基础目录路径匹配
有些应用程序要求用户提供的参数的格式是“目录路径+文件名”,而目录路径必须是符合要求,比如必须是 /var/www/images,这种防御仍然可以用 ../ 或 ..\ 序列绕过。如图:
filename 的值是目录路径 /var/www/images/ 加文件名 20.jpg,如果想直接读取 /etc/passwd,应用程序不允许:
要求 filename 必须以 /var/www/images 开头。
利用 ../ 绕过:
空字节截断
有些应用程序要求 filename 必须以某种后缀名结尾,比如 .png,那么可尝试空字节截断,例如 filename=../../../etc/passwd%00.png。
防御
最安全的防御就是不使用用户提供的 filename 作为文件系统函数的参数,如果无法避免,下面是推荐的处理步骤:
- 验证用户输入的 filename 是否在白名单中。如果无法设置一个明确允许访问的文件路径白名单,就设置一个允许出现在 filename 参数值的字符白名单,至少不包括 . / \ 。
- 验证完用户输入的 filename,在 filename 前面添加基础目录的路径,然后用函数规范化路径,最后验证规范化后的路径的基础目录是否满足预先的要求。
关于第二点,所谓规范化路径,就是去除 ../ 或 ..\ 序列,比如一个 filename 追加基础目录的路径后其值是 /var/www/images/../../../etc/passwd,规范化处理后得到的路径是 /etc/passwd。
得到规范化路径后,就验证它是否符合预先要求的基础目录,比如 /etc/passwd 就不符合预先要求的 /var/www/images,应当视这次请求为恶意请求,拒绝继续处理。
以下是防御路径遍历漏洞的示例代码:
File file = new File(BASE_DIRECTORY, userInput);
if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) {
// process file
}