fever
fever
发布于 2024-02-15 / 20 阅读
1
0

反序列化漏洞探讨以及CommonsCollections1链子

前情摘要

反序列化漏洞出现的前提条件是什么?
我们首先需要一个入口点,这个入口要能序列化,也就算实现了序列化接口,其次,接收任意对象为参数还有就是重写readObject方法,而重写的readObject里面可能又调用了其他的方法,其他方法可能有危险,这样我们才能控制想要执行的方法。漏洞常依赖​​第三方库的已知Gadget Chain(漏洞触发链,这个调用那个,那个又调用那个...)
cc1链的前置条件是什么?
CommonsCollections的版本小于3.2.1
jdk版本也不能太高,这里我用的是jdk8u65

前置知识

首先我们需要知道CommonsCollections1链子中几个重要的类内容,他们分别是ConstantTransformer invokerTransformer ChainedTransformer TransformedMap

invokerTransformer
79dd78ea7cc11607ab78a92e66b5dd3.png
可以看到图中这个类有一个transform方法,接收传入的参数,通过反射去执行方法,其中this的参数都是通过构造方法传入。那么这个地方就是可以作为我们的危害点,最终点,然后我们需要去向上找,看谁在调用这里

ConstantTransformer
这个类下有这样的方法

        return this.iConstant;
    }
传入什么object就返回什么

ChainedTransformer
76d445f0d13a90809502b644258fb79.png
可以看到这个类的transform方法,先接收一个object,然后处理完后的object又交给下一个iTransformers进行处理,也就是说前一个输出作为后一个输入

零碎的分析

知道InvokerTransformer的transform方法如何使用之后,可以尝试一下命令执行,在idea中直接测以下代码我们可以发现,可以弹出计算器来

 Runtime r =Runtime.getRuntime();
 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

我们现在已经能够通过这个方法进行命令执行,现在需要找到谁调用了这个方法
于是我们找到两个地方比较有希望的,一个是lazymap,一个是TransformedMap。其实cc1链是有两条链的,也就是在这里出现了分叉。
这里只探讨transformedmap这条,大差不差

以下代码都出现在TransformedMap类下

 public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
  protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

我们继续找谁调用了checkSetValue,发现是TransformedMap的父类AbstractInputCheckedMapDecorator,以下是其中的代码

public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return super.entry.setValue(value);
        }

到这里为止,我们的exp是这样

 Runtime r =Runtime.getRuntime();
  InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
       HashMap<Object, Object> map = new HashMap<>();
        map.put("aaa","bbb");
       Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

       for (Map.Entry entry:transformedMap.entrySet()){
           entry.setValue(r);

在进行setvalue的时候,因为TransformedMap类没有这个方法,于是会找他的父类AbstractInputCheckedMapDecorator有setValue方法,在执行的时候会执行checkSetValue方法,从而又回到了我们transform方法进行反射rce
这里只是暂时演示,我们现在的任务是如何去触发setValue,也就是说我们继续找哪里执行了setValue,然后我们一顿找,找到sun>reflect>annotation>AnnotationInvocationHandler类,很巧合的是,这里重写了readObject方法,并且调用了setValue方法,到这里整条链就差不多了。具体exp怎么写,我有空再研究研究。
回顾一下整条链

// 反序列化利用链调用流程
AnnotationInvocationHandler.readObject() 
    → AbstractInputCheckedMapDecorator$MapEntry.setValue() 
        → TransformedMap.checkSetValue() 
            → ChainedTransformer.transform() 
                → InvokerTransformer.transform() 
                    → Method.invoke("Runtime.exec()", "calc")

后记(有关lazymap)

lazymap和transformmap的区别,一个是在AnnotationInvocationHandler的invoke方法中触发利用链(于是构造poc就会用到动态代理),一个是在readobject中触发。
AnnotationInvocationHandler默认实现了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成动态代理后,当对象iswin在进行对象调用时,那么就会调用InvocationHandler.invoke(xx)方法,所以POC的执行流程为map.xx->proxy(Map).invoke->lazymap.get(xx) 就会触发transform方法从而执行恶意代码。


评论