Spring Core rce漏洞分析(CVE-2022-22965)

漏洞描述:

Springmvc框架参数绑定功能,绑定了请求里的参数造成变量注入,攻击者可以实现任意文件写入,漏洞点spring-beans包中。

漏洞编号:

CVE-2022-22965

影响范围:

JDK>=9

spring < 5.3.18 or spring < 5.2.20

tomcat

前置知识:

ClassLoader:

Class文件是编译好的,可以在jvm虚拟机中直接运行的字节码文件,ClassLoader类加载器负责把class类根据需求,动态地加载到jvm虚拟机中运行。

与反射的区别:

Java中两种加载class到jvm中的方式,都是通过类的全名来加载类。其加载过程分为以下三个步骤:

  1. 装载:(loading)找到class对应的字节码文件。
  2. 连接:(linking)将对应的字节码文件读入到JVM中。
  3. 初始化:(initializing)对class做相应的初始化动作。
  • Class.forName("className");
    调用方式为:Class.forName(className, true, ClassLoader.getCallerClassLoader())

className:需要加载的类的名称。

true:是否对class进行初始化(需要initialize)

classLoader:对应的类加载器

  • ClassLoader.laodClass("className");
    调用方式为:ClassLoader.loadClass(name, false)

name:需要加载的类的名称

false:这个类加载以后是否需要去连接

两种方式的区别是forName()得到的class是已经初始化完成的,loadClass()得到的class是还没有连接的。

BeanWrapper:

BeanWrapper是Spring的JavaBeans结构的中央结果可以,相当于一个用于分析和操作标准JavaBean结构的代理。拥有获取和设置属性值和属性描述符的功能。这里Spring的JavaBean的属性注入特性,也是导致漏洞的根本原因。

PropertyDescriptor:

属性描述器,BeanWrapper通过他可以获取JavaBean某个单独的属性。主要方法:

  1. getPropertyType(),获得属性的Class对象;

  2. getReadMethod(),获得用于读取属性值的方法;

  3. getWriteMethod(),获得用于写入属性值的方法;

public static void setProperty(UserInfo userInfo, String userName) throws Exception {  
        // 获取bean的某个属性的描述符  
        PropertyDescriptor propDesc = new PropertyDescriptor(userName, UserInfo.class);  
        // 获得用于写入属性值的方法  
        Method methodSetUserName = propDesc.getWriteMethod();  
        // 以Key:Value格式写入属性值  
        methodSetUserName.invoke(userInfo, "zhangsan");  
        System.out.println("set userName:" + userInfo.getUserName());  
    }

CachedIntrospectionResults:

专门用于缓存JavaBean的PropertyDescriptor描述信息的类。

环境配置:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.6.3</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <packaging>war</packaging>
   <groupId>com.example</groupId>
   <artifactId>spring4shell-vulnerable-application</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>Spring4Shell Vulnerable Application</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>11</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
         <version>2.6.3</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
         <version>2.6.3</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <version>2.6.3</version>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-tomcat</artifactId>
         <version>2.6.3</version>
         <scope>provided</scope>
      </dependency>
   </dependencies>
   <build>
      <finalName>helloworld</finalName>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.6.3</version>
         </plugin>
      </plugins>
   </build>
</project>

漏洞分析:

漏洞的利用,简而言之就是在你前端提交参数key=value给服务端,由服务端的controller来解析。

Payload分析:

目前已知的漏洞利用条件要求必须使用tomcat,利用tomcat的日志写入模块,我们可以在tomcat的配置文件server.xml中看到该模块的配置参数信息,和Payload一致:

图片

图片

核心利用点就在于调用AccessLogValve来修改tomcat的日志文件,从而实现任意文件写入,详细调用链分析如下。

漏洞调试:

spring以kv格式传入一个参数,会被绑定到其目标对象上去,而这个对象是动态获取的,我们在获取对象的地方打断点:

org.springframework.beans.BeanWrapperImpl#getCachedIntrospectionResults

这个方法会先判断缓存中是否存在目标对象,不存在则新建一个,然后返回这个目标对象,我们看一下目标对象是如何获取到的:

图片

这里的forclass返回当前包装的类,读取Class的内容,读取的时候先从缓存里读,有的话直接返回,没有就新建一个cache加入缓存里。

图片

看返回的cachedIntrospectionResults的值,返回一个属性描述器pd,pd能修改和读取目标对象的值。

图片

断点下在org.springframework.beans.BeanWrapperImpl#getLocalPropertyHandler

图片

用[和.进行分割,分割出的第一个属性为class,保存在nestedProperty中,后续属性保存在nestedPath中。

图片

图片

这里使用了get方法对比class属性是否存在于org.springframework.beans.CachedIntrospectionResults的这个属性描述器中,如存在则获取对应实例,进行后续的参数注入操作。

图片

图片

接下来,我们再发一个payload追踪一下属性描述器propertyDescriptors的值的获取途径:

在getCachedIntrospectionResults中会判断是否有nestedProperty缓存,如果没有就新建一个,如果有就获取其信息。

图片

图片

org.springframework.beans.CachedIntrospectionResults中使用put操作查询本地的类名,这里虽然对属性描述器查询的值做了检查,不过是只限制了class类的classLoader属性,而使用module调用classLoader属性就绕过了这里的检查。

图片

而在获取缓存的时候,会使用java.lang.Object.getClass(),从而获取到class,进而就能利用反射执行所有注入的数据了。

结合这个payload就是:

  • 调用HelloWorld的getClass() 拿到Class对象
  • 通过class对象调用getModule()
  • 通过Module调用getClassLoader()
  • 通过ClassLoader拿resources
  • context是Tomcat的StandardContext
  • parent拿到的是StandardEngine
  • pipeline拿到的是StandardPipeline
  • first拿到的是AccessLogValve
    目前公开的利用链也就是pipeline下的AccessLogValue的利用,这个类用来设置日志存储参数,包括路径、后缀,修改参数即可达到写入任意文件的目的。

补丁分析:

CVE-2010-1622绕过:

这个漏洞本质上是2010年Spring对象绑定漏洞(CVE-2010-1622)补丁的一种绕过,之前漏洞的修复方式是:禁止class对象直接获取classLoader方法,这种补丁当时是没有什么问题的,这也是本漏洞仅出现在jdk9以上版本的原因。

图片

因为在jdk9引入的moudle机制中,其中module.classloader绕过了之前的补丁,这里直接通过class.module的classLoader加载了key:value格式的请求类,从而产生了后续的漏洞利用。

图片

补丁修复:

我们分析一下spring5.3.18版本的修复手法,我们通过diff spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java可以看到:

图片

这里进一步限制了class对象获取classLoader,调用class.Class方法时,只允许调用名称为Name结尾的方法(比如setName、getName)。且加入了对其调用的属性的返回值的判断,不能为classLoader或者classLoader的子类。

posted @ 2022-04-15 11:40  Highness_DragonFly  阅读(630)  评论(0编辑  收藏  举报