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
orspring-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,setPropertyValue
is 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
- Spring Core RCE -Cyber Kendra
- CVE-2010-1622 – rui0 blog
- Apache Tomcat guide
- Spring Official Announcement
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.
GIPHY App Key not set. Please check settings