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中的方式,都是通過類的全名來載入類。其載入過程分為以下三個步驟:
- 裝載:(loading)找到class對應的位元組碼檔案。
- 連線:(linking)將對應的位元組碼檔案讀入到JVM中。
- 初始化:(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某個單獨的屬性。主要方法:
-
getPropertyType(),獲得屬性的Class物件;
-
getReadMethod(),獲得用於讀取屬性值的方法;
-
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的子類。