Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to parse the mechanism of Java dynamic class loading

2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

This article will explain in detail how to analyze the mechanism of Java dynamic class loading. The content of the article is of high quality, so the editor shares it for you as a reference. I hope you will have some understanding of the relevant knowledge after reading this article.

Today we'll talk about the dynamic class loading mechanism of Java.

1 Java class loader: ClassLoader

We usually divide the processors of programming languages into interpreters and compilers. An interpreter is a software used to execute a program, which performs operations according to the algorithms in the program code. if the execution software is written in a virtual or machine-like programming language, it is also called a virtual machine. A compiler is a program that converts code from one language to another, usually into a machine language.

Some programming languages mix interpreters with compilers. For example, Java first converts the source code into Java binary code (bytecode) through the compiler, and saves the virtual machine language in a file (usually .class file), then executes this code through the Java virtual machine (JVM) interpreter.

Java is an object-oriented language, and bytecode contains a lot of Class information. During the JVM interpretation, ClassLoader is used to load the Java class, which loads the Class in the Java bytecode into memory. Inside each Class object, there is a classLoader property that identifies which ClassLoader it was loaded by.

Class Class {... Private Class (ClassLoader loader) {/ / Initialize final field for classLoader. The initialization value of non-null / / prevents future JIT optimizations from assuming this final field is null. ClassLoader = loader;}... Private final ClassLoader classLoader;...}

The ClassLoader class is located in java.lang.ClassLoader, which is officially described as follows:

/ * A class loader is an object that is responsible for loading classes. The * class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to * locate or generate data that constitutes a definition for the class. A * typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system. * *. *

The ClassLoader class uses a delegation model to search for * classes and resources. Each instance of ClassLoader has an * associated parent class loader. When requested to find a class or * resource, a ClassLoader instance will delegate the search for the * class or resource to its parent class loader before attempting to find the * class or resource itself. The virtual machine's built-in class loader, * called the "bootstrap class loader", does not itself have a parent but may * serve as the parent of a ClassLoader instance. * *... * * / public abstract class ClassLoader {...} 2 Common ClassLoader

There are several common ClassLoader in JDK: BootstrapClassLoader, ExtensionClassLoader, AppClassLoader, URLClassLoader, ContextClassLoader.

ClassLoader uses the delegation pattern (Parents Delegation Model) to search for classes and resources. Every instance of a ClassLoader class has a parent ClassLoader. When a class needs to be loaded, the ClassLoader instance will delegate the parent ClassLoader to load first, and then load itself if it cannot be loaded. The BootstrapClassLoader built into JVM does not have a parent ClassLoader of its own, and it can be used as a parent to other ClassLoader instances.

BootstrapClassLoader, the boot classloader / root loader, is responsible for loading the JVM runtime core classes, which are located in the JAVA_HOME/lib/rt.jar file, where our commonly used built-in library java.*.* is included. This ClassLoader is special because it is not actually a ClassLoader instance object, but is implemented by C code. When implementing a custom classloader, if you need to delegate the load request to the startup classloader, you can directly pass in null as BootstrapClassLoader.

