路径遍历漏洞实战指南:从原理到绕过技巧

本文深入探讨路径遍历漏洞的原理、常见场景和五种实际绕过技术,包括绝对路径绕过、非递归过滤、双重URL编码等,并提供自动化测试方法和修复建议。

路径遍历漏洞

当我刚开始学习Web安全时,路径遍历是那些看起来简单到不真实的漏洞之一。像操纵文件名这样基础的东西怎么会造成如此严重的损害?但在实验室花费数小时并测试真实应用程序后,我意识到简单性正是使其如此危险的原因。开发人员会忽略它,过滤器无法捕获它,在你意识到之前,攻击者已经在读取服务器最敏感的文件了。

在本文中,我将带你了解什么是路径遍历,在哪里找到它,如何使用五种不同的绕过技术进行测试,最后如何使用Python自动化测试。

什么是路径遍历?

路径遍历,也称为目录遍历,是一种允许攻击者访问存储在Web根文件夹外部的文件和目录的漏洞。通过操纵文件路径引用,攻击者可以浏览服务器的目录结构并读取任意文件。

最常见的目标?Linux系统上的/etc/passwd文件。这是一个包含用户账户信息的标准文件,是完美的概念验证,因为任何用户都可读取它,并确认你已成功遍历目录结构。

影响范围从信息泄露(源代码、配置文件、凭据)到完全服务器入侵(如果攻击者可以写入文件或访问敏感的应用程序逻辑)。

我们在哪里找到路径遍历?

路径遍历漏洞通常出现在处理文件操作的功能中。以下是最常见的查找位置:

  • 图像加载器:基于文件名参数显示图像的应用程序(如产品图像、头像或图库查看器)
  • 文件下载功能:任何允许用户通过指定文件名或路径下载文件的端点
  • 文档查看器:PDF查看器、报告生成器或任何动态加载文档的功能
  • 模板引擎:基于用户输入加载模板的系统
  • 文件上传处理器:有时文件名本身可以在上传过程中被操纵
  • 包含/要求语句:特别是在较旧的PHP应用程序中,用户输入被传递给include()require()

关键是查找任何似乎引用文件的参数:filenamefilepathtemplatepagedocument,甚至是自定义参数名称,如imgdocresource

测试路径遍历:5个真实场景

现在让我们进入实践。我将向你展示在PortSwigger的Web Security Academy实验室练习时遇到的五种不同场景。每个场景代表不同的防御机制以及如何绕过它们。

场景1:简单情况

第一个场景是最直接的。应用程序接受一个文件名参数并直接使用它来加载图像。没有过滤器,没有验证,只是纯粹信任用户输入。

易受攻击的请求如下:

1
GET /image?filename=product.jpg

要利用它,我只需将文件名替换为路径遍历序列:

1
GET /image?filename=../../../etc/passwd

../告诉服务器向上移动一个目录。通过将三个链接在一起,我从images目录导航,穿过Web根目录,进入系统的/etc目录。

我是如何发现的:我在Burp Suite中拦截了图像请求,注意到filename参数,并立即使用经典的遍历payload进行测试。响应返回了/etc/passwd的完整内容。完全没有抵抗。

场景2:绝对路径绕过

第二个场景更加棘手。应用程序完全阻止了像../这样的遍历序列。任何使用它们的尝试都会导致错误或请求被拒绝。

但问题是:如果应用程序阻止相对路径,那么绝对路径呢?

我没有尝试向上导航目录,而是直接告诉服务器确切的位置:

1
GET /image?filename=/etc/passwd

它起作用了。应用程序太专注于阻止../,以至于忘记了验证绝对路径。

我是如何发现的:在我最初的../payload被阻止后,我尝试了不同的变体。当我使用绝对路径/etc/passwd时,应用程序愉快地提供了文件。这教会了我过滤器通常是不完整的——它们阻止一件事但忘记了另一件事。

场景3:非递归剥离

第三个场景有一个过滤器,可以从输入中剥离../序列。乍一看,这似乎是一个可靠的防御。但过滤器有一个致命缺陷:它只运行一次。

我的意思是,如果我发送../../../etc/passwd,过滤器会移除../序列,留下etc/passwd,这不会起作用。

