文件上传漏洞
翻译
原文:https://portswigger.net/web-security/file-upload
- name: 翻译
desc: 原文:https://portswigger.net/web-security/file-upload
bgColor: '#F0DFB1'
textColor: 'green'
2
3
4
# 0文件上传漏洞
在本节中,你将了解简单的文件上传函数,并将其用作于许多高严重性攻击的强大载体。我们还将向你展示 如何绕过常见的防御机制并上传 web shell,使你能够完全控制易受攻击的 Web 服务器。鉴于文件上传功能的常见程度,知道 “如何正确测试文件上传功能” 是必不可少的知识。
实验室
如果您已经熟悉 文件上传漏洞 背后的基本概念,并且只想在一些实际的、易受攻击的目标上练习和利用它们,那么您可以从下面的链接访问本主题中的所有实验室。
# 1什么是文件上传漏洞?
文件上传漏洞是指 Web 服务器允许用户将文件上传到其文件系统,但没有充分验证其名称、类型、内容或大小等属性。如果没有对这些文件属性进行适当的限制,则可能意味着,即使是基本的图像上传功能,也可以被用于上传 任意具有潜在危险性 的文件。这甚至可能包括 支持远程代码执行 的服务器端脚本文件。
在某些情况下,上传文件的行为本身就足以造成损害。而对于其他某些攻击,则可能涉及对文件的后续 HTTP 请求,通常是为了触发服务器对其的执行。
(译者加:前者,如果上传一个文件之后,网站崩了,那么 “上传文件” 本身就足以造成危害。后者,如果上传一个 webshell ,则攻击者必需通过后续请求与该 webshell 进行交互,让服务器执行其中的代码,才能造成危害性)
# 2文件上传漏洞会造成什么影响?
文件上传漏洞的影响,通常取决于两个关键因素:
- 文件的哪些方面没有被网站正确验证,无论是其大小、类型、内容等。
- 成功上传文件后,文件会受到哪些限制。
在最坏的情况下,文件的类型没有被正确验证,并且服务器配置了 允许某些类型的文件(如.php
和.jsp
)被作为代码执行。在这种情况下,攻击者可能会上传一个充当 web shell 的服务器端代码文件,从而有效的获得对服务器的完全控制权。
如果 文件名 未被正确验证,攻击者可以上传同名文件来覆盖关键文件。同时,如果服务器也容易受到目录遍历的攻击,则攻击者甚至能够将文件上传到意外的位置。
如果无法确保 文件大小 在预期阈值的范围内,还可能引发某种形式的拒绝服务(DoS)攻击,攻击者可能会填满可用的磁盘空间。
# 3文件上传漏洞是如何产生的?
鉴于相当明显的危险性,在野网站很少对 “用户允许上传哪些文件” 做任何限制。更常见的是,开发人员认为他们实现的验证功能是安全的,但这些验证要么存在固有缺陷,要么很容易绕过。
(译者加:网站要么没做任何限制,要么开发人员缺乏经验,做的限制可以被绕过跟shi一样)
例如,他们可能会尝试将危险的 文件类型 列入黑名单,但在检查文件扩展名时,未能考虑到 解析 上的差异。与任何黑名单一样,这很容易忽略某些仍然危险的模糊文件类型。
(译者加:例如,PHP 的文件类型有很多,包括 .php /.php3 / .php5 / .phtml等很多类型,黑名单总有遗漏的时候)
在某些情况下,网站可能会尝试验证 文件的其它属性 来检查文件类型,但攻击者可以使用 Burp Proxy 或 Repeater 等工具轻松操纵这些属性。
最终,即使是强大的验证措施,也可能在构成网站的主机和目录网络中,由于应用不一致而出现差异,导致这些差异可以被利用。
在本主题的后面部分,我们将教你如何利用其中的一些缺陷,来上传 web shell 并进行远程代码执行。我们还创建了一些交互式的、易受攻击的实验室,以便你可以针对一些实际的目标 练习所学的知识。
# 4Web 服务器如何处理对静态文件的请求?
在我们研究如何利用文件上传漏洞之前,你必须对 “服务器如何处理静态文件请求” 有一个基本的了解。
从历史上看,网站几乎完全由静态文件组成,这些文件将在用户请求时提供给用户。因此,每个请求的路径,都可以与服务器文件系统上的目录结构和文件层次 1:1 映射。但如今,随着网站越来越动态化,请求的路径通常与文件系统没有直接关系。尽管如此,Web 服务器仍然可以处理某些对静态文件的请求,包括样式表、图像等。
处理这些静态文件的过程大致相同。在某些时候,服务器会解析请求中的路径,以识别文件扩展名。然后,服务器使用该值来确定所请求的文件类型,一般会将其与 “扩展名和 MIME 类型之间的预配置映射列表” 进行比较。接下来发生的情况 取决于文件类型和服务器的配置。
- 如果此文件类型是不可执行的,例如图像或静态 HTML 页面,则服务器可能只在 HTTP 响应中将文件的内容返回给客户端。
- 如果文件类型是可执行的,例如 PHP 文件,并且服务器被配置为 “执行此类型的文件” ,则服务器将在运行脚本之前,根据 HTTP 请求中的标头和参数分配变量。然后,在 HTTP 响应中将输出结果返回给客户端。
- 如果文件类型是可执行的,但服务器没有配置 “执行此类型的文件” ,则它通常会返回一个错误的响应信息。但是,在某些情况下,文件的内容依然可以作为纯文本形式提供给客户端。这种错误配置有时会被用来 泄露源代码和其他敏感信息,你可以在我们的信息披露 (opens new window)学习资料中看到这方面的示例 (opens new window)。
提示
响应标头Content-Type
可能会提供有关于 “服务器提供哪种类型的文件” 的线索。如果应用程序代码没有显式设置此标头,则它通常包含文件扩展名/MIME
类型的映射结果。
现在你已经熟悉了关键概念,让我们看看如何利用这些类型的漏洞。
# 5利用不受限制的文件上传来部署 web shell
从安全的角度来看,最糟糕的情况是 网站允许你上传服务器端脚本,例如 PHP、Java 或 Python 文件,并且还配置为 “将它们作为代码执行” 。这使得在服务器上创建你自己的 web shell 变得非常简单。
# 5.1Web shell
Web shell 是一种恶意脚本,攻击者只需向正确的端点发送 HTTP 请求,即可在远程 Web 服务器上执行任意命令。
如果你能够成功上传一个 web shell ,则可以有效地完全控制服务器。这意味着你可以读取和写入任意文件、泄露敏感数据,甚至使用受控的服务器 对内部基础设施和网络外部的其他服务器 进行攻击。例如,以下 PHP 的一行代码可用于从服务器的文件系统中读取任意文件:
<?php echo file_get_contents('/path/to/target/file'); ?>
上传之后,发送对此恶意文件的请求,将会在响应中返回目标文件的内容。
- name: 实验室-学徒
desc: 通过上传web shell进行远程代码执行 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-web-shell-upload
bgColor: '#001350'
textColor: '#39d50c'
2
3
4
5
6
一个更通用的 web shell 可能看起来像这样:
<?php echo system($_GET['command']); ?>
此脚本允许你通过 查询参数 传递任意系统命令,如下所示:
GET /example/exploit.php?command=id HTTP/1.1
# 6利用文件上传的验证缺陷
在实际应用中,你不太可能找到一个没有任何 “针对文件上传攻击防护” 的网站,就像我们在上一个实验室中看到的那样。但仅仅是防御到位,并不意味着它们是强大的。
在本节中,我们将介绍 Web 服务器尝试验证和过滤文件上传的一些方法,以及如何利用这些机制中的缺陷,来获取用于远程代码执行的 web shell。
在本节中,我们将研究Web服务器尝试验证和清理文件上传的一些方法,以及如何利用这些机制中的缺陷来获得用于远程代码执行的Web shell。
# 6.1文件类型的验证缺陷
提交 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
[...图像二进制数据...]
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="description"
图像描述
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="username"
wiener
---------------------------012345678901234567890123456--
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如你所见,消息正文被分割为 每个表单输入的单独部分。每个部分都包含一个Content-Disposition
标头,该标头提供了相关输入字段的一些基本信息。这些单独的部分还可能包含相关的Content-Type
标头,该标头告诉服务器此部分数据的 MIME 类型。
网站验证文件上传的一种方法,检查此输入的Content-Type
标头是否与预期的 MIME 类型匹配。例如,如果服务器只需要图像文件,它可能只允许image/jpeg
和image/png
这样的类型。当服务器隐式信任此标头的值时,可能会出现问题。如果网站没有执行进一步的验证,来检查文件的内容是否真的与预期 MIME 类型匹配,则可以使用 Burp Repeater 之类的工具轻松绕过此防御。
- name: 实验室-学徒
desc: 绕过Content-Type限制并上传web shell >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-content-type-restriction-bypass
bgColor: '#001350'
textColor: '#39d50c'
2
3
4
5
6
# 6.2在用户可访问的目录中阻止文件执行
虽然,首先防止 “上传危险的文件类型” 是最佳措施,但第二道防线是:阻止服务器执行任何漏网的脚本。
作为预防措施,服务器通常只运行 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']); ?>
2
3
4
5
6
7
8
9
这种行为本身就很有趣,因为它可能提供了一种泄露源代码的方法,但它会使任何 web shell 都失效。
这种配置如果应用到不同的目录之间,通常会有所不同。提供用户文件上传的存储目录,该目录可能比文件系统上的其他位置存在更严格的控制,这些其他位置被认为是用户无法访问的。如果你能找到一种方法,将脚本上传到 “本不应该包含用户文件” 的其他目录,则服务器可能会执行你的脚本。
提示
Web 服务器通常在multipart/form-data
请求中使用filename
字段来确定文件的名称和保存位置。
- name: 实验室-从业者
desc: 通过路径遍历上传web shell >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-path-traversal
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
你还应该注意,即使你可以将所有的请求发送到同一个域名,但这通常会指向某种反向代理服务器,例如负载均衡器。你的请求通常由后台的其他服务器处理,这些服务器的配置也可能不同。
# 6.3黑名单缺少危险的文件类型
防止用户上传恶意脚本的一个更明显方法,将潜在危险的文件扩展名(如.php
)列入黑名单。黑名单的做法在本质上是有缺陷的,因为很难显式地阻止 可用于执行代码的、所有可能的文件扩展名。有时可以通过使用不太知名的替代文件扩展名(例如 .php5
、.shtml
)来绕过此类黑名单。
# 6.3.1覆盖服务器配置
正如我们在上一节中所讨论的,服务器通常不会执行文件,除非它们被配置为这样做。例如,在 Apache 服务器执行客户端请求的 PHP 文件之前,开发人员可能需要将以下指令添加到他们的/etc/apache2/apache2.conf
文件中:
LoadModule php_module /usr/lib/apache2/modules/libphp.so
AddType application/x-httpd-php .php
2
许多服务器还允许开发人员 在单个目录中创建特殊的配置文件,以便将配置 覆盖/添加 到一个或多个全局设置中。例如,Apache 服务器将从名为.htaccess
的文件中加载特定目录的配置(如果该文件存在)。
同样,开发人员可以在 IIS 服务器上使用web.config
文件进行特定目录的配置。这可能包括如下指令,在这种情况下,这些指令允许向用户提供 JSON 文件:
<staticContent>
<mimeMap fileExtension=".json" mimeType="application/json" />
</staticContent>
2
3
Web 服务器使用这些类型的配置文件(如果存在),但通常不允许用户通过 HTTP 请求来访问它们。但是,你可能偶尔会发现,服务器无法阻止你上传自己的恶意配置文件。在这种情况下,即使你需要的文件扩展名被列入黑名单,你也可以诱骗服务器,使其将任意的 自定义文件扩展名 映射到可执行的 MIME 类型当中。
- name: 实验室-从业者
desc: 绕过黑名单扩展并上传web shell >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-extension-blacklist-bypass
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 6.3.2混淆文件扩展名
(译者加:混淆技术 - 免杀的一种)
即使是最详尽的黑名单,也可以使用经典的混淆技术绕过。假设验证代码会区分大小写,并且无法识别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 转换或规范化后变为空字节和点。如果文件名被解析为 UTF-8 字符串,则
xC0 x2E
、xC4 xAE
或xC0 xAE
等序列可能会被转换为x2E
,但在该字符被应用于路径中之前,会被转换为 ASCII 字符。
(译者加:有兴趣的自行查阅资料 - “Apache HTTPD 未知后缀解析漏洞”、“点和空格绕过”、“URL编码绕过”、“IIS分号截断”、“00截断”,unicode 不太会 改天一定研究研究下次一定)
其他的防御措施包括 移除/替换 危险的扩展名,以防止文件被执行。但,如果没有以递归方式应用此措施,则可以定位禁止的字符串,使删除之后仍会留下有效的文件扩展名。考虑一下,如果从以下文件名中删除.php
会发生什么情况:
exploit.p.phphp
以上内容只是 混淆文件扩展名 众多方法中的一小部分。
- name: 实验室-从业者
desc: 通过混淆文件扩展名上传web shell >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-obfuscated-file-extension
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 6.4文件内容的验证缺陷
更安全的服务器不会隐式信任请求中指定的Content-Type
,而是尝试验证文件的内容 是否与预期的内容匹配。
对于图像上传功能,服务器可能会尝试验证图像的某些固有属性,例如图像尺寸。举个例子,如果你尝试上传一个 PHP 脚本,它根本没有任何尺寸。因此,服务器可以推断出 该文件不可能是图像,并相应地拒绝上传。
同样,某些文件类型可能始终在其 页眉或页脚 中包含特定的字节序列。这些序列可以像指纹或签名一样,用于确定内容是否与预期类型匹配。例如,JPEG 文件总是以字节FF D8 FF
开头。
这是验证文件类型的一种更健壮方法,但即使这样也不是万无一失的。使用特殊工具(如 ExifTool),可以轻松创建包含恶意代码的多语言 JPEG 文件。
- name: 实验室-从业者
desc: 通过上传多语言web shell进行远程执行代码 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-remote-code-execution-via-polyglot-web-shell-upload
bgColor: '#001350'
textColor: '#4cc1ff'
2
3
4
5
6
# 6.5利用条件竞争上传文件
现代框架对这类攻击的防御能力更加强大。它们通常不会将文件直接上传到文件系统中的预期位置。相反,他们会采取一些预防措施,例如 先将文件上传到一个临时的沙盒目录,然后对临时文件随机命名,以避免覆盖现有的文件。然后,他们对这个临时文件执行验证,仅在文件被认为是安全的情况时,才将其传输到目的位置。
但话又说回来,开发人员有时会独立于任何框架,以实现自己的文件上传处理。这不仅相当复杂,而且还可能引入危险的竞争条件,使攻击者能够完全绕过最强大的验证。
例如,一些网站直接将文件上传到主文件系统,如果文件未通过验证,则再次将其删除。这种行为依赖反病毒软件来检查恶意脚本,这在网站中很常见。这可能只需要几毫秒,但在服务器上的短时间内,攻击者仍可能执行该文件。
(以下两条为译者加:)
- (比较nb的框架 “用户上传文件 --> 先放到隔离沙箱中检测 --> 检测通过再放到主目录当中”)
- (一些缺乏经验的开发人员 “用户上传文件 --> 直接放到主目录中检测 --> 检测不合格再删除”,在 检测和删除 之间的那段时间给了攻击者可乘之机)
这些漏洞通常极其细微,使得它们在黑盒测试期间很难被检测到,除非你能找到泄露相关源代码的方法。
- name: 实验室-专家
desc: 通过条件竞争上传web shell >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/file-upload/lab-file-upload-web-shell-upload-via-race-condition
bgColor: '#001350'
textColor: '#d112fe'
2
3
4
5
6
# 6.5.1基于URL文件上传中的条件竞争
类似的竞争条件,也可能出现在允许通过所提供的 URL 来上传文件的函数中。在这种情况下,服务器必须通过互联网获取文件,并在本地创建文件副本,然后才能执行下一步的验证。
由于文件是使用 HTTP 加载的,因此开发人员无法使用其框架的内置机制 来安全地验证文件。相反,他们可能会手动创建自己的验证流程,并用来临时存储和验证文件,这可能不太安全。
例如,如果文件被加载到一个具有随机名称的临时目录中,理论上,攻击者应该不可能利用任何竞争条件。如果他们不知道目录的名称,他们就无法请求文件以触发其执行。另一方面,如果使用伪随机函数(如 PHP 的uniqid()
)来生成随机目录名称,则可能会被暴力破解。
(译者加:uniqid()
函数基于以微秒计的当前时间,生成一个唯一的 ID。注意,由于基于系统时间,通过该函数生成的 ID 不是最佳的。如需生成绝对唯一的 ID,请使用md5()
函数。 - 菜鸟教程 (opens new window))
为了使此类攻击更容易实施,你可以尝试延长处理文件所需的时间,为暴力破解目录名称争取足够的时间。一种方法是上传大型文件。如果它是分块处理的,则可以通过创建一个恶意文件来利用这一点,该文件的起始内容存在恶意有效负载,后面的内容是大量的任意填充字节。
(译者加:第一种方法,上传一个大型文件,使程序处理时间延长。对于分块处理,还是上传一个大型文件,其中前 1% 的内容写入恶意代码,后 99% 的内容写如垃圾字符)
# 7利用文件上传漏洞但不执行远程代码
到目前为止,在我们看到的示例中,我们已经能够上传服务器端脚本以进行远程代码执行。这是不安全文件上传功能的最严重后果,但这些漏洞仍然可以通过其他方式被利用。
# 7.1上传恶意客户端脚本
尽管你可能无法在服务器上执行脚本,但你仍然可以上传脚本以进行客户端攻击。例如,如果可以上传 HTML 文件或 SVG 图像,则可以使用<script>
标记来创建存储型XSS (opens new window)有效负载。
如果上传的文件,随后会出现在其他用户访问的页面上,则当他们的浏览器尝试呈现该页面时,将执行脚本。请注意,由于同源策略 (opens new window)的限制,只有在该文件来自于你上传文件的同一来源时,此类攻击才会有效。
# 7.2利用文件上传中的解析漏洞
如果上传的文件看起来都安全地存储和提供,则最后的手段是,尝试利用 “处理不同文件格式时的特定解析方式” 的漏洞。例如,你知道服务器会解析基于 XML 的文件,就像 Microsoft Office 中的.doc
或.xls
文件,这可能是XXE注入 (opens new window)攻击的潜在媒介。
# 8使用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'); ?>
2
3
4
5
6
提示
你可以尝试将OPTIONS
请求发送到不同的端点,以测试是否有任何公开支持PUT
方法的端点。
# 9如何防范文件上传漏洞
允许用户上传文件是很常见的事情,只要你采取正确的预防措施,就不会有危险。一般来说,保护你自己的网站免受这些漏洞侵害的最有效方法是 实施以下所有做法:
- 如果想检查文件扩展名,请使用 允许扩展名 的白名单,而不是 禁止扩展名 的黑名单。猜想你可能需要允许哪些扩展名,比猜想攻击者可能尝试上传哪些扩展名要容易得多。
- 确保文件名不包含任何 “可能被解释为目录遍历序列” 的字符串(
../
)。 - 重命名上传的文件,避免导致现有文件被覆盖的冲突事故发生。
- 在文件经过完全验证之前,不要将文件上传到服务器的永久文件系统中。
- 尽可能使用一个已存在的框架来处理文件上传,而不是尝试编写自己的验证机制。
(译者加:以下为个人总结)
- 对于文件上传,要使用白名单验证机制,不要使用黑名单。
- 确保文件名不会出现
../
等路径遍历序列。 - 对用户上传的文件进行重命名。
- 在对上传的文件进行验证时,先将文件放入一个临时目录(沙盒),通过验证后,再将文件移动到永久存储系统中。
- 有框架就用框架,不要自己写。
(译者加:根据以上防范建议,得出文件上传大致流程)
- 白名单验证扩展名;
- 检查文件名称中的不允许字符(例如路径遍历序列、空字符等);
- 将文件移动到临时目录,进行病毒查杀;
- 通过以上验证后,对文件重命名,并移动到永久存储系统中。