ExtClassLoader, the extension class loader, is responsible for loading the JVM extension class. The extension jar package is located in JAVA_HOME/lib/ext/*.jar, and the library name usually starts with javax.

AppClassLoader, the application class loader / system class loader, is provided directly to the ClassLoader used by the user. It loads ClASSPATH environment variables or jar packages and directories in the path defined in the java.class.path attribute, and is responsible for loading classes including developer code and third-party libraries.

An implementation of an URLClassLoader,ClassLoader abstract class that can search for classes or resources based on URL and load them remotely. BootstrapClassLoader, ExtClassLoader, AppClassLoader, and so on are subclasses of URLClassLoader.

AppClassLoader can be obtained from the static method getSystemClassLoader () provided by the ClassLoader class, and the class in the developer's code is loaded through AppClassLoader, including the first user class in the main () method.

We can run the following code to view the delegation relationship of ClassLoader:

ClassLoader.getParent () can get the parent class loader for delegation, which usually returns null to represent bootstrap class loader.

Public class JavaClassLoader {public static void main (String [] args) {ClassLoader appClassloader = ClassLoader.getSystemClassLoader (); ClassLoader extensionClassloader = appClassloader.getParent (); System.out.println ("AppClassLoader is" + appClassloader); System.out.println ("The parent of AppClassLoader is" + extensionClassloader); System.out.println ("The parent of ExtensionClassLoader is" + extensionClassloader.getParent ());}}

Execution result:

AppClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2The parent of AppClassLoader is sun.misc.Launcher$ExtClassLoader@5e2de80cThe parent of ExtensionClassLoader is null

ExtensionClassLoader and AppClassLoader are both subclasses of URLClassLoader, and they both load class libraries from the local file system. URLClassLoader can load not only remote class libraries, but also local path class libraries, depending on the address form in the constructor.

The implementation code for the ExtClassLoader and AppClassLoader classes is located in the sun.misc.Launcher class in rt.jar, and Launcher is loaded by BootstrapClassLoader. ExtClassLoader and AppClassLoader are defined as follows:

Static class ExtClassLoader extends URLClassLoader {private static volatile Launcher.ExtClassLoader instance; public static Launcher.ExtClassLoader getExtClassLoader () throws IOException {if (instance = = null) {Class var0 = Launcher.ExtClassLoader.class; synchronized (Launcher.ExtClassLoader.class) {if (instance = = null) {instance = createExtClassLoader ();}} return instance }. Static class AppClassLoader extends URLClassLoader {final URLClassPath ucp = SharedSecrets.getJavaNetAccess (). GetURLClassPath (this); public static ClassLoader getAppClassLoader (final ClassLoader var0) throws IOException {final String var1 = System.getProperty ("java.class.path"); final File [] var2 = var1 = = null? New File [0]: Launcher.getClassPath (var1); return (ClassLoader) AccessController.doPrivileged (new PrivilegedAction () {public Launcher.AppClassLoader run () {URL [] var1x = var1 = = null? New URL [0]: Launcher.pathToURLs (var2); return new Launcher.AppClassLoader (var1x, var0);}}); several important methods of} 3 ClassLoader

There are several important methods in ClassLoader: loadClass (), findClass (), and defineClass (). ClassLoader attempts to locate or generate a Class data, usually by converting the binary name to a file name and then finding the file in the file system.

LoadClass (String classname), whose argument is the fully qualified class name that needs to be loaded, first checks whether the target class has been loaded, looks at the parent loader and recursively calls loadClass (), and if none are found, findClass () is called.

FindClass (), which searches for the location of the class, usually loads a .class bytecode file based on the name or location, gets an array of bytecodes, and then calls defineClass ().

DefineClass (), which converts the bytecode to the java.lang.Class object of JVM.

/ / Welcome to subscribe to my Wechat official account: security engine 4 Class.forName ()

Class.forName () can also be used to dynamically load a specified class. It returns a Class object of the specified class / interface, and if ClassLoader is not specified, it uses BootstrapClassLoader to load the class. The method is defined as follows:

Public static Class forName (String name, boolean initialize, ClassLoader loader) throws ClassNotFoundExceptionpublic static Class forName (String className) throws ClassNotFoundException

Both the Class.forName () and ClassLoader.loadClass () methods can be used to load the target class, but neither supports loading native types, such as int. Class.forName () can load arrays, while ClassLoader.loadClass () cannot.

/ / dynamically load int array Class ia = Class.forName ("[I"); System.out.println (ia); / / output: / / class [IClass ia2 = ClassLoader.getSystemClassLoader () .loadClass ("[I"); / / array type cannot use ClassLoader.loadClass method, error will be reported: / / Exception in thread "main" java.lang.ClassNotFoundException: [I ")

The Class.forName () method is actually implemented by the calling CLassLoader, and the ClassLoader can also be explicitly specified in the parameters when called. A small difference from ClassLoader.loadClass () is that forName () initializes the class by default and executes blocks of static code in the class. By default, ClassLoader.loadClass () does not initialize the class, but loads the class into the JVM virtual machine.

We execute the following test code:

Class Test {static {System.out.println ("/ / This is static code executed");}} public class JavaClassLoader {public static void main (String [] args) throws ClassNotFoundException {ClassLoader appClassloader = ClassLoader.getSystemClassLoader (); System.out.println ("Execute Class.forName:"); Class cl = Class.forName ("Test"); System.out.println (cl); System.out.println ("Execute ClassLoader:") Class cl2 = appClassloader.loadClass ("Test"); System.out.println (cl2);}}

The execution result is as follows, and you can see that when Class.forName (), the static code block is executed:

Execute Class.forName:// This is static code executedclass TestExecute ClassLoader:class Test

/ / Welcome to subscribe to my official Wechat account: security engine

5. Utilization of FastJson private network

Remember when FastJson TemplatesImpl used the chain?

TemplatesImpl.getOutputProperties ()

> TemplatesImpl.newTransformer ()

> TemplatesImpl.getTransletInstance ()

> TemplatesImpl.defineTransletClasses ()

Private void defineTransletClasses () throws TransformerConfigurationException {. _ class [I] = loader.defineClass (_ bytecodes [I]); final Class superClass = _ class [I] .getSuperclass (); / / Check if this is the main class if (superClass.getName (). Equals (ABSTRACT_TRANSLET)) {.}

In principle, this PoC also uses ClassLoader to dynamically load malicious code and pass bytecode directly into the Payload. TransletClassLoader.defineClass () converts the Bytecode bytecode to a Class object. However, there are many restrictions, requiring developers to set additional Feature.SupportNonPublicField when calling parseObject (), which is a less common usage scenario.

In fact, when the FastJson vulnerability was first announced in 2017, we quickly captured a more general-purpose Exploit that took advantage of the org.apache.tomcat.dbcp.dbcp.BasicDataSource class. This Payload does not need to reverse connection, does not require specific code writing, directly passed into the malicious code bytecode to complete the use, and the dependency package tomcat-dbcp is also widely used, is the database driver component of Tomcat. However, there are not many online analysis articles on this PoC, and only genxor has a more complete analysis in this article. PoC is as follows:

{{"x": {"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": {"@ type": "com.sun.org.apache.bcel.internal.util.ClassLoader"}, "driverClassName": "$BCEL$$$l$8b$I$A$..."}}: "x"}

Here the deserialization generates the org.apache.tomcat.dbcp.dbcp2.BasicDataSource object and completes the command execution. Look directly at the utilization chain:

BasicDataSource.getConnection ()

> createDataSource ()

> createConnectionFactory ()

Protected ConnectionFactory createConnectionFactory () throws SQLException {... If (driverClassLoader = = null) {driverFromCCL = Class.forName (driverClassName);} else {driverFromCCL = Class.forName (driverClassName, true, driverClassLoader);}.

After a chain of calls, Class.forName () is called in BasicDataSource.createConnectionFactory (), and ClassLoader can be customized. As mentioned in the previous section, Class.forName () is initialized by default when the class is dynamically loaded, so here the static code snippet is executed during dynamic loading.

So how to implement command execution in the case of controllable classname and classloader?

Next we have to mention the com.sun.org.apache.bcel.internal.util.ClassLoader in this PoC, which is a magical ClassLoader because it extracts Class's bytecode data directly from classname.

Protected Class loadClass (String class_name, boolean resolve) throws ClassNotFoundException {... If (class_name.indexOf ("$BCEL$$") > = 0) clazz = createClass (class_name); else {...} if (clazz! = null) {byte [] bytes = clazz.getBytes (); cl = defineClass (class_name, bytes, 0, bytes.length);} else cl = Class.forName (class_name);. Return cl;} / * * The name contains the special token $$BCEL$$. Everything before that* token is consddered to be a package name. You can encode you own* arguments into the subsequent string. * The default implementation interprets the string as an encoded compressed* Java class, unpacks and decodes it with the Utility.decode () method, and* parses the resulting byte array and returns the resulting JavaClass object.** @ param class_name compressed byte code with "$BCEL$$" in it*/protected JavaClass createClass (String class_name) {.}

