Spring RCE Vulnerability Analysis: CVE-2022-22965
VMware released a security advisory on March 31 addressing a remote code execution vulnerability in Spring Framework (CVE-2022-22965), which can allow an unauthenticated attacker to write malicious files and execute arbitrary code on a target system running JDK 9 or later.
Read in Chinese→0x01 Vulnerability Overview
Spring Framework is an open-source, lightweight framework for developing J2EE applications.
On March 31, VMware released a security advisory addressing a remote code execution vulnerability in Spring Framework (CVE-2022-22965). In environments running JDK 9 or later, this vulnerability can be exploited without authentication to write malicious files to the target system and remotely execute arbitrary code.
0x02 Affected Scope
Affected component: org.springframework:spring-beans
Affected versions: Spring Framework versions < 5.3.18 and < 5.2.20.RELEASE are affected by this vulnerability
JDK version: JDK>=9
Deployment method: WAR package deployed in TOMCAT
0x03 Vulnerability Principles
After the vulnerability emerged, I studied analyses by experts and discovered that this latest Spring 0-day was not actually an entirely new vulnerability. Instead, it was a bypass of CVE-2010-1622.
CVE-2010-1622 occurs because Spring parameter binding can inject a Java POJO object. This object can be malicious and used to set certain sensitive Tomcat properties, ultimately allowing dangerous operations to be performed by modifying Tomcat’s configuration.
Therefore, the latest vulnerability, CVE-2022-22965, bypasses this restriction. In a sense, Java 9 created a problem for Spring: JDK 9 contains a class that can bypass the blacklist restriction, resulting in this vulnerability. The final exploitation method is also the same as before.
The following sections explain this issue in detail.
0x04 Spring Parameter Binding
First, let us review parameter binding in Spring.
Simply put, Spring MVC can automatically assign values to parameters.
For example, a common way to pass parameters is shown below:``` http://localhost:8080/spring4shell_war/?name=zzz&age=123

### How Parameter Binding Is Implemented
```java
@Controller
public class HelloController {
@GetMapping("/")
public String index(Person person)
{
Person person1 = new Person();
person1.setName(person.getName());
person1.setAge(person.getAge());
return "hello";
}
}
With this approach, the parameter is passed directly as a Person object instead of being retrieved using the previous @RequestParam approach.
public String HelloController( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
The implementation of this parameter-binding method works as follows: if the user submits name=zzz, Spring Framework automatically calls person.setName(‘zzz’) to assign the value. If a submitted parameter corresponds to a public field or method of the Person class, the value from the user’s request is automatically assigned to it.

0x05 Debugging Process
1)After the user’s request is processed by Tomcat, the doDispath method in DispatcherServlet.java, Spring’s main entry point, is called to route and process the HTTP request:
org.springframework.web.servlet.DispatcherServlet#doDispatch

The following method performs the actual data binding:
org.springframework.beans.AbstractPropertyAccessor#setPropertyValues(org.springframework.beans.PropertyValues, boolean, boolean)

For nestedPa = getPropertyAccessorForPropertyPath\(propertyName\);, let us examine what is inside.

The recursive function getPropertyAccessorForPropertyPath is called to obtain the parameter value. It loops through the parameter to check whether it contains “[” “]‘’ or ‘’.”
If one exists, the value is split and assigned to nestedProperty. Mine does not contain any, so it returns -1.

What happens if we switch to another payload: class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
At this point, pos is 5, and the first segment, class, is extracted.

BeanWrapperImpl#getCachedIntrospectionResults().getPropertyDescriptor(propertyName)
This is what I initially saw analyzed in other articles: it searches the cache for the propertyName parameter we entered.

Eventually, execution reaches org.springframework.beans.CachedIntrospectionResults#getPropertyDescriptor.
Here, we can see what can be retrieved. 
Patch Bypass
Red indicates the original patch, while green indicates the current fix.

The original logic was therefore based on a blacklist: beanClass had to either not be Class, or the property name had to not match (classLoader|protectionDomain).
In JDK 8, this was unavailable, so class.classLoader had to be used.
However, the latest CVE-2022-22965 uses class.module.classLoader, thereby bypassing this restriction.
The reason is that, starting with Java 9, Class objects gained a Module property. The Module class also contains a getClassLoader() method, allowing class.module.classLoader to be obtained.
My understanding of this patch is that if one of the properties is ClassLoader or ProtectionDomain, it directly uses continue to skip it.
Exploitation
The essence of this vulnerability is property injection into certain internal objects in Java 9 modules.
The currently known PAYLOAD modifies certain global Tomcat configurations.
Specifically, it changes the log format and filename stored in the context at classLoader.resources.context.
These are shown below:
class.module.classLoader.resources.context.parent.pipeline.first.pattern=xxx& class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp& class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT& class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar& class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
Finally, a webshell is written to the webapps/ROOT directory.
0x06 Pitfalls
Another issue encountered during debugging is that if Tomcat is deployed and debugged from within IDEA, the webshell cannot be found during the final test.
This is because IDEA uses a mapped directory rather than this directory. Therefore, the file is not present when you access the original Tomcat directory; it is actually located in one of IDEA’s directories.

Also, a cache remains after each shell is written. If repeatedly sending the payload fails to write the file, restarting the Tomcat service should resolve the issue.
0x06 References
https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement
Analysis of the Spring MVC Arbitrary Code Execution Vulnerability (CVE-2010-1622) - Ruilin (rui0.cn)