A Truesec investigation

Spring4Shell/SpringShell Spring Remote Code Execution Vulnerability: Impact and Response

Spring Framework is a hugely popular Java application framework, often used in self-contained Spring boot applications, but traditionally often deployed as an application in a servlet container such as Tomcat. A critical vulnerability exists in Spring framework for endpoints that uses data binding to bind requests to Java objects (“POJOs”). This has the potential to lead to remote code execution by passing malicious request parameters to the application. There are publicly available exploits for certain conditions and reports of attacks being performed. Truesec believes other malicious payloads with different behavior will be developed in the near future. A patch has been released by Spring

Update: The issue has been assigned CVE-2022-22965
Update: Since the time of writing the initial post, 5.3.18 and 5.2.20 along with a detailed writeup has been published by Spring and is available here!

What You Need to Know About Spring4Shell/SpringShell Vulnerability Right Now

Current payloads seem to focus on dropping and running a jsp file on the host. This is likely not possible if running as a self-contained jar, but also not the only likely payload type. Assume that all Spring applications/paths using Spring DataBinder could be vulnerable. As an example, a RestController method taking a String as a parameter does not seem vulnerable, whereas a RestController method taking a custom class/bean might trigger the dangerous code paths.

Update: An official remediation description has been published, and a CVE assigned.
At the time of writing an official remediation is likely to be imminent or already available. Versions 5.3.18 and 5.2.20 have been released as source and maven packages, but not yet officially acknowledged. The relevant commit looks to fix the issue, but is also slightly complex.

  • Upgrade to 5.3.18 or 5.2.20 (or later versions). See the Spring blog post for more info
    Alternatively (not recommended), patch spring-beans manually (see below). NOTE: Be aware that Truesec has not verified compatibilities for this version and that no official advice regarding these versions exist at the time of writing.
  • If no patch can be applied: If you are using Spring DataBinder in your applications, you could implement a ControllerAdvice or RequestMappingHandlerAdapter denylisting the known dangerous patterns. See section “Mitigation in code” for details.
  • If you have a web application firewall (WAF), consider blocking requests with parameter names matching “class.*“, “Class.*“, “*.class.*” or “*.Class.*“. This is relevant for both query parameters and request bodies (so all request methods including GET and POST).

Patching

See Spring’s announcement for more information. Setting the property spring-framework.version 5.3.18 or 5.2.20 should upgrade to a patched version.

Trying Out the Patch for Spring-Beans

Once an official release note is available, make sure to update versions or rebuild with the latest depending on your project structure (expected to be published on the Spring release notes page and vmware Tanzu security list along with a CVE). Follow instructions from the publisher.
Update: An official release with notes is now available.

If you want to test just the patch for spring-beans you can add the following dependency to your pom file (or corresponding for your build system of choice). Add it to your <dependencies> block.

NOTE: Truesec does not guarantee that this is safe with regards to functionality or mitigation. Initial analysis points to this version working. Also, if you go this route be sure to switch to the official way of managing dependency versions once release notes are available.

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-beans</artifactId>
 <version>5.3.18</version><!-- or 5.2.20 for 5.2 -->
</dependency>

This will update your project’s dependency tree, overwriting the previous dependency on for example 5.3.17. You can use “mvn dependeny:tree” before and after to see the change in versions

Note: Truesec’s initial tests indicate that 5.3.18 fixes the issue for typical payloads as well as other potential dangerous payloads. Further it looks to fix the underlying issue (to a point were certain non-optimal functionality remains, but is likely to not be vulnerable to this vector).

Mitigation in Code

If patching is not sufficient, if you want to wait a while before patching or if you just want to do defense in depth, you can add ControllerAdvice to disallow fiels (or configure the binder locally).
As also recommended by Praetorian among others, adding a ControllerAdvice to the application could mitigate the issue

Update: Spring notes that in certain applications the controller might update the disallowedFields value. Therefore they recommend creating a RequestMappingHandlerAdapter for such cases. See their blog post.

It could look something like this:

package <REPLACE_WITH_PACKAGE>;

