Spring4Shell(CVE-2022-22965)
リクエストパラメータだけでWebシェルを書き込めた重大RCEの全貌がつかめる。getClassを起点にClassLoaderを操るバインディングの原理と、12年前の緩和が破れた理由まで分かる。
- Springのフォームバインディングでプロパティ名からClassLoaderへ到達できた。JDK 9以降の変更で旧ブラックリストが効かず、Tomcat上でRCEに至るCVSS 9.8の欠陥だ。
- class.module.classLoader経由でTomcatのAccessLogValveを書き換え、JSPを任意パスへ出力する。CVE-2010-1622と同種で、環境の前提変更が旧緩和を破った。
- Spring 5.3.18/5.2.20は許可プロパティ制御を強化しClassLoaderへの到達を遮断。教訓はフレームワークの再帰的リフレクションバインディングの危険と、環境が変われば古い緩和は無効化されうるという再検証の必要性。
何が起きたか(影響範囲・深刻度)
2022年3月末に公表されたSpring4Shell(CVE-2022-22965)は、Spring MVCおよびSpring WebFluxのデータバインディング機構を悪用してリモートコード実行(RCE)に至る欠陥です。CVSS v3.1で9.8(Critical、AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)とされ、認証不要・ネットワーク越しに単一のHTTPリクエストで成立しうる点が深刻でした。名前がLog4Shell(CVE-2021-44228)を連想させ、また同時期に別物のSpring Cloud FunctionのSpEL欠陥(CVE-2022-22963)も出回ったため、初期は情報が混乱しました。両者は無関係で、Spring4Shellは前者を指します。
影響を受けるのは、JDK 9以降で動作し、WARとしてApache Tomcatにデプロイされ、かつリクエストパラメータをJavaBean形式のオブジェクトへバインドするアプリという、複数条件が重なるケースです。埋め込みTomcatを使うSpring Bootの実行可能JARは、代表的な公開エクスプロイトの経路(後述のAccessLogValve)では直接の対象になりにくいものの、原理としての欠陥自体はフレームワーク側にあり、パッチ適用は必須とされました。
脆弱性の原理(なぜ起きるか)
Spring MVCはリクエストパラメータをコマンドオブジェクトへ自動束縛します。内部ではWebDataBinder/BeanWrapperImplが、パラメータ名をプロパティパスとして解釈し、ネストしたセッターを次々にたどります。たとえばaddress.city=Tokyoはobj.getAddress().setCity("Tokyo")に対応します。ここで問題は、あらゆるJavaオブジェクトがObject.getClass()を継承し、Bean記法ではこれが読み取り専用プロパティclassとして露出することです。
つまりパラメータ名class.xxxはターゲットのClassオブジェクトを起点にプロパティチェーンを開始できます。ClassはさらにgetClassLoader()など多数のゲッターを持つため、リクエストパラメータの命名だけで、本来アプリが意図しないオブジェクトグラフの奥深くへ到達できてしまいます。到達できるゲッターの戻り値にセッター可能なプロパティがあれば、外部入力でそれを書き換えられます。
Bean のプロパティ解決は再帰的で、a.b.c を getA().getB().setC(...) に機械的に写像します。ルートに class という出発点がある限り、class.module.classLoader.… のような経路でクラスローダやその設定オブジェクトへ橋渡しできてしまいます。
これは新種の発見ではありません。2010年のCVE-2010-1622が同じclass.classLoader到達を報告しており、Springは対策としてclassLoader/protectionDomainといったプロパティ名をCachedIntrospectionResultsのブラックリストで拒否していました。当時のJDKではClass.getClassLoader()の直後はClassLoaderであり、そこから先に危険な書き込み可能プロパティへ抜ける実用的な経路が乏しく、名前の遮断で概ね塞げていたのです。
エクスプロイトの流れ(概念レベル)
横にスクロール
破綻したのはJDK 9で導入されたモジュールシステム(JPMS)です。ClassにgetModule()が加わり、class.module.classLoaderという新しい迂回路が生まれました。既存のブラックリストはclass.classLoaderという直接名を想定しており、moduleを挟む経路を見落としていたため、緩和をすり抜けてクラスローダへ再到達できるようになったのです。
到達先で狙われた代表例が、TomcatのParallelWebappClassLoaderから辿れるresources.context.parent.pipeline.first、すなわちアクセスログを司るAccessLogValveでした。攻撃者はリクエストパラメータ名として次のようなプロパティパスを与え、ログ設定を外部から書き換えます(値は概念を示すための省略表現です)。
class.module.classLoader.resources.context.parent.pipeline.first.pattern → ログ行の書式
class.module.classLoader.resources.context.parent.pipeline.first.suffix → 出力ファイルの拡張子(.jsp)
class.module.classLoader.resources.context.parent.pipeline.first.directory → 出力ディレクトリ
class.module.classLoader.resources.context.parent.pipeline.first.prefix → ファイル名の接頭辞
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat → 日付部の無効化
patternにJSPスニペットを、suffixを.jspに、directoryをWeb公開領域に設定させると、以降Valveは「アクセスログ」として攻撃者制御のJSPをWebルート配下へ書き出します。あとはそのJSPへリクエストすれば、リクエストパラメータをOSコマンドへ渡すWebシェルとして機能し、RCEが成立します。要は「ログ出力機構をファイル書き込みプリミティブに転用する」構図であり、完全なペイロードはここでは示しません。
修正と対策(パッチが何を変えたか)
Springは2022年3月31日にSpring Framework 5.3.18および5.2.20を公開しました(Spring Boot 2.6.6/2.5.12が同梱)。修正の要点は、ブラックリスト(禁止名の列挙)という破れやすい方式を改め、CachedIntrospectionResultsにおいてバインド許可を絞り込んだことです。具体的には、Class型を返すプロパティ経由での横断を制限し、宣言クラスがjava.lang.Classのプロパティについてはnameなど無害なものだけを残してclassLoader・protectionDomain系への到達を遮断しました。これによりmoduleを挟む迂回路も含めてクラスローダへ橋渡しできなくなります。
恒久対策はパッチ適用です。パッチ前の緩和として @ControllerAdvice で WebDataBinder.setDisallowedFields に class.*・Class.*・*.class.*・*.Class.* を登録し、getClass 起点の横断自体を拒否する方法が案内されました。ただし列挙漏れの危険が残るため、あくまでつなぎと位置づけられました。
| 対象 | CVE | 概要 |
|---|---|---|
| Spring Framework(本記事) | CVE-2022-22965 | データバインディング経由のClassLoader到達によるRCE。JDK 9+/Tomcat/WARが条件 |
| Spring Cloud Function | CVE-2022-22963 | routing式のSpEL評価によるRCE。名前が紛らわしいが完全に別物 |
| Spring Framework 3系(前史) | CVE-2010-1622 | class.classLoaderへの到達。当時はプロパティ名の遮断で緩和 |
教訓(一般化できる原則)
第一に、フレームワークが提供する「便利な自動バインディング」は、外部入力をオブジェクトグラフへ再帰的に流し込む強力な機構であり、意図せぬ到達面(reachability)を生みます。パラメータ名がそのままプロパティパスとして解釈される設計では、ルートにgetClassのような普遍的な出発点がある限り、想定外のオブジェクトへ橋が架かります。バインド対象は明示的な許可リスト(allowlist)で絞り、class系プロパティは既定で締め出すのが安全です。
第二に、古い緩和策は「当時の前提」の上に成り立っており、前提が変われば静かに無効化されます。CVE-2010-1622の対策はJDK 8までのClassの形に依存しており、JDK 9のモジュール導入という無関係な変更が抜け道を開けました。ブラックリスト方式は列挙の網羅性に賭ける方式で、環境進化に弱い。禁止を数え上げるより、許可を絞る(deny by default)ほうが将来の変化に耐えます。
第三に、依存プラットフォームの更新は既存の防御前提を再検証する契機として扱うべきです。ランタイム(JDK)、コンテナ(Tomcat)、フレームワークの版が変わるたび、過去のセキュリティ判断が今も有効かを見直す運用が要ります。到達可能性を最小化する設計思想は、投機的実行を突くSpectre / Meltdownのような別系統の欠陥にも通じる、防御の一般原則です。より広い攻撃面の考え方はセキュリティ、Webアプリの入力処理はWeb、言語ランタイムやリフレクションの背景はプログラミングの各トピックも参照してください。
脆弱性の解剖の記事ガイド
Spring4Shell(CVE-2022-22965)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Spring
比較で見る軸
難易度: advanced / カテゴリ: 脆弱性の解剖 / タグ数: 6
導入後に効く点
class.module.classLoader経由でTomcatのAccessLogValveを書き換え、JSPを任意パスへ出力する。CVE-2010-1622と同種で、環境の前提変更が旧緩和を破った。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 脆弱性の解剖
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Spring / CVE-2022-22965」に近いか確認する。
- 強みである「Springのフォームバインディングでプロパティ名からClassLoaderへ到達できた。JDK 9以降の変更で旧ブラックリストが効かず、Tomcat上でRCEに至るCVSS 9.8の欠陥だ。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。