文件上传漏洞
Last updated
Last updated
在本节中,你将了解如何将简单的文件上传功能用作多种高危攻击的强大手段。我们会向你展示如何绕过常见的防御机制,以上传一个Web shell,从而让你完全控制一个存在漏洞的Web服务器。考虑到文件上传功能的普遍性,了解如何正确地测试它们是必不可少的知识。
Labs
如果你已经熟悉文件上传漏洞背后的基本概念,并只想在一些现实的、故意易受攻击的目标上练习利用它们,你可以从下面的链接访问本主题中的所有实验。
文件上传漏洞是指Web服务器允许用户将文件上传到其文件系统,但在验证这些文件的名称、类型、内容或大小时没有进行足够的校验。如果对这些没有做好适当的限制,那么甚至一个基本的图片上传功能也可能被用来上传任意的、具有潜在危险的文件。这可能包括能够执行远程代码的服务器端脚本文件。
在某些情况下,上传文件本身就足以造成损害。其他攻击可能涉及对文件进行后续的HTTP请求,通常是为了触发服务器执行该文件。
文件上传漏洞的影响通常取决于两个关键因素:
网站未能正确验证文件的哪个方面,无论是大小、类型、内容等等。
在文件成功上传后,对文件施加了哪些限制。
在最坏的情况下,文件的类型没有被正确验证,并且服务器配置允许某些类型的文件(如.php
和.jsp
)作为代码执行。在这种情况下,攻击者可能会上传一个作为Web shell的服务器端代码文件,实际上授予了他们对服务器的完全控制。
如果文件名没有被正确验证,可能会允许攻击者通过上传一个同名文件来覆盖关键的文件。如果服务器还存在目录遍历漏洞,这可能意味着攻击者甚至能够将文件上传到未预期的位置。
如果没有确保文件大小在预期的阈值内,还可能会导致某种形式的拒绝服务(DoS)攻击,攻击者借此会填满可用的磁盘空间。
鉴于这些明显的危险,实际情况中的网站对允许用户上传的文件很少没有任何限制。更常见的是,开发者实现了他们认为是健壮的验证,但这种验证要么本身存在缺陷,要么可以轻易地被绕过。
例如,他们可能试图将危险的文件类型列入黑名单,但在检查文件后缀名时未能考虑到解析差异。就像任何黑名单一样,也很容易无意中遗漏一些很不常见的仍可能危险的文件类型。
在其他情况下,网站可能会试图通过验证属性来检查文件类型,而这些属性很容易被使用Burp Proxy或Repeater等工具的攻击者轻松操纵。
归根结底,即使是强大的验证措施也可能在构成网站的主机和目录网络中应用不一致,从而产生可以被利用的差异。
在本主题的后面,我们将教你如何利用这些缺陷来上传一个用于远程代码执行的Web shell。我们甚至创建了一些交互式的、故意易受攻击的实验,让你可以针对一些现实的目标练习你所学到的知识。
在研究如何利用文件上传漏洞前,你必须对服务器如何处理静态文件的请求有一个基本了解。
在过去,网站几乎完全由静态文件组成,当用户请求时,这些文件会被提供给用户。因此,每个请求的路径可以与服务器文件系统中的目录和文件的层级结构一一对应。而现在,网站越来越动态,请求的路径往往与文件系统没有直接关系。尽管如此,Web服务器仍然要处理一些静态文件的请求,包括样式表、图片等等。
处理这些静态文件的过程基本上还是相同的。在某个时刻,服务器解析请求中的路径来识别文件后缀名。然后,它利用这一点来确定被请求的文件类型,通常是通过将其与预配置的后缀名和MIME类型的映射列表进行比较。接下来的情况取决于文件类型和服务器的配置。
如果这种文件类型是不可执行的,比如图片或静态HTML页面,服务器可能只是在HTTP响应中将文件的内容发送到客户端。
如果文件类型是可执行的,比如PHP文件,并且服务器配置了执行这种类型的文件,它将根据HTTP请求中的标头和参数分配变量,然后运行脚本。之后,运行结果可以通过HTTP响应发送到客户端。
如果文件类型是可执行的,但是服务器没有配置执行这种类型的文件,它通常会返回一个错误。然而,在某些情况下,文件的内容仍可能以纯文本形式提供给客户端。这种错误配置偶尔可以被利用来泄露源代码和其他敏感信息。你可以在我们的信息泄露学习材料中看到这样的例子。
提示
Content-Type
响应标头可以提供关于服务器认为它提供了什么类型文件的线索。如果这个标头没有被应用程序代码明确设置,它通常包含文件后缀名/MIME类型映射的结果。
现在你已经熟悉了关键概念,让我们看看你如何能够潜在地利用这些类型的漏洞。
从安全角度看,最糟糕的情况是网站允许你上传服务器端脚本,比如PHP、Java或Python文件,并配置为将它们作为代码执行。这使得在服务器上创建你自己的Web shell变得非常简单。
Web shell
Web shell是一种恶意脚本,它可以让攻击者仅通过向正确的端点发送HTTP请求就能在远程Web服务器上执行任意命令。
如果能成功地上传一个Web shell,你实际上就完全控制了服务器。这意味着可以读取和写入任意文件,窃取敏感数据,甚至利用服务器对内部基础设施和网络外的其他服务器进行攻击。例如,下面的单行PHP可以用来从服务器的文件系统中读取任意文件:
上传后,向这个恶意文件发送请求会在响应中返回目标文件的内容。
LAB
一个更多功能的Web shell看起来可能像这样:
这个脚本让你可以通过一个查询参数传递一个任意的系统命令,如下所示:
在实际环境中,不太可能会遇到像之前实验中看到的那样,对于文件上传攻击完全没有保护的网站。但是,仅因为有防御措施,并不意味着这些防御措施就足够强大。
在这一部分中,我们将探讨Web服务器试图验证和清理文件上传的一些方式,以及如何利用这些机制中的缺陷获得一个Web shell以进行远程代码执行。
当提交HTML表单时,浏览器通常以POST
请求的形式发送所提供的数据,内容类型为 application/x-www-form-url-encoded
。这对于发送简单的文本,如你的姓名、地址等是没有问题的,但不适合发送大量的二进制数据,比如整个图片文件或PDF文档。在这种情况下,multipart/form-data
内容类型是首选的方式。
考虑一个包含上传图片、提供图片描述和输入用户名等字段的表单。提交这样的表单可能会导致一个类似以下的请求:
如你所见,对于每个表单的输入,消息正文被分割成单独的部分。每部分包含一个Content-Disposition
头,它提供了一些关于它所涉及的输入字段的基本信息。这些独立部分也可能包含它们自己的Content-Type
标头,该标头告诉服务器使用此输入提交的数据的MIME类型。
网站可能试图验证文件上传的一种方式是检查这个特定输入的Content-Type
标头是否匹配预期的MIME类型。例如,如果服务器只期待图片文件,它可能只允许image/jpeg
和image/png
等类型。当这个标头的值被服务器隐含地信任时就会出现问题。如果没有进行进一步的验证来检查文件的内容是否真的与假定的MIME类型相匹配,那么可以使用Burp Repeater等工具轻松绕过这个防御。
LAB
虽然从一开始就阻止危险的文件类型被上传显然更好,但第二道防线是阻止服务器执行任何漏网之鱼的脚本。
作为预防措施,服务器通常只运行那些被明确配置为执行的MIME类型的脚本。否则,它们可能只返回某种错误信息,或者在某些情况下,以纯文本的方式提供文件的内容:
这种行为本身可能很有趣,因为它可能提供一种泄露源代码的方法,但它使创建Web shell的任何企图都化为泡影。
这种类型的配置在不同的目录之间往往有所不同。一个接收用户提供的文件的目录,很可能比文件系统上的其他位置有更严格的控制,这些位置被认为是终端用户无法访问的。如果你能找到一种方式,将脚本上传到一个不应该包含用户提供的文件的不同目录中,那么服务器可能仍然会执行你的脚本。
提示
Web服务器经常使用
multipart/form-data
请求中的filename
字段来确定文件的名称和应该保存的位置。
LAB
还应该注意,即使你可能将所有的请求发送到同一个域名,但这往往会指向某种反向代理服务器,如负载均衡。你的请求通常会由后面的其他服务器处理,这些服务器的配置也可能有所不同。
防止用户上传恶意脚本的一个比较明显的方法是将可能危险的文件后缀名(如.php
)列入黑名单。但是,黑名单的做法本质上存在缺陷,因为很难明确地阻止每一种可能被用来执行代码的文件后缀名。这样的黑名单有时可以通过使用鲜为人知的、可能仍然是可执行的替代文件后缀名(如.php5
、.shtml
等)来绕过。
正如我们在前一部分中讨论的,服务器通常不会执行文件,除非它们已经被配置为这样做。例如,Apache服务器在执行客户端请求的PHP文件之前,开发者可能需要将以下指令添加到他们的/etc/apache2/apache2.conf
文件:
许多服务器还允许开发者在单独的目录中创建特殊的配置文件,以覆盖或添加一个或多个全局设置。例如,Apache服务器会从一个名为.htaccess
的文件中加载一个特定目录的配置,如果存在的话。
同样,开发者可以使用web.config
文件在IIS服务器上进行特定目录的配置。这可能包括以下指令,在这种情况下,它允许将JSON文件提供给用户:
Web服务器在存在这种类型的配置文件时使用它们,但通常不允许你使用HTTP请求访问它们。然而,你可能偶尔会发现服务器未能阻止你上传自己的恶意配置文件。在这种情况下,即使需要的文件后缀名在黑名单上,你也可能欺骗服务器将一个任意的、自定义的文件后缀名映射到一个可执行的MIME类型。
LAB
即使是最全面的黑名单,也可能使用经典的混淆技术被绕过。假设验证代码区分大小写,并没有认出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字符。
其他防御措施包括清除或替换危险的后缀名以防止文件被执行。如果这种转换不是递归应用的,你可以以这样的方式放置禁止的字符串,即去除它后仍会留下一个有效的文件后缀名。例如,考虑一下如果你将.php
从下面的文件名中清除会发生什么:
这只是混淆文件后缀名的多种方法中的一小部分。
LAB
更安全的服务器会试图验证文件的内容是否实际符合预期,而不是隐含地信任请求中指定的Content-Type
。
在一个图片上传功能的情况下,服务器可能会尝试验证图片的某些固有属性,例如其尺寸。例如,如果你尝试上传一个PHP脚本,它根本就没有任何尺寸。因此,服务器可以推断它不可能是一个图片,并据此拒绝上传。
同样,某些文件类型在其头部或尾部可能总是包含一个特定的字节序列。这些可以像指纹或签名一样被用来确定内容是否符合预期的类型。例如,JPEG文件总是以FF D8 FF
字节开始。
这是验证文件类型的一种更稳健的方法,但即使这样也不是万无一失的。使用特殊工具,如ExifTool,可以轻易地创建一个元数据中含有恶意代码的polyglot[1] JPEG文件。
LAB
现代框架更能防御此类攻击。它们通常不会将文件直接上传到文件系统的预期目标地。相反,它们会采取预防措施,比如先上传到一个临时的、沙箱化的目录,并随机化名称以避免覆盖现有的文件。然后对这个临时文件进行验证,只有在认为是安全的情况下才将其转移到其目的地。
也就是说,开发者有时会独立于任何框架实现他们自己的文件上传处理。这不仅相当复杂,还可能引入危险的条件竞争,使攻击者能够完全绕过最健壮的验证。
例如,有些网站将文件直接上传到主文件系统,然后如果它没有通过验证,就再次将其删除。这种行为在那些依赖杀毒软件等检查恶意软件的网站中很典型。可能只需要几毫秒,但在文件存在于服务器上的短暂时间内,攻击者仍有可能执行它。
这些漏洞往往极其微妙,除非你能找到一种方法泄露相关源代码,否则在黑盒测试中很难检测到它们。
LAB
在允许你通过提供一个URL来上传文件的功能中也会出现类似的条件竞争。在这种情况下,服务器必须通过互联网获取该文件,并在进行任何验证之前创建一个本地副本。
由于文件是通过HTTP加载的,开发者无法使用他们的框架的内置机制来安全地验证文件。相反,他们可能会手动创建自己的流程来临时存储和验证文件,这可能并不完全安全。
例如,如果文件被加载到一个随机名称的临时目录中,理论上,攻击者应该不可能利用任何条件竞争。如果他们不知道目录的名称,他们将无法请求文件以触发其执行。另一方面,如果随机的目录名是使用伪随机函数生成的,比如PHP的uniqid()
,那么它可能会被暴力破解。
为了使这样的攻击更容易,你可以试图延长处理文件所需的时间,从而延长暴力破解目录名的窗口。做到这一点的一种方法是上传一个更大的文件。如果它是分块处理的,你就有可能利用这一点,创建一个恶意文件,把有效载荷放在开头,后面是大量的任意填充字节。
在我们迄今为止看到的例子中,我们能够上传服务器端脚本以进行远程代码执行。这是不安全的文件上传功能最严重的后果,但这些漏洞仍然可以以其他方式被利用。
尽管可能无法在服务器上执行脚本,但你仍然可以上传脚本以进行客户端攻击。例如,如果你能够上传HTML文件或SVG图片,你就有可能使用<script>
标签来创建存储型XSS有效载荷。
如果上传的文件随后出现在其他用户访问的页面上,他们的浏览器将在试图渲染该页面时执行该脚本。请注意,由于同源策略的限制,只有当上传的文件从你上传它的相同源进行服务时,这类攻击才会起作用。
如果上传的文件看起来既被安全地存储又被安全地提供,那么最后的手段就是尝试利用特定于解析或处理不同文件格式的漏洞。例如,你知道服务器解析基于XML的文件,如Microsoft Office .doc
或.xls
文件,这可能是XXE注入攻击的一个潜在载体。
值得注意的是,一些Web服务器可能被配置为支持PUT
请求。如果没有适当的防御措施,这可以提供一个上传恶意文件的替代方法,即使是在通过Web界面没有上传功能的情况下。
提示
你可以试着向不同的端点发送
OPTIONS
请求,以测试是否有任何支持PUT
方法的端点。
允许用户上传文件是司空见惯的事,只要你采取正确的预防措施,就不一定会有危险。一般来说,保护你自己的网站免受这些漏洞影响的最有效方法是实施以下所有的做法:
根据允许的后缀名白名单来检查文件后缀名,而不是禁止的黑名单。猜测你可能要允许哪些后缀名,要比猜测攻击者可能尝试上传哪些后缀名要容易得多。
确保文件名中不包含任何可能被解释为目录或遍历序列(../
)的子字符串。
重命名上传的文件,以避免可能导致现有文件被覆盖的冲突。
在文件被完全验证之前,不要将它们上传到服务器的永久文件系统。
尽可能地使用一个成熟的框架来预处理文件上传,而不是试图编写你自己的验证机制。
译者注:
[1] 在计算机领域,polyglot是指以多种编程语言或文件格式的有效形式编写的计算机程序或脚本(或其他文件)。一个polyglot文件是由两种或多种不同格式的语法组合而成。当文件格式要被编译或解释为源代码时,可以说该文件是一个polyglot程序,尽管文件格式和源代码语法从根本上说都是字节流,而利用这一共性是开发polyglot程序的关键。polyglot文件在兼容性方面有实际应用,但当被用于绕过验证或利用漏洞时,也会带来安全风险。