但是如果我嵌套序列呢?如果我发送....//而不是../呢?

当过滤器从....//中移除../时,它再次留下了../。所以我的payload变成了:

1
GET /image?filename=....//....//....//etc/passwd

过滤器剥离了中间字符,但剩余字符形成了有效的遍历序列。

我是如何发现的:在意识到过滤器正在移除../后,我尝试了不同的模式。嵌套方法起作用了,因为过滤器不是递归的——它只扫描输入一次,并且不检查移除后剩下的内容。

场景4:双重URL编码

第四个场景更加复杂。应用程序阻止了路径遍历序列,然后在使用输入之前对其执行URL解码。

这实际上是Web应用程序中的常见模式:解码输入,验证它,然后使用它。但顺序很重要。

如果应用程序在验证后解码,我可以通过双重编码我的payload来绕过过滤器。方法如下:

  • 正常:../
  • URL编码一次:%2e%2e%2f
  • URL编码两次:%252e%252e%252f

当我发送双重编码版本时,验证看到%252e%252e%252f,这看起来不像遍历序列。它通过验证。然后应用程序解码一次,将其变为%2e%2e%2f。最后,当应用程序使用它时,Web服务器再次解码它,将其变为../

我的payload:

1
GET /image?filename=..%252f..%252f..%252fetc/passwd

我是如何发现的:我注意到我的正常遍历序列被阻止了,但当我尝试URL编码时,行为略有变化。这告诉我应用程序正在进行某种解码。然后我尝试了双重编码,它完全绕过了过滤器。

场景5:路径验证绕过

第五个场景是最现实的。应用程序验证提供的路径以预期目录开头:/var/www/images/

这实际上是一个好的安全实践,但实施不正确。应用程序检查前缀,但之后没有规范化路径。

因此,我可以通过包含预期前缀来满足验证,然后从中遍历出来:

1
GET /image?filename=/var/www/images/../../../etc/passwd

应用程序看到开头的/var/www/images/并批准了它。但当路径被解析时,../序列从该目录导航回/etc

我是如何发现的:我注意到应用程序拒绝了不以/var/www/images/开头的路径。所以我在payload中包含了该前缀,然后在它后面添加了遍历序列。验证通过,遍历起作用。

奖励场景:空字节绕过(遗留系统)

还有一个值得一提的技术,尽管它只在较旧的系统上有效。一些应用程序验证文件名以特定扩展名结尾,如.png.jpg

在遗留系统(特别是较旧的PHP版本)上,你可以使用空字节(%00)绕过此限制。空字节在底层的C函数中终止字符串,因此之后的所有内容都被忽略。

Payload:

1
GET /image?filename=../../../etc/passwd%00.png

应用程序看到末尾的.png并批准了它。但当实际读取文件时,空字节在/etc/passwd处截断了字符串。

注意:这不再适用于现代系统,但对于旧应用程序仍然值得了解。

如何修复路径遍历

既然我们已经看到了如何利用路径遍历,让我们谈谈如何修复它。

最佳解决方案:根本不要在文件路径中使用用户输入。相反,使用在服务器端映射到文件名的ID或索引。例如:

1
GET /image?id=123

服务器在数据库中查找ID 123并检索相应的文件名。用户从不控制实际路径。

如果必须使用用户输入

  1. 根据允许文件的允许列表进行验证
  2. 规范化路径(解析所有../和符号链接)
  3. 验证规范化路径仍在预期目录内
  4. 使用防止遍历的安全文件API

以下是Java中的示例:

1
2
3
4
5
File file = new File(BASE_DIRECTORY, userInput);
String canonicalPath = file.getCanonicalPath();
if (!canonicalPath.startsWith(BASE_DIRECTORY)) {
    throw new SecurityException("Invalid file path");
}

这确保了无论攻击者使用什么技巧,最终路径都必须在基目录内。

最后思考

路径遍历是那些表面上看起来简单但在现实世界中有无尽变化的漏洞之一。每个应用程序都实施自己的过滤器和验证,这意味着你需要有创造力和持久性。

最重要的是:始终以道德方式测试。只测试你有权限测试的应用程序,无论是通过漏洞赏金计划、渗透测试任务还是像PortSwigger的Web Security Academy这样的实践实验室。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计