リクルートテクノロジーズ メンバーズブログ  Spring Data Commonsにおける任意コード実行の脆弱性(CVE-2018-1273)

Spring Data Commonsにおける任意コード実行の脆弱性(CVE-2018-1273)

サイバーセキュリティエンジニアリング部の藤村です。 4月にリクルートテクノロジーズに入社し、脆弱性検査を業務で担当しています。セキュリティ業務は未経験で入社しましたが、今回は自己紹介と検査学習を兼ねて先日発生した脆弱性について記事を書きました。

先日、Spring Data Commonsにおけるリモートで任意のコードが実行される脆弱性(CVE-2018-1273)が公表されました。この記事では、Spring Data Commonsにおいて実際にリモートでコードが実行されるまでのプロセスをコードベースで解説します。

要約

  • Spring Frameworkで採用されているSpring Data Commonsで任意コード実行の脆弱性(CVE-2018-1273)が公表されました。
  • Spring FrameworkはGitHubのframeworkタグが付いているProjectの中でもTop10に入るほどの人気のWeb Application Frameworkです。
  • Spring Frameworkには、全てのSpring製品で共通の記述をすることができる、Spring Expression Language(略してSpEL)という仕組みがあります。
  • Javaにはリフレクションと呼ばれる、プログラムの実行中に文字列などから動的に処理を行うことができる機能があります。SpELを評価する際に内部的にリフレクションの仕組みが利用されます。
  • Spring Data Commonsでは、特定のリクエストをSpELとして評価させることが可能であり、SpELからリフレクションを悪用することで内部的に文字列をJavaクラスに変換し実行することで、任意コード実行の脆弱性となりました。
  • 初期の対策では、特定のリクエストからのSpELではリフレクションが出来ないように修正されましたが、SpELが実行できることは変わらず、SpELで記述することができる正規表現によって別の脆弱性(ReDoS)が生まれました。

脆弱性(CVE-2018-1273)とは


2018年4月10日 (現地時間) 、Pivotal Software は、Spring Data Commons に関する複数の脆弱性情報を公開しました。公開された情報によると、Spring Data Commons には複数の脆弱性があり、脆弱性を悪用されると、実行しているアプリケーションサーバの実行権限で、リモートから任意の OS コマンドが実行されるなどの可能性があります。詳細は、Pivotal Software からの情報を参照してください。

引用: Spring Data Commons の脆弱性に関する注意喚起

Spring Data Commonsにおけるリモートで任意のコードが実行される脆弱性はSpring Frameworkによって作られたWebアプリケーションに対して特殊な細工をしたパラメータを送るとサーバ上で任意のコマンドを実行することができるというものです。

リモートから任意のOSコマンドが実行できる脆弱性は一般的にRCE(Remote Code Execution)と呼称され、非常に危険度が高いものになります。過去に、Apache Strutsなどでも同様の脆弱性が発生しニュースなどにも取り上げられておりました。

この脆弱性についての詳細はPivotalから報告されており、影響を受けるとされているバージョンは以下の様になっております。

・Spring Data Commons 1.13 to 1.13.10 (Ingalls SR10)
・Spring Data REST 2.6 to 2.6.10 (Ingalls SR10)
・Spring Data Commons 2.0 to 2.0.5 (Kay SR5)
・Spring Data REST 3.0 to 3.0.5 (Kay SR5)

Spring Data Commonsとは

Spring Data Commonsは、Spring Dataのコア部分を担っており、Spring DataはMVCフレームワークのモデル(M)に相当するライブラリです。このライブラリはSpring Frameworkを用いてRESTAPIなどを開発するアプリケーションで広く利用されているライブラリです。

 

検証

プロジェクトの構成

実際に今回発生したSpring Data CommonsのPoC(攻撃が可能なことを実証したコード。Proof of Conceptと呼ばれる)を動作させるために、実際にSpring Frameworkを導入し、環境を構築します。

今回、利用するライブラリはspring-boot:1.5.12とspring-data-commons:1.13.10を利用しております。
以下のようなPoCを動作させるために必要なものを構築し、検証を進めていきます。

ディレクトリ構成
└── src
   └── main
       └── java
           └── com
               └── example
                   ├── VulnApplication.java
                   └── VulnerableController.java

Springは http://localhost/account?name=masahiro の様なAccountモデルのリクエストパラメータを受け取るAPIを作る際には、Controllerクラスでpathの宣言と、内部に取得したいパラメータのgetterをinterface型やClass型で宣言する必要があります。後述しますが、この脆弱性はClass型で宣言した場合には動作せず、Interface型で宣言した場合に動作することが確認できています。

脆弱性の分析

0. PoCを動かして見る

初めにこのアプリケーションの正常な動作から確認をしてきます。このWebアプリケーションはhttp://example.com/account?name=john_doe といったリクエストを受け取った際に、nameのパラメーターであるjohn_doeの文字列が標準出力されます。

図1正常な動作

それでは実際にPoCをlocalhostで動作させてみましょう。アプリの内部の挙動を把握できるようにするために、Javaのデバッグができる環境としてIntelliJ IDEAを利用しています。

