漏洞介绍
CVE编号
CVE-2022-22965
影响范围
- JDK >= 9
- 使用Apache Tomcat 作为Servlet容器,并且使用传统的war包部署方法
- Spring Framework 5.3.0 - 5.3.17,5.2.0 - 5.2.19,以及更早的版本,或其他包含
spring-webmvc
orspring-webflux
依赖的应用
漏洞复现
拉取此漏洞的vulhub代码进行复现,我本地环境是jdk11+tomcat8.5.39
漏洞分析
基础知识
Java内省机制
Java内省(Introspector)机制就是JDK提供的一套API来查找、设置JavaBean
的属性,只要有 getter
/setter
方法中的其中一个,那么 Java 的内省机制就会认为存在一个属性,内省的核心类就是Introspector
类。
这里我们新建一个名为Person
的JavaBean
,使用内省的方法来调用Person
类所有属性以及属性的读写方法
1 | public class Person { |
1 | import java.beans.BeanInfo; |
运行结果除了包含Person
类的属性和属性的读写方法之外,另外还包括class
属性以及getClass
方法,这是因为呢?
为什么会有class属性?
查看Introspector.getBeanInfo(Class<?> beanClass)
方法,会将beanClass
传入Introspector
构造方法,并调用Introspector
实例getBeanInfo()
方法
先跟入Introspector
构造方法,stopClass
为空就会获取父类java.lang.Object
的BeanInfo
并赋给superBeanInfo
完成构造方法后调用getBeanInfo()
,getBeanInfo()
方法里面的getTargetMethodInfo()
、getTargetEventInfo()
、getTargetPropertyInfo()
几个方法都会先获取superBeanInfo
中的值并加到自己的BeanInfo
中
因为java.lang.Object
存在一个getClass()
方法,所以内省机制会认为有class
属性。这也就解释了为什么Person
类有class
属性和getClass
方法了。
SpirngBean
SpringBean
可以当成JavaBean
的升级版,由Spring
框架的ApplicationContext
操控SpringBean
,ApplicationContext
也称控制反转(IoC)容器,是Spring
框架的核心。控制反转就是用户将对象转为实例过程,变成了容器生产实例,然后通过实例去注入到对应的对象的过程。
简单的可以将Spring
容器理解为工厂,SpringBean
的生产过程就是我们定义好什么产品(Bean)需要什么样的原材料(Bean中的属性)这样的配置信息,Spring
容器负责将原材料生产(实例化)为产品并存储(Cache)
在SpringBean要使用时,第一步就是从SpringBean的注册表中获取Bean的配置信息,然后根据配置信息实例化Bean,实例化以后的Bean被映射到了Spring容器中,并且被存储在了Bean Cache池中,当应用程序要使用Bean时,会向Bean Cache池发起调用。
参考panda
大佬画的一张图
关键代码分析
根据历史漏洞分析文章,看下通到CachedIntrospectionResults
的调用链,可以看到在getPropertyAccessorForPropertyPath
递归了8次
getPropertyAccessorForPropertyPath
方法根据分隔符.
将传入的字符串分割,并从左往右递归处理嵌套属性(嵌套结构的理解可以参考文章),所以如果我们想通过class去调用classLoader的属性,只需要通过class.classLoader的方式即可
1 | protected AbstractNestablePropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) { |
所以我们可以通过Tomcat Access Log
来写shell。Tomcat Access Log
是通过 server.xml
配置
1 | <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access." suffix=".log" |
根据前面对SpirngBean
和内省机制的理解,通过xml文件加载的配置属性,实际上也是可以通过内省机制修改的,Tomcat具体有哪些属性可以参考官方文档,通过修改下面的几个属性可创建任意后缀名的文件,即可写入一个shell
1 | class.module.classLoader.resources.context.parent.pipeline.first.directory = |
为什么只有 >= jdk9受影响?
此漏洞其实算是CVE-2010-1622
的JDK高版本利用,CVE-2010-1622
的修复增加了class.classLoader
的黑名单限制,而jdk9
以下版本只能通过class.classLoader
利用,pd.getName
为classLoader
时,beanClass
为Class
,即所以没法利用,黑名单判断代码如下
1 | if (Class.class != beanClass || !"classLoader".equals(pd.getName()) && !"protectionDomain".equals(pd.getName())) { |
jdk9
引入了模块系统,可通过class.module.classLoader
使得当pd.getName
为classLoader
时,Class.class != beanClass
,从而不走后面||
判断逻辑导致绕过
补丁分析
踩坑记录
由于本地调试环境问题,导致调试前踩了不少坑,这里记录下
1.CATALINA_BASE
tomcat
默认配置的CATALINA_BASE
和CATALINA_HOME
是同一目录,这两者的区别可参考官网介绍
用idea配置tomcat后,启动时CATALINA_BASE
并没有和CATALINA_HOME
在同一目录,而是在C盘的用户目录下
写入的shell
在CATALINA_BASE
下,而不是tomcat的安装目录CATALINA_HOME
下,这就导致生成的shell访问不到
解决办法
idea中配置tomcat环境变量,指定CATALINA_BASE
为本地tomcat目录,然后重启即可
2.idea配置tomcat端口不生效
为了解决上一个问题,idea配置了CATALINA_BASE
后,idea中不管怎么设置tomcat服务的HTTP port
,运行时始终都是以tomcat默认的8080
端口启动(一直以为是我项目配置问题,这里卡了半天也没整出来,吐了…)
还不清楚具体是什么原因导致的,如果要修改端口只能修改tomcat的server.xml
配置文件,或者直接访问默认的8080
端口
参考
https://xz.aliyun.com/t/11129#toc-13
https://tttang.com/archive/1532/
https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement
https://github.com/vulhub/vulhub/tree/master/base/spring/spring-webmvc/5.3.17
https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22965
- Post title:Spring Framework RCE分析
- Post author:p0melo
- Create time:2022-04-18 01:01:35
- Post link:2022/04/18/Spring-Framework-RCE分析/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.