If the classname contains $$BCEL$$, the ClassLoader decodes the string after the $$BCEL$$ according to the BCEL encoding as the bytecode of the Class and calls defineClass () to get the Class object.

So we deserialize and deserialize a BasicDataSource object through FastJson, and assign its member variable classloader to a com.sun.org.apache.bcel.internal.util.ClassLoader object and classname to a bytecode encoded by BCEL (assuming the corresponding class is Evil.class). We can write the code that needs to be executed in the static code block of Evil.class.

BCEL encoding and decoding methods:

Import com.sun.org.apache.bcel.internal.classfile.Utility;...String s = Utility.encode (data,true); byte [] bytes = Utility.decode (s, true);... 0x05.1 do you know?

Look back at PoC again.

{{"x": {"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": {"@ type": "com.sun.org.apache.bcel.internal.util.ClassLoader"}, "driverClassName": "$BCEL$$$l$8b$I$A$..."}}: "x"}

Another noteworthy aspect of the PoC structure here is that

First, the {"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource". } this whole paragraph is put on the position of JSON Value, and then there is another layer of "{}" on the outside.

Then the whole Payload is placed in the position of Key in the JSON string.

Why is it so designed?

Because in order to complete the entire utilization chain mentioned earlier, we need to trigger the BasicDataSource.getConnection () method.