これを動作させると図2のような結果になります。 図2では、terminalでPoCを実行し、背面にアプリケーションのログを表示しています。実際にlsコマンドなどで確認すると/tmp/poofが作成されていることがわかります。アプリケーションとしてはExceptionで終了して500 Internal Server Errorを返してしまっていますが、PoCの実行完了には影響しません。

図2 PoCの動作画面

先ほどのリクエストは内部的にRuntimeクラスのインスタンスを呼び出してRuntimeからexec関数を実行しています。 次はどのようにしてリクエストパラメータがロードされてRCEに繋がるか確認していきましょう。

脆弱性を検証する際にPoCの動作箇所を特定することができればブレークポイントなどを利用したトレースが行えます。今回実行したPoCでは、Runtimeクラスを実行しているためブレークポイントを設定しにくくなっています。そこで、ブレークポイントを設定したテストクラスを作成し、それを呼び出すようにPoCを変更したいと思います。

リクエストするパラメータはRuntimeのクラスを呼び出すのではなくRCETestクラスを呼び出してtest()メソッドを実行します。

それでは実際に動作させてスタックトレースを見ましょう。

図3 スタックトレース

のスタックトレースをいくつかのレイヤーに分けて見ていきます。図4では、以下のように3つのパートに分けて例示しています。

  1. SpELがクラスへ変換し実行されるまで
    2. リクエストがSpELとして評価されるまで
    3. 脆弱性が動作するハンドラーが選択されるまで

図4

解説は実際にRCEが動作する部分から検証を行い、その後どの様にして危険な文字列が入り込んだのかを検証していきたいと思います。

1. SpELがクラスへ変換し実行されるまで

初めにリクエストパラメータとして渡された文字列がSpELとして評価された後に、どの様にしてクラスとして評価されるのかを確認していきます。

SpelExpressionクラス

まずは実際に「#this.getClass().forName(‘com.example.RCETest’).test()」という文字列がspring内で動作する部分から見ていきましょう。図4に1で示す赤枠で囲まれた部分になります。

ソースコードを見る前にPoCからどの様に動いているか予測します。まず、先頭についている「#this」これは標準のjavaでは動作せずSpringの中で利用されているSpEL(Spring Expression Language)と呼ばれるもので動作します。

7.5.10 Variables/ The #this and #root variables

PoCからこの攻撃はSpELを経由して動作していることがわかりますので、はじめにSpELのパッケージを利用しているところから脆弱性を追いかけていきます。初めは図4の赤枠1のスタックトレースについて下から見ていきましょう。

下図は

のスタックトレースです。

図5 SpelExpressionのスタックトレース

図5の右下のVariablesを見るとSpelExpressionのexpressionメンバ変数(String型)の中にはname[#this.getClass().forName(‘com.example.RCETest’).test()]がして代入されており、ソースコードのsetValueメソッドの第二引数のvaluesにはtestが代入されていることがわかります。

これはVulnControllerが「/account」へのリクエストパラメータとしてname[#this.getClass().forName(‘com.example.RCETest’).test()]=testをKey/Value型の文字列として評価し、SpELに入力しているからです。 まだこの段階ではexpressionが文字列型なので、#this.getClass().forName(‘com.example.RCETest’).test()はプログラムとして認識されていません。

このメソッドの最後で、expression変数を別のクラスで作り直しthis.ast.setValueを用いてast(CompoundExpressionクラス)に代入しています。

CompoundExpressionクラス

次に先ほどCompoundExpressionクラスに代入された#this.getClass().forName(‘com.example.RCETest’).test()文字列を追いかけていきましょう。

図6 CompoundExpressionクラス

図6はCompoundExpressionクラス内の処理です。setValueメソッドの内部処理として、図6上部のgetValueRefメソッドが呼ばれており、その中で#this.getClass().forName(‘com.example.RCETest’).test()文字列が評価されていき 図6 右下のVariablesにある通り、valueの中に#this.getClass().forName(‘com.example.RCETest’)で呼び出されたcom.example.RCETestクラスが格納されていることがわかります。実際にcom.example.RCETestクラスを生成しているのは51行目のgetValueInternalメソッドです。このメソッドは並行してクラスだけではなく.test()の部分も評価しています。

ReflectiveMethodExecutorクラス

いかにも動的にメソッドを実行しそうなクラス名なReflectiveMethodExecutorクラスがSpring Frameworkとして最後に動作しています。

図7 ReflectiveMethodExecutorクラス

図7 右下のvariablesのthis.methodを見るとRCETestクラスのtestメソッドがロードされていることがわかります。

以上が、PoCの文字列をSpELに入力してから実行されるまでの流れです。次はSpELにPoCのリクエストが流れるまでを確認していきます。

2. リクエストがSpELとして評価されるまで

次に図4に2で示したピンク枠のSpELにリクエストが入る処理を確認していきます。 DataBinderはリクエストを受け取るHandlerからデータを処理するクラスまでのデータの転送を行います。実際にデータを渡している処理はMapDataBinderのsetPropertyValue処理になります。

MapDataBinderクラス