Skip to main content

Securing a Java Spring Boot API from broken JSONObject serialization CVE-2023-5072

Escrito por:
0 minutos de leitura

The recent discovery of CVE-2023-5072, a critical vulnerability related to JSONObject serialization, has brought to light significant security concerns for Java developers, particularly those utilizing the Spring Boot framework.

This out-of-memory vulnerability, stemming from a flaw in the way JSONObject handles serialization, can potentially expose Java applications to a range of security threats. 

CVE-2023-5072 represents a challenging scenario where the integrity of data structures during serialization and deserialization processes may be compromised. This is especially crucial for applications built with Spring Boot, which is widely used for its efficiency and ease in developing robust Java applications.

Understanding the nature and implications of such vulnerabilities is essential for maintaining the security and reliability of Java applications in an increasingly complex digital landscape.

In this article, we aim to provide a clear overview of this issue by building our own simple Java API and offer practical steps to mitigate the risks in a Spring Boot API environment.

A Spring Boot Java API vulnerable to JSONObject CVE-2023-5072

To demonstrate the JSON serialization vulnerability in JSONObject we will create a small Java API server using Spring Boot and Maven to handle a POST request at /api/todos, parse a JSON object with an array of To-Do items, and return their count in the HTTP response. 

Let’s begin with step-by-step instructions to build this project.

Step 1: Set up the project

1. Create a New Spring Boot Project:

a. You can use Spring Initializr to generate your project.

b. Choose Maven as the build tool.

c. Add 'Spring Web' as a dependency.

d. Download and unzip the project.

Spring Boot initializer to generate a new Java project with Maven and Spring Boot

2. Open the Project:

a. Open the project in your preferred IDE (like IntelliJ IDEA, Eclipse, or VSCode).

b. Confirm you have the pom.xml file in place with the Spring Boot dependency and a build entry for the spring-boot-maven-plugin.

Step 2: Add Java dependencies

How to manage dependencies in pom.xml?

In a pom.xml file used by Maven, which is a popular Java build tool, dependencies (external libraries or modules your project needs) are specified using a combination of groupId, artifactId, and version. These elements together uniquely identify a specific version of a library in the Maven repositories.

Edit the pom.xml file and ensure you have the Spring Web dependency. Add the JSONObject library dependency. If it's not included in Spring Boot, you can use the JSON library from org.json by adding this dependency:

xml
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20230618</version>
</dependency>

Let's break down what each part means, focusing on the artifactId:

  • groupId: This is generally the reverse domain name of the organization or group that created the library. In our example application, org.json is the groupId, indicating that the library is maintained by the organization json.org.

  • artifactId: This is the name of the actual library or module. It's what you would generally refer to when talking about a library. In our case, the artifactId is json, which means the name of the library we are adding is simply referred to as "json". It's a common practice for the artifactId to reflect the primary functionality or identity of the library.

  • version: This specifies the particular version of the library that you want to use. The version 20230618 in our example is a unique version identifier for the json library. Versioning is crucial for ensuring consistent builds, as newer versions of libraries might introduce changes that are not compatible with your project.

So, in the pom.xml file, the dependency declaration tells Maven to download and include the json library, which is part of the org.json group, and specifically version 20230618, in the project. The build tool Maven will automatically fetch this library from a central repository and make it available for our project to use.

Step 3: Create the Java API controller

This is the part where we add our own Java code to the project and introduce a new API. This new API endpoint defines a POST HTTP request to the /api/todos URI served by the Java application.

  1. Create a New Controller Class:

    1. In your project, navigate to src/main/java/<your-group-id>/<your-artifact-id>/ and create a new directory called controller/. If you followed the Spring Initializr default prompts then this path would be src/main/java/com/example/demo/controller/DemoApplication.java.

    2. Create a new Java class file named TodoController.java.

  2. Add the Controller Code:

    1. Annotate the class with @RestController.

    2. Create a method to handle the POST request.

Following is a full example of the TodoController.java class file:

java
import org.json.JSONObject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TodoController {

    @PostMapping("/api/todos")
    public String handleTodo(@RequestBody String todoData) {
        JSONObject jsonObject = new JSONObject(todoData);
        int count = jsonObject.getJSONArray("todos").length();
        return "Count: " + count;
    }
}

Step 4: Run the server

Let’s run the Java Spring Boot API server. We can do that in one of two primary ways:

  • Use the IDE to run the application, usually by pressing the F5 key.

  • Navigate to the root directory of the project in the terminal and run the command mvn spring-boot:run. This method requires that you have Maven installed in your development environment.

Now we’re ready to test the Java API endpoint and send a POST request. Use a tool like Postman or cURL to send a POST request to http://localhost:8080/api/todos. The body should be a JSON string, for example: {"todos": ["Task 1", "Task 2"]}.

Here is an example of the cURL command that you can use to send the POST request:

sh
curl -X POST http://localhost:8080/api/todos \
-H "Content-Type: application/json" \
-d '{"todos": ["Task 1", "Task 2", "Task 3"]}'

The HTTP response back will then be:

sh
< HTTP/1.1 200 
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 8
< Date: Mon, 27 Nov 2023 10:23:51 GMT
< 
* Connection #0 to host localhost left intact
Count: 3

Test the Java Spring Boot project with Snyk

In the world of Java development, ensuring the security and integrity of your applications is as crucial as their functionality. One effective tool for this purpose is Snyk, a security platform that specializes in identifying and fixing vulnerabilities in project dependencies.

