log4j漏洞的好搭档Spring4Shell
各位,今天来跟大家说一下Spring4Shell这个漏洞,这个漏洞可能大家都已经听过,但是!它其实也有前世今生的,并不是突然的出现。

1. 漏洞的前世今生
1.1 曾经的名字:CVE-2010-1622
这个漏洞在2010年其实就已经出现过一次,那个时候它的名字叫:CVE-2010-1622,而这次的CVE-2022-22965和CVE-2010-1622暴雷的代码块也是在同一个地方。
在这次Spring4Shell漏洞最早被发现是2022年3月31号的 vmware 的一篇博客:CVE-2022-22965: Spring Framework RCE via Data Binding on JDK 9+,在这篇博客当中,介绍了这个漏洞会被触发的前提条件:
These are the prerequisites for the exploit:
- JDK 9 or higher(JDK 9以上版本)
- Apache Tomcat as the Servlet container (servlet容器:tomcat)
- Packaged as WAR (打包类型为WAR包)
- spring-webmvc or spring-webflux dependency (包含Spring-webmvc或者spring-webflux依赖)
1.2 最佳拍档 log4j漏洞
可以说现在Spring + Tomcat + WAR的组合仍然是很多的,又因为前段时间log4j的漏洞很多公司升级JDK到JDK9以上,导致这个漏洞在各个公司遍地开花。

在 vmware 发出这个博客不久,Spring就有了专门的一个页面来跟进这个漏洞:Spring Framework RCE, Early Announcement,在这个页面中更加详细的说明了本次CVE-2022-22965影响到的范围。
接着就是CISA(美国网络安全和基础设施安全局)将这个漏洞添加到其基于“主动利用证据”的已知利用漏洞目录中。这是他们官方发布的消息Spring Releases Security Updates Addressing "Spring4Shell" and Spring Cloud Function Vulnerabilities
再接着时间点来到漏洞爆出的第一个周末,Check Point就提到在4月2号一个周末就检测到了37000次Spring4Shell攻击。
Check Point,为一家软件公司,全称Check Point软件技术有限公司,成立于1993年,总部位于以色列特拉维夫,全球首屈一指的 Internet 安全解决方案供应商。

漏洞利用的地区分布方面,欧洲以20%的高居榜首

最受影响力的行业是软件供应商,其中28%的组织受到漏洞的影响
当然你可以通过这个链接找到Check Point报道的更加详细的信息:16% of organizations worldwide impacted by Spring4Shell Zero-day vulnerability exploitation attempts since outbreak
到现在为止我相信还是有很多的在线服务没有进行修复,因为这个漏洞远不如log4j的那个影响传播广泛。正因为如此我们更需要注意到这个漏洞,它是可以直接通过扫描器被发现的,而在阿里云-2019年上半年Web应用安全报告中提到90%以上攻击流量来源于扫描器。

2. 漏洞成因以及修复
以下内容会用到的项目已经放在github上:spring4shelldemo
聊完了到现在为止这个漏洞的进展,作为一个程序员追本溯源的精神,我们扒一下这个漏洞在代码中干了一些什么,为什么JDK9以上版本才会出现。
但在这之前我们需要知道CVE-2010-1622 成因。
2.1 CVE-2010-1622 成因

在2010年,JDK8的时代已经有了SpringMVC,我们可以通过定义Java bean对象解析用户的请求实现用户提交的参数和类中的参数进行绑定,进行赋值。如下所示:
定义Bean对象,ShoppingCart购物车
package run.runnable.spring4shelldemo.entity;
/**
* @author Asher
* on 2022/4/17 */public class ShoppingCart {
private Integer userId;
private Long total;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
@Override
public String toString() {
return "ShoppingCart{" +
"userId=" + userId +
", total=" + total +
'}';
}
}
然后在Controller中我们写一个方法,用来查询某个用户的购物车中金额价格
package run.runnable.spring4shelldemo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import run.runnable.spring4shelldemo.entity.ShoppingCart;
import java.util.Map;
/**
* @author Asher
* on 2022/4/17 */@Controller
public class ShoppingCartController {
private final static Logger logger = LoggerFactory.getLogger(ShoppingCartController.class);
@RequestMapping(value = "/total", method = RequestMethod.POST)
@ResponseBody
public ShoppingCart total(@RequestParam Map<String, String> requestparams, ShoppingCart shoppingCart) {
String userId = requestparams.get("userId");
logger.info("userId:{}", userId);
//query from DB
Long total = 100L;
shoppingCart.setTotal(total);
return shoppingCart;
}
}
当我们通过postman或者其他工具进行请求的时候,可以通过form表单进行提交,这样在total这个方法当中会自己把userId和ShoppingCart对象进行绑定,注入userId这个值


在上面这个截图可以清楚的看到ShoppingCart中userId属性已经有了对应的value,这是因为在这个自动的过程中Spring会自动发现ShoppingCart对象中的public方法和字段,如果ShoppingCart中出现public的一个字段,就自动绑定,并且允许用户提交请求给他赋值。
正是因为我们的ShoppingCart类中存在:
public void setUserId(Integer userId) {
this.userId = userId;
}
在Spring自动检索后,将我们传递的值绑定在userId上
当你了解到上面的操作再来说漏洞的原因就会更加的理解了,在Java对象中,对象存在对应的类对象,例如ShoppingCart的类对象就是ShoppingCart.class,类对象中有类加载器,这个类加载器负责了Java对象的类的加载流程,而在加载的这一个过程当中JVM要完成3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。