As I mentioned in three details of FastJson deserialization exploit, JSON.parse () in FastJson recognizes and calls the setter method of the target class and some getter methods that meet certain conditions, but getConnection () does not meet certain conditions, so normally it is not called during FastJson deserialization.

/ / Welcome to subscribe to my official Wechat account: security engine

The original PoC skillfully made use of the toString () method of the JSONObject object to achieve a breakthrough. JSONObject is a subclass of Map. When you execute toString (), it converts the current class to a string, extracts all the Field in the class, and naturally executes the corresponding getter, is, and other methods.

First of all, in {"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource". } this whole paragraph is surrounded by a layer of {}, and a JSONObject object is deserialized.

Then, put the JSONObject in the location of JSON Key, and when JSON deserializes, FastJson automatically calls the toString () method on JSON Key:

Com.alibaba.fastjson.parser.DefaultJSONParser.parseObjectDefaultJSONParser.java:436if (object.getClass () = = JSONObject.class) {key = (key = = null)? "null": key.toString ();}

This triggers BasicDataSource.getConnection (). The most complete way to write PoC should be:

{{"@ type": "com.alibaba.fastjson.JSONObject", "x": {"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": {"@ type": "com.sun.org.apache.bcel.internal.util.ClassLoader"} "driverClassName": "$$BCEL$$$l$8b$I$A$..."}: "x"}

Of course, if JSON.parseObject () is called in the developer code of the target environment, it won't be so much trouble. Compared to parse (), parseObject () converts the Java object into a JSONObject object, that is, calls JSON.toJSON (), and calls all setter and getter methods during processing.

So for JSON.parseObject (), passing in such a Payload directly can also trigger:

{"@ type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassLoader": {"@ type": "com.sun.org.apache.bcel.internal.util.ClassLoader"}, "driverClassName": "$BCEL$$$l$8b."} 0x05.2 Tips

The BasicDataSource class is in the old version of the tomcat-dbcp package, and the corresponding path is org.apache.tomcat.dbcp.dbcp.BasicDataSource.

For example: 6.0.53, 7.0.81 and other versions. MVN dependencies are written as follows:

Org.apache.tomcat dbcp 6.0.53

After Tomcat 8.0, the package path has changed. When changing to org.apache.tomcat.dbcp.dbcp2.BasicDataSource, you need to pay attention when constructing PoC. MVN dependencies are written as follows:

Org.apache.tomcat tomcat-dbcp 9.0.8 on how to parse the Java dynamic class loading mechanism is shared here, I hope the above content can be of some help to you, can learn more knowledge. If you think the article is good, you can share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report