1. 程式人生 > 其它 >Spring Core漏洞分析(CVE-2022-22965)

Spring Core漏洞分析(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的子類。