0x00 背景
前人栽树,后人砍树,不然怎来薪火相传。有意义的卷,能带来进步!
昨天写完《JDK8任意文件写场景下的Fastjson RCE》后,看到Landgrey大佬的文章再次补充了一下SpringBoot使用Accept头触发charset.jar加载的方式,应该是非常之完美的SpringBoot Fat Jar任意文件写的RCE了。
- 触发请求头payload
1
Accept: text/plain, */*; q=0.01
跟到源码中,可以发现,分号后面的键值对就是可以触发Charset.forName的地方
1 | q=0.01 |
其中,0.01会导致执行1
Charset.forName("0.01")
但还是那个观点,覆盖写charsets.jar文件,有两个缺点:
- 文件太大,mac osx版的jdk8u241下,该文件足足3MB大小
- 需要针对目标服务jdk版本准备恶意charsets.jar文件(否则容易影响到正常服务)
这篇文章将根据该思路,探究Charset,提供一种更为稳定的方式,实现RCE。
0x01 JDK8下的Bootstrap和Ext ClassLoader
回顾我的上一篇文章《JDK8任意文件写场景下的Fastjson RCE》,根据类的双亲委派模型,类的加载顺序会先从Bootstrap ClassLoader的加载路径中尝试加载,当找不到该类时,才会选择从下一级的ExtClassLoader的加载路径寻找,以此类推到引发加载的类所在的类加载器为止。
Bootstrap ClassLoader(sun.boot.class.path):
1 | /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/resources.jar |
1 | sun.boot.class.path |
是一个配置变量,通过执行1
System.getProperty("sun.boot.class.path")
可以获取到以上路径列表,该列表即为Bootstrap ClassLoader加载类时,文件的读取路径。
只要在jre/classes目录中写入class和SPI文件,即可在Class.forName的执行中加载到写入的class。
0x02 Charset.forName的原理
跟进Charset.forName的源码
1 | public static Charset forName(String charsetName) { |
这里有三个加载Charset的方法
1 | standardProvider.charsetForName(charsetName) |
跟进其代码,可以发现,前两种加载方式,都是已经内置provider的模式,没有利用的价值,而第三种模式,跟进其代码,很明显是一个SPI加载provider的模式
1 | ClassLoader cl = ClassLoader.getSystemClassLoader(); |
那么,其实我们完全可以编写一个继承了java.nio.charset.spi.CharsetProvider类的恶意provider,通过SPI机制,触发其加载并初始化。
这里面有个重点,因为使用到的是系统类加载器,它对应的是Ext ClassLoader,理论上打包成jar包放到jre/lib/ext内,但由于类加载器缓存的机制,需要重启后才能加载到(备选方案),按照上一节所描述的内容,我们需要把恶意provider的class和SPI相对应的文件放到jre/classes内。
0x03 利用SPI机制 - 编写恶意provider
SPI的原理非常简单,就是打包好的jar包内,需要有一个固定的目录META-INF/services,在其内部创建对应类名字的文件,而文件内容即每行一个类名,为文件名所对应类的实现。很抽象,看具体内容。
因为我们需要创建一个java.nio.charset.spi.CharsetProvider的恶意provider,所以,需要创建一个名为java.nio.charset.spi.CharsetProvider的文件
而恶意provider的类名,我定义为Evil,具体类文件:
1 | import java.io.IOException; |
编译:
1 | javac Evil.class |
当前目录:
1 | . |
但这里有个小缺陷,就是需要分两次上传文件。
0x04 SpringBoot触发RCE
把上一节的Evil.class和SPI文件放到jre/classes目录后,启动一个SpringBoot项目,发送一个请求:
1 | curl -X GET "http://127.0.0.1:8080/" -H "Accept: text/html;Charset=Evil" |
可以看到,恶意class被加载执行了
Other
- Fastjson对应的触发方式:
1 | {"@type":"java.nio.charset.Charset","val":"Evil"} |