所有内容,来自burpsuite官方靶场(portswigger)
文件上传
什么是文件上传
文件上传漏洞是指 Web 服务器允许用户在没有充分验证文件名称、类型、内容或大小等内容的情况下将文件上传到其文件系统。未能正确执行这些限制可能意味着即使是基本的图像上传功能也可用于上传任意且具有潜在危险的文件。这甚至可以包括启用远程代码执行的服务器端脚本文件。
在某些情况下,上传文件的行为本身就足以造成损害。其他攻击可能涉及对文件的后续 HTTP 请求,通常是为了触发服务器执行该文件。
文件上传漏洞有什么影响
文件上传漏洞的影响一般取决于两个关键因素:
- 网站未能正确验证文件的哪个方面,无论是其大小、类型、内容等。
- 文件成功上传后会受到哪些限制。
在最坏的情况下,文件的类型没有得到正确验证,服务器配置允许某些类型的文件(例如.php
和.jsp
)作为代码执行。在这种情况下,攻击者可能会上传一个充当 Web shell 的服务器端代码文件,从而有效地授予他们对服务器的完全控制权。
如果文件名没有得到正确验证,这可能允许攻击者通过上传同名文件来覆盖关键文件。如果服务器也容易受到目录遍历的攻击,这可能意味着攻击者甚至可以将文件上传到意外位置。
未能确保文件大小在预期阈值范围内还可能导致某种形式的拒绝服务 (DoS) 攻击,攻击者借此填满可用的磁盘空间。
文件上传漏洞是如何产生的
鉴于相当明显的危险,野外网站很少对允许用户上传哪些文件没有任何限制。更常见的是,开发人员实施他们认为具有内在缺陷或可以轻松绕过的强大验证。
例如,他们可能会尝试将危险文件类型列入黑名单,但在检查文件扩展名时未能考虑解析差异。与任何黑名单一样,也很容易意外忽略可能仍然危险的更晦涩的文件类型。
在其他情况下,网站可能会尝试通过验证攻击者可以使用 Burp Proxy 或 Repeater 等工具轻松操纵的属性来检查文件类型。
最终,即使是强大的验证措施也可能在构成网站的主机和目录网络中不一致地应用,从而导致可以利用的差异。
下文有如何利用这些缺陷上传 Web shell 以进行远程代码执行。
Web服务器如何处理对静态文件的请求
在我们研究如何利用文件上传漏洞之前,您必须对服务器如何处理静态文件请求有一个基本的了解。
从历史上看,网站几乎完全由静态文件组成,这些文件会在用户请求时提供给用户。因此,每个请求的路径可以与服务器文件系统上的目录和文件的层次结构 1:1 映射。如今,网站越来越动态,请求的路径通常与文件系统没有直接关系。尽管如此,Web 服务器仍会处理对一些静态文件的请求,包括样式表、图像等。
处理这些静态文件的过程仍然大致相同。在某些时候,服务器会解析请求中的路径以识别文件扩展名。然后它使用它来确定所请求文件的类型,通常通过将其与扩展名和 MIME 类型之间的预配置映射列表进行比较。接下来会发生什么取决于文件类型和服务器的配置。
- 如果此文件类型是不可执行的,例如图像或静态 HTML 页面,则服务器可能只是在 HTTP 响应中将文件的内容发送给客户端。
- 如果文件类型是可执行的,例如 PHP 文件,并且服务器被配置为执行这种类型的文件,它会在运行脚本之前根据 HTTP 请求中的标头和参数分配变量。然后可以将结果输出以 HTTP 响应的形式发送到客户端。
- 如果文件类型是可执行的,但服务器没有配置为执行这种类型的文件,它通常会响应错误。但是,在某些情况下,文件的内容可能仍以纯文本形式提供给客户端。这种错误配置有时会被利用来泄露源代码和其他敏感信息。
Tip
Content-Type
响应标头可能会提供有关服务器认为它已提供的文件类型的线索 。如果应用程序代码没有明确设置此标头,它通常包含文件扩展名/MIME 类型映射的结果。
利用不受限制的文件上传来部署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'); ?>
上传后,发送对该恶意文件的请求将在响应中返回目标文件的内容。
LAB:通过web shell 上传远程代码执行
<?php echo file_get_contents('/home/carlos/secret'); ?>
一个更通用的 web shell 可能看起来像这样:
<?php echo system($_GET['command']); ?>
此脚本使您能够通过查询参数传递任意系统命令,如下所示:
GET /example/exploit.php?command=id HTTP/1.1
利用有缺陷的文件上传验证
在野外,您不太可能找到一个网站,它对文件上传攻击没有任何保护,就像我们在之前的实验室中看到的那样。但仅仅因为防御措施到位,并不意味着它们是强大的。
在本节中,我们将研究 Web 服务器尝试验证和清理文件上传的一些方法,以及如何利用这些机制中的缺陷来获取用于远程代码执行的 Web shell。
有缺陷的文件类型验证
提交 HTML 表单时,您的浏览器通常会POST
在内容类型为 的请求中发送提供的数据application/x-www-form-url-encoded
。这适用于发送您的姓名、地址等简单文本,但不适用于发送大量二进制数据,例如整个图像文件或 PDF 文档。在这种情况下,内容类型multipart/form-data
是首选方法。
考虑一个包含用于上传图像、提供图像描述和输入用户名的字段的表单。提交此类表单可能会导致请求如下所示:
POST /images HTTP/1.1
Host: normal-website.com
Content-Length: 12345
Content-Type: multipart/form-data; boundary=---------------------------012345678901234567890123456
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="image"; filename="example.jpg"
Content-Type: image/jpeg
[...binary content of example.jpg...]
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="description"
This is an interesting description of my image.
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="username"
wiener
---------------------------012345678901234567890123456--
如您所见,消息正文被拆分为每个表单输入的单独部分。每个部分都包含一个Content-Disposition
标题,该标题提供了有关与其相关的输入字段的一些基本信息。这些单独的部分也可能包含它们自己的Content-Type
标头,它告诉服务器使用此输入提交的数据的 MIME 类型。
网站可能尝试验证文件上传的一种方法是检查此特定于输入的Content-Type
标头是否与预期的 MIME 类型匹配。例如,如果服务器只需要图像文件,它可能只允许image/jpeg
和之类的类型image/png
。当此标头的值被服务器隐式信任时,可能会出现问题。如果不执行进一步的验证来检查文件的内容是否实际匹配假定的 MIME 类型,则可以使用 Burp Repeater 等工具轻松绕过这种防御。
LAB:通过Content-Type 限制绕过Web shell上传
更改Content-Type
为image/jpeg
.
防止在用户可访问的目录中执行文件
虽然首先防止危险文件类型被上传显然更好,但第二道防线是阻止服务器执行任何通过网络溜走的脚本。
作为预防措施,服务器通常只运行其 MIME 类型已明确配置为执行的脚本。否则,它们可能只是返回某种错误消息,或者在某些情况下,将文件内容作为纯文本提供:
GET /static/exploit.php?command=id HTTP/1.1
Host: normal-website.com
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 39
<?php echo system($_GET['command']); ?>
这种行为本身可能很有趣,因为它可能会提供一种泄漏源代码的方法,但它会使任何创建 Web shell 的尝试无效。
这种配置通常在目录之间有所不同。用户提供的文件上传到的目录可能比文件系统上假定最终用户无法访问的其他位置具有更严格的控制。如果您能找到一种方法将脚本上传到不应该包含用户提供的文件的不同目录,那么服务器最终可能会执行您的脚本。
Web 服务器经常使用请求中的
filename字段
multipart/form-data来确定文件应保存的名称和位置。
LAB:通过路径遍历上传Web shell
上传后、访问;PHP文件并未得到解析
把php文件上传到“上级”路径中
/ url编码 %2f
危险文件类型的黑名单不足
防止用户上传恶意脚本的最明显方法之一是将具有潜在危险的文件扩展名(如.php
. 黑名单的做法本质上是有缺陷的,因为很难明确阻止每个可能用于执行代码的文件扩展名。有时可以通过使用鲜为人知的替代文件扩展名绕过此类黑名单,这些扩展名可能仍然是可执行的,例如.php5
,.shtml
等。
覆盖服务器配置
正如我们在上一节中所讨论的,服务器通常不会执行文件,除非它们已被配置为这样做。例如,在 Apache 服务器执行客户端请求的 PHP 文件之前,开发人员可能必须将以下指令添加到他们的/etc/apache2/apache2.conf
文件中:
LoadModule php_module /usr/lib/apache2/modules/libphp.so AddType application/x-httpd-php .php
许多服务器还允许开发人员在各个目录中创建特殊的配置文件,以便覆盖或添加一个或多个全局设置。例如,Apache 服务器将从一个名为(.htaccess
如果存在)的文件中加载特定于目录的配置。
同样,开发人员可以使用web.config
文件在 IIS 服务器上进行特定于目录的配置。这可能包括如下指令,在这种情况下允许将 JSON 文件提供给用户:
<staticContent> <mimeMap fileExtension=".json" mimeType="application/json" /> </staticContent>
Web 服务器在存在时使用这些类型的配置文件,但通常不允许您使用 HTTP 请求访问它们。但是,您可能偶尔会发现无法阻止您上传自己的恶意配置文件的服务器。在这种情况下,即使您需要的文件扩展名被列入黑名单,您也可以欺骗服务器将任意自定义文件扩展名映射到可执行的 MIME 类型。
LAB:通过扩展黑名单绕过Web shell上传
-
filename
将参数 的值更改为.htaccess
。 -
Content-Type
将标头 的值更改为text/plain
. -
将文件的内容(您的 PHP 有效负载)替换为以下 Apache 指令:
AddType application/x-httpd-php .l33t
这会将任意扩展名 (
.l33t
) 映射到可执行 MIME 类型application/x-httpd-php
。由于服务器使用该mod_php
模块,它已经知道如何处理这个问题。
filename将参数 的值从 更改
exploit.php为
exploit.l33t。再次发送请求,发现文件上传成功。
未解析成功…
混淆文件扩展名
即使是最详尽的黑名单也可能被经典的混淆技术绕过。假设验证代码区分大小写并且无法识别它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
这只是混淆文件扩展名的众多方法中的一小部分。
LAB:通过混淆文件扩展名上传Web shell
%00截断
有缺陷的文件内容验证
更安全的服务器不会隐式信任Content-Type
请求中指定的内容,而是尝试验证文件的内容是否与预期内容实际匹配。
在图像上传功能的情况下,服务器可能会尝试验证图像的某些内在属性,例如其尺寸。例如,如果您尝试上传 PHP 脚本,则它根本没有任何维度。因此,服务器可以推断它不可能是图像,并相应地拒绝上传。
同样,某些文件类型可能总是在其页眉或页脚中包含特定的字节序列。这些可以用作指纹或签名来确定内容是否与预期的类型匹配。例如,JPEG 文件总是以 bytes 开头FF D8 FF
。
这是一种更可靠的文件类型验证方法,但即使这样也不是万无一失的。使用 ExifTool 等特殊工具,可以轻松创建包含元数据中恶意代码的多语言 JPEG 文件。
LAB:通过多语言Web shell上传远程执行代码
制作图片马
访问
利用文件上传竞争条件
现代框架更能抵御此类攻击。他们通常不会将文件直接上传到文件系统上的预期目的地。相反,他们采取了预防措施,例如首先上传到临时的沙盒目录并随机命名以避免覆盖现有文件。然后,他们对这个临时文件执行验证,只有在认为安全的情况下才将其传输到目的地。
也就是说,开发人员有时会独立于任何框架来实现自己的文件上传处理。做好这件事不仅相当复杂,而且还可能引入危险的竞争条件,使攻击者能够完全绕过最强大的验证。
例如,一些网站直接将文件上传到主文件系统,如果没有通过验证,则再次将其删除。这种行为在依赖防病毒软件等检查恶意软件的网站中很常见。这可能只需要几毫秒,但在文件存在于服务器上的短时间内,攻击者仍有可能执行它。
这些漏洞通常非常微妙,因此在黑盒测试期间很难检测到,除非您能找到泄漏相关源代码的方法。
LAB:通过竞争条件上传Web shell
可能因为网络问题…未成功
基于URL的文件上传中的竞争条件
在允许您通过提供 URL 来上传文件的函数中可能会出现类似的竞争条件。在这种情况下,服务器必须通过 Internet 获取文件并创建本地副本,然后才能执行任何验证。
由于文件是使用 HTTP 加载的,因此开发人员无法使用其框架的内置机制来安全地验证文件。相反,他们可以手动创建自己的流程来临时存储和验证文件,这可能不太安全。
例如,如果文件被加载到具有随机名称的临时目录中,理论上,攻击者应该不可能利用任何竞争条件。如果他们不知道目录的名称,他们将无法请求文件以触发其执行。另一方面,如果随机目录名称是使用 PHP 之类的伪随机函数生成的uniqid()
,则它可能会被暴力破解。
为了使此类攻击更容易,您可以尝试延长处理文件所需的时间,从而延长暴力破解目录名称的窗口。一种方法是上传更大的文件。如果它以块的形式进行处理,您可以通过在开始时创建一个带有有效负载的恶意文件,然后是大量的任意填充字节来利用这一点。
无需远程执行代码即可利用利用文件上传漏洞
在目前看到的示例中,我们已经能够上传服务器端脚本以进行远程代码执行。这是不安全的文件上传功能最严重的后果,但这些漏洞仍然可以通过其他方式被利用。
上传恶意客户端脚本
尽管可能无法在服务器上执行脚本,但仍然可以上传脚本以进行客户端攻击。例如,如果可以上传 HTML 文件或 SVG 图像,则可以使用<script>
标签来创建存储的 XSS有效负载。
如果上传的文件随后出现在其他用户访问的页面上,则他们的浏览器将在尝试呈现页面时执行脚本。请注意,由于同源策略限制,这些类型的攻击只有在上传的文件是从您上传文件的同一源提供时才会起作用。
利用上传文件解析中漏洞
如果上传的文件看起来既安全又安全,最后的手段是尝试利用特定于解析或处理不同文件格式的漏洞。例如,您知道服务器解析基于 XML 的文件,例如 Microsoft Office.doc
或.xls
文件,这可能是XXE 注入攻击的潜在载体。
使用PUT上传文件
值得注意的是,某些 Web 服务器可能被配置为支持PUT
请求。如果没有适当的防御措施,这可以提供上传恶意文件的替代方法,即使上传功能无法通过 Web 界面使用。
PUT /images/exploit.php HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-httpd-php
Content-Length: 49
<?php echo file_get_contents('/path/to/file'); ?>
如何防止文件上传漏洞
允许用户上传文件是司空见惯的事,只要您采取正确的预防措施,就不一定会有危险。一般来说,保护您自己的网站免受这些漏洞影响的最有效方法是实施以下所有做法:
- 根据允许扩展名的白名单而不是禁止扩展名的黑名单检查文件扩展名。猜测您可能希望允许哪些扩展比猜测攻击者可能尝试上传哪些扩展要容易得多。
- 确保文件名不包含任何可能被解释为目录或遍历序列 (
../
) 的子字符串。 - 重命名上传的文件以避免可能导致现有文件被覆盖的冲突。
- 在完全验证之前不要将文件上传到服务器的永久文件系统。
- 尽可能使用已建立的框架来预处理文件上传,而不是尝试编写自己的验证机制。
评论区