Spring4Shell は、Spring Framework の Spring Core に存在する CVE の名前です。
CVSS 3.x スコアは 9.8 で、この脆弱性は最高リスクレベル(クリティカル)に分類されています。この脆弱性により、攻撃者はリモートから任意のコードを実行し、脆弱性のあるサーバーを制御できます。
インターネット上での Spring Core の普及と Spring4Shell の深刻な影響を考慮し、この脆弱性は専門家によって Log4shell に劣らない影響力があると評価されています。
Spring4Shell は、インターネット上の Spring Framework を使用するすべての Web アプリケーションに影響するわけではなく、次の要素をすべて満たす Web アプリケーションにのみ影響します。
今回セットアップする環境は次の構成です。
Apache Tomcat のインストール
前述のとおり、Kali 2021.4a と Apache Tomcat 9.0.45 を使用しています。Apache Tomcat のインストール方法が分からず、Kali Linux にインストールしたい場合は、このリンク を参照してください。
注: https://mirror.kiu.ac.ug/apache/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz を https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.45/bin/apache-tomcat-9.0.45.tar.gz に置き換えてください。
IDE の選択
プロジェクトのコードを書き、プロジェクトを .war ファイルとしてパッケージ化し、そして非常に重要なデバッグを行うために IDE が必要です。私は Intellij を使用していますが、Eclipse や Netbeans など、Java をサポートする IDE であれば何でも問題ありません。
脆弱性を含むシンプルなプロジェクトの作成
私のプロジェクトは非常にシンプルで、次の構成です。
model の HelloWorld.java

controller の HelloWorldController.java

view の hello.jsp

.war ファイルのビルド
プロジェクトをパッケージ化するには、次のようにします: Build -> Build Artifacts -> helloworld:war -> Build。
ビルドが成功するのを待ちます。すると、プロジェクトに out フォルダーが新たに現れます。./out/artifacts/your_war_name/ に移動すると your_war_name.war ファイルがあります。この .war ファイルが、コンパイルおよびパッケージ化された Web プロジェクト本体であり、Apache Tomcat などの Java Servlet にデプロイできます。
Build Artifacts がグレーアウトしている(Build Artifacts を実行できない)場合は、このプロジェクトに Build Artifacts がまだ設定されていないためです。File -> Project Structure -> Artifacts -> 既存のすべての artifacts を削除 -> Add(+ ボタン)-> Web Application: Exploded -> From Modules... -> OK(Exploded の作成完了)-> Add(+ ボタン)-> Web Application: Archive -> For ‘helloworld:war exploded’ -> OK の順に操作してください。その後、Build Artifacts を再度実行してください。
デプロイとデバッグ設定
デプロイ
.war ファイルを Apache Tomcat にデプロイするには、Apache Tomcat のディレクトリ内の /webapps フォルダーに .war ファイルをコピーするだけです(例: 私の場合は、呼び出しやすいようにリネームした helloworld.war を /opt/tomcat/apache-tomcat-9.0.45/webapps/ にコピーします)。その後、Tomcat サーバーを次の 2 つの方法(Linux の場合)で起動します。
デプロイが完了したら、http://localhost:8080/helloworld にアクセスしてください。
デバッグ設定
Tomcat の(リモート)デバッグを設定するには、次の手順を実行します。
サーバー側:
catalina.sh ファイルを開き、JPDA_ADDRESS パラメータの localhost の値を仮想マシンの IP アドレスに変更します

次のコマンドで Tomcat サーバーを再起動します: /opt/tomcat/apache-tomcat-9.0.45/bin/catalina.sh jpda start。このとき、Tomcat は HTTP サーバー用のポート 8080 に加えて、接続してデバッグするためのポート 8000 も開きます。
注意: このデバッグ手順では、Intellij を Windows 10 で実行し、Tomcat を Kali 仮想マシンで実行しているため JDPA_ADDRESS を変更する必要があります。Intellij と Tomcat を同じマシンにセットアップする場合は、変更する必要はありません。
Intellij 側:
Run -> Edit Configurations... -> Add(+ ボタン)-> Remote JVM Debug の順に選択します
名前を設定 -> Host と Port を先ほど catalina.sh で変更した IP とポートに変更 -> OK -> Shift + F9(デバッグを開始)

まず、デバッグに使用しているプロジェクトを分析します。前述のとおり、このプロジェクトは単純に次の構成です。
以下に例を示します。

アプリケーションは POST リクエストの params から情報を取得し、helloWorld{“person”:”Leo”, “message”:”Hi there”} オブジェクトを生成します。この helloWorld オブジェクトが helloPost メソッドへの入力になります。アプリケーションは上記の処理を行い、ユーザーにそのレスポンスを返します。
POST リクエストの body 内のパラメータから helloWorld オブジェクトへの変換は、完全に Spring によって自動的に行われます。では、Spring はどのように処理しているのでしょうか。また、入力されたパラメータを検証しているのでしょうか。
これはデバッグ中のキャプチャで、body が “class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT” のリクエストを実行した例です(左側(スタックトレース)を (1)、右側を (2) と呼びます):

(1) では注意すべき点をハイライトしています(下から上に読んでください)。Spring は POST リクエストの params から helloWorld オブジェクトに対して applyPropertyValue を実行します。params が単純に person=Leo&message=Hi%20there の場合、Spring はキーワード ‘person’ が helloWorld.person に、‘message’ が helloWolrd.message に対応することを見つけ出します。
しかし、Spring は HTTP リクエスト経由でオブジェクトを送信することも許可しています(HTTP でオブジェクトを送信するという言い方は大げさかもしれませんが、簡単に言うとそういうことです)。person プロパティが string ではなく、Person オブジェクトであり、Person の中に name(string)と age(int)の 2 つのサブプロパティがあると仮定します。この Person オブジェクトの情報をサーバーに送信するには、次のようにします: “person.name=Leo&person.age=23”。
つまり、params の形式は A=X ではなく A.B.C.D… = X になります。A.B.C.D… = X という形式を処理するために、たとえば A.B.C= X というパラメータがある場合、Spring は簡単に言うと、その文字列を getA.getB.setC(X) に変換する処理を行います。
getA が何かは説明しませんが、body が “person.name=Leo&person.age=23” のリクエストを例に説明します。Spring は helloWorld オブジェクトに person プロパティと getPerson メソッドが存在するか探します。存在すれば、Spring は helloWorld.getPerson() を呼び出します。これにより、型が Person のオブジェクトが返されるので、仮に person1 と呼びます。次に Spring は person1 に ‘name’ というプロパティと setName メソッド(‘name’ の後ろが ‘=’ なので)が存在するか探し、存在すれば person1 に対して setName(Leo) を呼び出します。
person.age の場合、Spring は最初から person を探してから age に進むことはせず、以前に取得したオブジェクト(この場合は helloWorld と person1)を再利用します。
上記の手順の後、サーバー上には helloWorld{person:{name:”Leo”, age:23}} オブジェクトが存在することになります(message プロパティはひとまず無視してください)。
では、Spring は各オブジェクトのプロパティ(たとえば helloWorld オブジェクトの ‘person’ プロパティ)をどのように見つけているのでしょうか。