Java developers often prefer methods more aligned with their existing workflows. Let's explore how to test a Java application using Snyk as a Maven plugin.

Integrate Snyk with Your Maven Project

Snyk provides a Maven plugin that you can add to your pom.xml. This plugin allows you to run Snyk tests as part of your Maven build process.

Add the Snyk Maven plugin to your pom.xml:

xml
<plugin>
    <groupId>io.snyk</groupId>
    <artifactId>snyk-maven-plugin</artifactId>
    <version>2.2.0</version>
</plugin>

After setting up the plugin, you'll need to authenticate it with your Snyk account. Sign Up for Snyk if you haven't already, it’s free to create an account on Snyk's website.

Then you can generate a Snyk API token from your Snyk account settings and set it as an environment variable SNYK_TOKEN on your machine or CI/CD environment. To make it available in the CLI for running the Maven Snyk test we can run:

sh
export SNYK_TOKEN=<the Snyk API token goes here>

You may also choose to provide explicit configuration to the Snyk plugin and set the apiToken and command-line arguments in a more specific form. This Snyk Maven plugin setting is added to the <plugin> declaration:

xml
      <configuration>
        <apiToken>${env.SNYK_TOKEN}</apiToken>
        <args>
          <arg>--all-projects</arg>
        </args>
      </configuration>

We can then run the Snyk test:

sh
mvn snyk:test

Oh my, it looks like Snyk found that the new JSONObject dependency we added has some vulnerabilities. Look at the following Maven Snyk test log from the build process:

sh
$ mvn snyk:test

[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- snyk:2.2.0:test (default-cli) @ demo ---
[INFO] Snyk Executable Path: /Users/lirantal/Library/Application Support/Snyk/snyk-macos
[INFO] Snyk CLI Version:     1.1254.0
[INFO] 
[INFO] Testing /Users/lirantal/projects/repos/spring-boot-java-jsonobject-vulnerability...
[INFO] 
[INFO] Tested 37 dependencies for known issues, found 1 issue, 1 vulnerable path.
[INFO] 
[INFO] 
[INFO] Issues to fix by upgrading:
[INFO] 
[INFO]   Upgrade org.json:json@20230618 to org.json:json@20231013 to fix
[INFO]   ✗ Allocation of Resources Without Limits or Throttling [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGJSON-5962464] in org.json:json@20230618
[INFO]     introduced by org.json:json@20230618
[INFO] 
[INFO] 
[INFO] 
[INFO] Organization:      snyk-demo-567
[INFO] Package manager:   maven
[INFO] Target file:       pom.xml
[INFO] Project name:      com.example:demo
[INFO] Open source:       no
[INFO] Project path:      /Users/lirantal/projects/repos/spring-boot-java-jsonobject-vulnerability
[INFO] Licenses:          enabled
[INFO] 
[INFO] 
[ERROR] 
[ERROR] You have reached your monthly limit of 401 private tests for your snyk-demo-567 org.
[ERROR] To learn more about our plans and increase your tests limit visit https://snyk.io/plans.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  12.507 s
[INFO] Finished at: 2023-11-27T14:42:01+02:00
[INFO] ------------------------------------------------------------------------

Brian Vermeer, a Java Champion and a security advocate for Snyk wrote up a detailed blog post on using Snyk with the Maven plugin.

Demonstrate the Out of Memory bug in JSONObject

Snyk found a type of Denial of Service vulnerability in the JSON parser package and has provided more information to look up this security issue in Snyk’s vulnerability database.

There, it shows a proof-of-concept that we can apply to understand why our Java API server is vulnerable. Let’s apply the vulnerable input to the JSON parser to see what happens.

Back to our TodoController.java code, add the following private method which generates a malformed JSON payload:

java
    private static String makeNested(int depth) {
        if (depth == 0) {
            return "{\"a\":1}";
        }

        return "{\"a\":1;\t\0" + makeNested(depth - 1) + ":1}";
    }

Next, update the handleTodo() API route method to use that string as the input to the JSONObject constructor:

java
        String vulnerablePayload = makeNested(30);
        JSONObject jsonData = new JSONObject(vulnerablePayload);

Re-run the Java API server with mvn spring-boot:run and send the cURL POST HTTP request again and watch how the server pauses for a while until a response is returned. During this time that it pauses, a memory leak occurs and more memory is occupied by the JVM until the Java application eventually crashes.

Snyk already told us how to fix the security issue. If you run the Maven snyk:test build once again you’ll see that Snyk mentioned how to fix this Denial of Service security vulnerability by upgrading from version 20230618 to version 20231013:

sh
[INFO] Tested 37 dependencies for known issues, found 1 issue, 1 vulnerable path.
[INFO] 
[INFO] 
[INFO] Issues to fix by upgrading:
[INFO] 
[INFO]   Upgrade org.json:json@20230618 to org.json:json@20231013 to fix
[INFO]   ✗ Allocation of Resources Without Limits or Throttling [High Severity][https://security.snyk.io/vuln/SNYK-JAVA-ORGJSON-5962464] in org.json:json@20230618
[INFO]     introduced by org.json:json@20230618

As such, you should edit the pom.xml file to update the org.json dependency version to 20231013 to fix the security issue.

Follow-up resources to write build and ship secure Java applications

You can refer to the full source code used to build the Java Spring Boot API with the JSONObject dependency in the following repository: https://github.com/snyk-snippets/spring-boot-java-jsonobject-vulnerability

If you hold your Java code to high standards and you’re committed to shipping high quality products I’d suggest reviewing the following resources to learn more about the security aspects of Java applications: