Blind XXE 详解 + Google CTF 一道题目分析

本文首发于Freebuf,文章链接

现在来看有回显的XXE已经很少了,Blind XXE重点在于如何将数据传输出来。以往很多文章通过引入外部服务器或者本地dtd文件,可以实现OOB(out-of-band)信息传递和通过构造dtd从错误信息获取数据。

无论是上面的OOB、还是基于错误的方式,无一例外都需要引入外部DTD文件。而为什么要引入外部DTD文件,很多文章在这里都是稍稍一笔带过。这篇文章将详细分析这两种Blind XXE的原理和为啥需要引入外部DTD文件,最后也发现一些情况不用引入外部DTD文件也能直接做的情况(今年google ctf 就可以)

下面测试就用以下的php代码来测试。

参数实体

XML的DTD可以定义普通实体和参数实体两种实体类型,而这两种类型也可以再分别为内部实体和外部实体。XXE,全称就为XML外部实体注入漏洞。通过外部实体SYSTEM请求本地文件uri,通过某种方式返回本地文件内容就导致了XXE漏洞。声明内部实体和外部实体区别如下
<!ENTITY 实体名 SYSTEM url > //外部实体
<!ENTITY 实体名 实体的值 > //内部实体

Blind XXE 需要使用到DTD约束自定义实体中的参数实体。参数实体是只能在DTD中定义和使用的实体,以 % 为标志定义,定义和使用方法如下

而且参数实体还能嵌套定义,但需要注意的是,内层的定义的参数实体% 需要进行HTML转义,否则会出现解析错误。

Blind XXE

OOB

引入服务器DTD文件

既然外部实体可以通过请求内部文件uri获得内部文件内容,那么这样的话我们可以写两个外部参数实体,第一个用file协议请求本地文件并将内容保存在参数实体中,第二个用http或者ftp协议请求自己的服务器并带上文件内容。

这样可以吗,在这本书《XML Schema, DTD, and Entity Attacks》第10页中明确表示了不行,几乎所有XML解析器都不会解析同级参数实体的内容。

但是在上面我们也展示了参数实体也可以嵌套定义,当两个参数实体不是同一级时。我们尝试调用一下。

如上,我们先调用start参数实体,生成了send参数实体声明。在send参数实体声明中,调用了files参数实体并请求了相关链接。测试发现报错PEReferences forbidden in internal subset in Entity PEReferences 指的是参数实体引用(Parameter Entity Reference),禁止在内部Entity中引用参数实体。

也就是因为这个限制,所以前人就想到,既然内部不行就引用外部的DTD试试。现在在自己的服务器中加入下列DTD文件。

xml.dtd

然后请求的数据为下面(用php协议将发送的数据编码为base64)

成功读到文件!

引用本地DTD文件

如果目标主机的防火墙十分严格,不允许我们请求外网服务器dtd呢?由于XML的广泛使用,其实在各个系统中已经存在了部分DTD文件。按照上面的理论,我们只要是从外部引入DTD文件,并在其中定义一些实体内容就行。

我们先来就看看ubuntu系统自带的/usr/share/yelp/dtd/docbookx.dtd部分内容

它定义了很多参数实体并调用了它。那么其实,我们可以在内部重写一个该dtd文件中含有的参数实体,而此时调用是在外部,这样仍然可以实现

其中,这里已经是三层参数实体嵌套了,第二层嵌套时我们只需要给定义参数实体的%编码,第三层就需要在第二层的基础上将所有%&'" html编码。

我们仔细看一下很好理解,第一个调用的参数实体是%remote,在/usr/share/yelp/dtd/docbookx.dtd文件中调用了%ISOamso;,在ISOamso定义的实体中相继调用了eval、和send。在这里不直接使用两层嵌套的原因是,如果直接用两层仍然会报PEReferences forbidden in internal subset in Entity 错误。

基于报错的Blind XXE

基于报错的原理和OOB类似,OOB通过构造一个带外的url将数据带出,而基于报错是构造一个错误的url并将泄露文件内容放在url中,通过这样的方式返回数据。

所以和OOB的构造方式几乎只有url出不同,其他地方一模一样。

通过引入服务器文件

xml.dtd

通过引入本地文件

重新审视为啥要引用外部文件

按照上面的说法,似乎一定要引用外部文件。在w3关于XML协议中有这样一段话:

In the internal DTD subset, parameter-entity references MUST NOT occur within markup declarations; they may occur where markup declarations can occur. (This does not apply to references that occur in external parameter entities or to the external subset.)

简单翻译一下:在内部DTD集中,参数实体的引用不能存在于标记的声明中。这并不适用于外部的参数实体中。

这意味着,协议本身就必须要求不能在内部的实体声明中引用参数,

Google CTF bnv

在今年的Google CTF 中出了一道Blind XXE 题 bnv,这道题完整WP可以参考这里,我们这里只分析Blind XXE部分。

这题目可以从错误响应中泄露信息。因为题目无法和外界通信,我自己思考和看别人的payload都是通过引入本地DTD文件做得。payload并不复杂,就和我们上面分析的一样

可是我发现,如果我不引用外部DTD文件,直接通过嵌套参数实体,这道题同样可以做出来。

有趣的发现

我发现,虽然W3C协议是不允许在内部的实体声明中引用参数实体,但是很多XML解析器并没有很好的执行这个检查。几乎所有XML解析器能够发现如下这种两层嵌套式的

但是对于三层嵌套参数实体构造的payload有些XML解析器是无法检测出来的,比如我本次测试的两种组合php7.2 + libxml2 2.9.4版本和php5.4 + libxml2 2.9.1都是可以有效利用的

这意味着,不用引用外部dtd也可以实现Blind XXE。

发表评论

电子邮件地址不会被公开。 必填项已用*标注