import org.springframework.core.annotation.Order;
import org.springframework.core.Ordered;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class BinderControllerAdvice {
 @InitBinder
 public void setAllowedFields(WebDataBinder dataBinder) {
 String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
 dataBinder.setDisallowedFields(denylist);
 }
}

Notes

  • We set the @Ordered value to LOWEST_PRECEDENCE, which may sound unintuitive as it makes the ControllerAdvice run last of the ControllerAdvice. The reason being that a lower priority custom ControllerAdvice might otherwise run later, replacing the deny list. While this is unlikely, it would nullify the protection.
    However, be aware that depending on the complexity of the pipelines and other ControllerAdvice there might be code that modifies orders, pipelines or similar or triggers earlier parsing of data (not verified).
    If that is a risk, make sure to test that the ControlledAdvice gets called. A potential untested workaround could be to add another identical ControllerAdvice with the Ordered value of HIGHEST_PRECEDENCE to further increase the likelihood of the denylist being used.
  • Some examples on the internet lack a package declaration. It is likely that the ControllerAdvice will not get loaded unless located in the same directory or a subdirectory of the SpringApplication. Otherwise it might silently not get loaded.
    Change <REPLACE_WITH_PACKAGE> above with the appropriate package for the file (one in the same directory or below the application).
    For example if your SpringBootapplication is in com.example.demo your BinderControllerAdvice should reside in the same package or in a sub package. This can be done by setting “package com.example.demo;” at the beginning of the BinderControllerAdvice class file.
  • Test how this new ControllerAdvice interacts with potential other existing ControllerAdvice. It could potentially break functionality from other ControllerAdvice.

It is recommended to verify that the ControllerAdvice gets loaded and works as expected.
If you run in DEBUG mode and trigger a potentially vulnerable endpoint that has been mitigated you could see the following. Note that output may vary between versions.

`DEBUG 19849 --- [nio-8080-exec-1] o.springframework.validation.DataBinder : Field [class.module.<SOME_FIELD> has been removed from PropertyValues and will not be bound, because it has not been found in the list of allowed fields``

Testing for the Spring4Shell Vulnerability

A vulnerable app will typically return a HTTP 400 response core on a request like this:

curl http://<host>/<path>?class.module.classLoader.URLs%5B0%5D=0

(idea credit: Randori Attack Team)

A non-vulnerable path would likely return as usual (for example 200). In a vulnerable app the application will likely render a log message on WARN level containing “Could not retrieve URL for class path resource“. The same or similar log messages are not expected to exist for successful exploitations.

Note: The endpoint could of course return 400 for other reasons. The main indicator would be if the endpoint has a different response code without the parameter in question.

Also note that just because one endpoint is not vulnerable does not mean that some other endpoint does not use the functionality and thus introduces the vulnerability.

Some Thoughts on the Spring4Shell/SpringShell Vulnerability

Similar vulnerabilities have been noted as far back as 2010 in the form of CVE-2010-1622 among others. However, mitigations could have stopped working when Java 9 introduced the module system since this made paths/packages be renamed from things such as “class.classLoader…”, to “class.module.classLoader…”. This is likely the reason that Java 9+ has been listed as a requirement in blogs.

It could also be that there are still unmitigated vulnerabilities for Java 8 and older as well, as it is likely that this will be tested extensively in the coming days, weeks and months.

From a secure development perspective, the issue seems to stem from Spring allowing to overwrite any field in logic that is supposed to only write to the application’s data transfer fields. Generally, a solution in Spring might be to focus on allow listing of fields that can be deserialized, as opposed to a recommendation based on deny listing.

Managing Data Transfer Objects

For sensitive applications, Truesec’s general recommendation would be to do custom request object mapping and validation as opposed to using a dynamic bean setter/getter structure. Having custom domain/transfer objects with validation in a class constructor can also make sure that basic data validation has been performed on simple constructs such as Strings being passed on the application. For example, we might accept simple types such as Strings in our application, but map collections of values to custom class constructors that validate fields and combinations of values. Whether that helps for similar vulnerabilities or not depends on the framework, but such a structure is likely to have protected also from the previous Jackson ObjectMapper vulnerabilities.