文件上传漏洞是指 Web 服务器允许用户在没有充分验证文件名称、类型、内容或大小等内容的情况下将文件上传到其文件系统。
0x01 前言
1.1 什么是文件上传漏洞?
文件上传漏洞是指 Web 服务器允许用户在没有充分验证文件名称、类型、内容或大小等内容的情况下将文件上传到其文件系统。未能正确执行这些限制可能意味着即使是基本的图像上传功能也可用于上传任意且具有潜在危险的文件。这甚至可以包括启用远程代码执行的服务器端脚本文件。
在某些情况下,上传文件的行为本身就足以造成损害。其他攻击可能涉及对文件的后续 HTTP 请求,通常是为了触发服务器执行该文件。
1.2 文件上传漏洞有什么影响?
文件上传漏洞的影响一般取决于两个关键因素:
- 网站未能正确验证文件的哪个方面,无论是其大小、类型、内容等。
- 文件成功上传后会受到哪些限制。
在最坏的情况下,文件的类型没有得到正确验证,服务器配置允许某些类型的文件(例如.php 和.jsp)作为代码执行。在这种情况下,攻击者可能会上传一个充当 Web shell 的服务器端代码文件,从而有效地授予他们对服务器的完全控制权。
如果文件名没有得到正确验证,这可能允许攻击者通过上传同名文件来覆盖关键文件。如果服务器也容易受到目录遍历的攻击,这可能意味着攻击者甚至可以将文件上传到意外位置。
未能确保文件大小在预期阈值范围内还可能导致某种形式的拒绝服务 (DoS) 攻击,攻击者借此填满可用的磁盘空间。
1.3 文件上传漏洞是如何产生的?
鉴于相当明显的危险,野外网站很少对允许用户上传哪些文件没有任何限制。更常见的是,开发人员实施他们认为具有内在缺陷或可以轻松绕过的强大验证。
例如,他们可能会尝试将危险文件类型列入黑名单,但在检查文件扩展名时未能考虑解析差异。与任何黑名单一样,也很容易意外忽略可能仍然危险的更晦涩的文件类型。
在其他情况下,网站可能会尝试通过验证攻击者可以使用 Burp Proxy 或 Repeater 等工具轻松操纵的属性来检查文件类型。
最终,即使是强大的验证措施也可能在构成网站的主机和目录网络中不一致地应用,从而导致可以利用的差异。
0x02 利用场景
2.1 利用不受限制的文件上传来部署 web shell
最糟糕的情况是网站允许您上传服务器端脚本,例如 PHP、Java 或 Python 文件,并且还配置为将它们作为代码执行。这使得在服务器上创建自己的 web shell 变得很简单。
网页外壳
Web shell 是一种恶意脚本,攻击者只需将 HTTP 请求发送到正确的端点,就可以在远程 Web 服务器上执行任意命令。
如果您能够成功上传 Web Shell,您就可以有效地完全控制服务器。这意味着您可以读取和写入任意文件、泄露敏感数据,甚至可以使用服务器对内部基础设施和网络外的其他服务器进行攻击。例如,以下 PHP one-liner 可用于从服务器的文件系统中读取任意文件:<?php echo file_get_contents('/path/to/target/file'); ?>
上传后,发送对该恶意文件的请求将在响应中返回目标文件的内容。
一个更通用的 web shell 可能看起来像这样:<?php echo system($_GET['command']); ?>
此脚本使您能够通过查询参数传递任意系统命令,如下所示:GET /example/exploit.php?command=id HTTP/1.1
2.2 利用有缺陷的文件上传验证
2.2.1 有缺陷的文件类型验证
网站可能尝试验证文件上传的一种方法是检查此特定于输入的 Content-Type 标头是否与预期的 MIME 类型匹配。
例如,如果服务器只需要图像文件,它可能只允许 image/jpeg 和之类的类型 image/png。当此标头的值被服务器隐式信任时,可能会出现问题。如果不执行进一步的验证来检查文件的内容是否实际匹配假定的 MIME 类型,则可以使用 Burp Repeater 等工具轻松绕过这种防御。
2.2.2 防止在用户可访问的目录中执行文件
虽然首先防止危险文件类型被上传显然更好,但第二道防线是阻止服务器执行任何通过网络溜走的脚本。
作为预防措施,服务器通常只运行其 MIME 类型已明确配置为执行的脚本。否则,它们可能只是返回某种错误消息,或者在某些情况下,将文件内容作为纯文本提供:
1 | GET /static/exploit.php?command=id HTTP/1.1 |
这种行为本身可能很有趣,因为它可能提供一种泄漏源代码的方法,但它会使任何创建 Web shell 的尝试无效。
这种配置通常在目录之间有所不同。用户提供的文件上传到的目录可能比文件系统上假定最终用户无法访问的其他位置具有更严格的控制。如果您能找到一种方法将脚本上传到不应该包含用户提供的文件的不同目录,那么服务器最终可能会执行您的脚本。
Web 服务器经常使用请求中的 filename 字段 multipart/form-data 来确定文件应保存的名称和位置。
2.2.3 危险文件类型的黑名单不足
防止用户上传恶意脚本的最明显方法之一是将具有潜在危险的文件扩展名列入黑名单,例如.php. 黑名单的做法本质上是有缺陷的,因为很难明确阻止每个可能用于执行代码的文件扩展名。这种黑名单有时可以通过使用鲜为人知的替代文件扩展名来绕过,这些文件扩展名可能仍然是可执行的,例如.php5,.shtml 等。
覆盖服务器配置
服务器通常不会执行文件,除非它们已被配置为这样做。例如,在 Apache 服务器执行客户端请求的 PHP 文件之前,开发人员可能必须将以下指令添加到他们的/etc/apache2/apache2.conf 文件中:
1 | LoadModule php_module /usr/lib/apache2/modules/libphp.so |
许多服务器还允许开发人员在各个目录中创建特殊的配置文件,以便覆盖或添加一个或多个全局设置。例如,Apache 服务器将从一个名为(.htaccess 如果存在)的文件中加载特定于目录的配置。
同样,开发人员可以使用 web.config 文件在 IIS 服务器上进行特定于目录的配置。这可能包括如下指令,在这种情况下允许将 JSON 文件提供给用户:
1 | <staticContent> |
Web 服务器在存在时使用这些类型的配置文件,但通常不允许您使用 HTTP 请求访问它们。但是,您可能偶尔会发现无法阻止您上传自己的恶意配置文件的服务器。在这种情况下,即使您需要的文件扩展名被列入黑名单,您也可以欺骗服务器将任意自定义文件扩展名映射到可执行的 MIME 类型。
混淆文件扩展名
即使是最详尽的黑名单也可能被经典的混淆技术绕过。假设验证代码区分大小写并且无法识别它 exploit.pHp 实际上是一个.php 文件。如果随后将文件扩展名映射到 MIME 类型的代码不区分大小写,则这种差异允许您将恶意 PHP 文件偷偷通过验证,最终可能由服务器执行。
您还可以使用以下技术获得类似的结果:
- 提供多个扩展。根据用于解析文件名的算法,以下文件可能被解释为 PHP 文件或 JPG 图像:exploit.php.jpg
- 添加尾随字符。一些组件会去除或忽略尾随空格、点等:exploit.php.
- 尝试对点、正斜杠和反斜杠使用 URL 编码(或双 URL 编码)。如果在验证文件扩展名时该值没有被解码,但后来在服务器端被解码,这也可以让您上传否则会被阻止的恶意文件:exploit%2Ephp
- 在文件扩展名前添加分号或 URL 编码的空字节字符。如果验证是用 PHP 或 Java 等高级语言编写的,但服务器使用 C/C++ 中的低级函数处理文件,例如,这可能会导致文件名结尾出现差异:exploit.asp;.jpg 或 exploit.asp%00.jpg
- 尝试使用多字节 unicode 字符,在 unicode 转换或规范化后可能会转换为空字节和点。xC0 x2E 如果文件名被解析为 UTF-8 字符串,则类似 xC4 xAE 或的序列 xC0 xAE 可能会被转换为 x2E,但随后会在用于路径之前转换为 ASCII 字符。
其他防御措施包括剥离或替换危险的扩展名以防止文件被执行。如果不递归应用此转换,您可以将禁止的字符串定位为删除它仍会留下有效的文件扩展名。例如,考虑.php 从以下文件名中剥离会发生什么:exploit.p.phphp
这只是混淆文件扩展名的众多方法中的一小部分。
2.2.4 有缺陷的文件内容验证
更安全的服务器不会隐式信任 Content-Type 请求中指定的内容,而是尝试验证文件的内容是否与预期内容实际匹配。
在图像上传功能的情况下,服务器可能会尝试验证图像的某些内在属性,例如其尺寸。例如,如果您尝试上传 PHP 脚本,则它根本没有任何维度。因此,服务器可以推断它不可能是图像,并相应地拒绝上传。
同样,某些文件类型可能总是在其页眉或页脚中包含特定的字节序列。这些可以用作指纹或签名来确定内容是否与预期的类型匹配。例如,JPEG 文件总是以 bytes 开头 FF D8 FF。
这是一种更可靠的文件类型验证方法,但即使这样也不是万无一失的。使用 ExifTool 等特殊工具,可以轻松创建包含元数据中恶意代码的多语言 JPEG 文件。
2.2.5 利用文件上传竞争条件
现代框架更能抵御此类攻击。他们通常不会将文件直接上传到文件系统上的预期目的地。相反,他们采取了预防措施,例如首先上传到临时的沙盒目录并随机命名以避免覆盖现有文件。然后,他们对这个临时文件执行验证,只有在认为安全的情况下才将其传输到目的地。
也就是说,开发人员有时会独立于任何框架来实现自己的文件上传处理。做好这件事不仅相当复杂,而且还可能引入危险的竞争条件,使攻击者能够完全绕过最强大的验证。
例如,一些网站直接将文件上传到主文件系统,如果没有通过验证,则再次将其删除。这种行为在依赖防病毒软件等检查恶意软件的网站中很常见。这可能只需要几毫秒,但在文件存在于服务器上的短时间内,攻击者仍有可能执行它。
这些漏洞通常非常微妙,因此在黑盒测试期间很难检测到,除非您能找到泄漏相关源代码的方法。
基于 URL 的文件上传中的竞争条件
在允许您通过提供 URL 来上传文件的函数中可能会出现类似的竞争条件。在这种情况下,服务器必须通过 Internet 获取文件并创建本地副本,然后才能执行任何验证。
由于文件是使用 HTTP 加载的,因此开发人员无法使用其框架的内置机制来安全地验证文件。相反,他们可以手动创建自己的流程来临时存储和验证文件,这可能不太安全。
例如,如果文件被加载到具有随机名称的临时目录中,理论上,攻击者应该不可能利用任何竞争条件。如果他们不知道目录的名称,他们将无法请求文件以触发其执行。另一方面,如果随机目录名称是使用 PHP 之类的伪随机函数生成的 uniqid(),则它可能会被暴力破解。
为了使此类攻击更容易,您可以尝试延长处理文件所需的时间,从而延长暴力破解目录名称的窗口。一种方法是上传更大的文件。如果它以块的形式进行处理,您可以通过在开始时创建一个带有有效负载的恶意文件,然后是大量的任意填充字节来利用这一点。
2.3 无需远程执行代码即可利用文件上传漏洞
在我们目前看到的示例中,我们已经能够上传服务器端脚本以进行远程代码执行。这是不安全的文件上传功能最严重的后果,但这些漏洞仍然可以通过其他方式被利用。
2.3.1 上传恶意客户端脚本
尽管您可能无法在服务器上执行脚本,但您仍然可以上传脚本以进行客户端攻击。例如,如果您可以上传 HTML 文件或 SVG 图像,则可以使用