0ctf-2021 buggyLoader题目复现
0x01 前言
鸽了很久的题目,之前就下载附件分析过,但是当时没有学太多java的东西,复现的时候啥都不懂,很难进行下去。现在打算重新捡起这道题,这道java题的质量很高,于是就写下此篇文章记录一下本题复现与分析的过程。
0x02 题目源码分析
题目有源码附件,控制器中只定义了一个路由

显而易见的反序列化入口,有一个if判断,这不成问题,只需要在序列化流添加这个字符串和数字就行。
但是它自己重写了一个输入流,我们跟进看一下

在构造方法中获取了一个 URLClassLoader 的类加载器
在 resolveClass 这个类解析方法中用 URLClassLoader 类加载器 调用 loadClass 方法进行加载。在pom.xml中有 commons-collections 的依赖可以打。
0x03 loadClass无法加载数组类的相关分析
在学习shiro框架的时候,实际上 CC6 是打不了的,至于为什么,我当时也只是浅浅的了解到 shiro框架自己重写了一个序列化器,不能解析数组,然后构造了一条能用的组合的cc链。但是我现在回头看,发现了一个问题

在利用类加载代码执行的cc链中,是有数组类的啊,为什么还能够被加载?
接下来调试源码分析这个问题
翻开shiro框架的源码,shiro使用默认的序列化器——DefaultSerializer 在它的反序列化函数中

重写了一个输入流

是用 ClassUtils 类的 forName 函数进行加载。将断点下载 resolveClass 函数里,开始调试
我们跟进到 forName 方法

它利用当前上下文的类加载器进行加载,跟进 loadClass 方法,现在要加载的就是 HashMap

它又获取了一个 ParallelWebappClassLoader ,它是 tomcat 中使用的,用于加载当前web应用程序的类加载器,继续跟进 loadClass 方法
我也不知道调试出什么问题了,强制步入跟进不到此方法中,全局搜索也搜不到,于是就网上视频里截了一张

知道这个点就行,这也不算安全方面的知识了。有个布尔变量,如果为true,那么就会用父类的Class.forName去加载类,forName 当然是可以加载数组的,因为它是原生的 ObjectInputStream 加载类的方法

false的话就会用findClass来加载类。
就是说,如果要加载的类是JDK内置的类,走的就是父类类加载器的 forName 方法,如果加载的类是 webapp下的类或者是导入的依赖类,就会用WebappClassLoader 类加载器用 findClass 来加载
findClass 方法是加载不了数组类的,这也证实了上面提出的疑问了。
跟进 findClass 方法看看,为什么不能加载数组类

这里会做一个路径的转换,

如果将这个类转换为带路径的class文件的话,显然是加载不到的,因为根本就没有这个路径,这也是加载不到数组类的真正原因。
0x04 题目分析
说了这么多其实就是解释 LoadClass 方法最终会走到 findClass,是加载不了数组类的
题目中直接调用 LoadClass,那么就是所有的数组类包括JDK里面的都加载不了,这也是本题最难的点
我们就可以利用二次反序列化,将受限的反序列化变成不受限的反序列化
因为数组类不能用了,所以 InvokerTransformer 类要调用的方法必须是public,而且能够走到可控的 readObject 方法中的,一般来说,需要利用一个代码分析工具,比如 codeql。但是我不会用,所以本次以复现的角度来分析这道题。

在 findRMIServerJRMP 方法中有可控的反序列化点,继续往上找,看看谁调用了它

获得一个路径,路径中必须存在 /stub/ 这个方法传递的是 JMXServiceURL 实例,从这个实例中调用 getURLPath 方法,获取 urlPath 属性,该属性是通过构造方法写入的。所以我们需要构造一个合理路径的 JMXServiceURL 实例,才能满足条件走到 findRMIServerJRMP 方法里
继续往上跟,看看谁调用了 findRMIServer 方法,最终跟踪到 connect 方法,它是一个public 方法,可以利用 InvokerTransformer 类来调用,链子就接上了。
0x05 思路总结
因为本题目不能加载数组类,所有的CC链都打不了,所以得使用二次反序列化来打一个不受限的反序列化,因为题目中是不出网的,所以需要打入内存马。
首先,魔改CC链的执行的地方,将它走到二次反序列化。
然后再写一个原生的CC链用于打入内存马,将该链base64编码写入 urlPath 属性 属性中。
0x06 题目复现
用于打入内存马的CC链
public class CCExp { |
加载的 Class 文件就是我们编写的 Filter 内存马
序列化写入二进制文件,利用python脚本base64编码。

将编码后的base串,写入path,注意格式,写出最终exp
public class exp { |
启动服务

运行EXP,将payload打入 /basic 路由,得用POST,get传不了这么多数据
看日志没有特殊报错,看来是写进去了

因为打入的是filter内存马,所以任何路由都能够执行命令,而我们执行命令的参数就是cmd


执行命令成功。
0x07 结语
这道题质量还是挺好的,复现完这道题后,理解了loadClass无法加载数组类的真正原因,以及二次反序列化的应用思路。后续还会找一些不错的java题来复现。