Log4Shell という API セキュリティの課題:ユーザー入力/JNDI/LDAP が生み出すモンスター

Log4Shell – The API Security Challenge

2021/12/16 SecurityBoulevard — 先週に発生した Log4Shell CVE-2021-44228 の脆弱性は、現代のアプリケーションや、相互に接続されたサービス、そして普及した API が、いかにしてセキュリティに大きな課題を引き起こすかを示す顕著な例である。長年にわたり、API の脆弱性を調査してきたセキュリティ・リサーチャーとしては、これは、どのようにして物事が上手くいかなくなるかを示す好例だと言いたい。私は最近、この新たな脅威に対する、私の理解を共有するために、このエクスプロイトの詳細を説明するウェビナーに参加した

背景

ログとは、アプリケーションで発生したイベントを、開発者が記録するための仕組みだ。実際には、開発者がメッセージを1つのファイルに保存し、その確認や、トラブル・シューティング、デバッグを行うためのシンプルな方法である。一般的に見て、ログ・メッセージには2つのタイプがある。1つ目はハードコードされたものだ。たとえば、アプリの CPU 使用量が突然 10% 増加したとする。このイベントのログ・メッセージは固定されていて、”The CPU increased 10%” などとなる。これはハードコードされたものであり、ユーザーからの入力は存在しない。

2つ目のタイプのログ・メッセージは、何らかの形で行われたユーザーの入力を含んでいる。たとえば、ユーザーが Facebook にログインした場合、Facebook API はそのアクティビティを記録し、ログに “User logged in at 7:00 PM” などというメッセージを書き込む。(余談だが、私のハンドル名は Hugo だ。したがって、もしログに Hugo が出てきたら、私があなたのシステムを悪用しようとしていた可能性が高い☺)。重要なことは、ユーザー名がログに送信されたことだ。これはセキュリティの観点からも重要であり、Log4 jの脆弱性では、これが悪用されている。ユーザーの入力がログに入ってしまうことで、攻撃者が Log4Shell を悪用する道が開かれてしまうのだ。

ログに脆弱性がある理由

ログの仕組みを実装するには、たくさんの方法がある。すべてのメッセージを1つのファイルにまとめて、それを手動で読むだけでも良いだろう。それはシンプルな方法だが、複雑なシステムには対応できない。開発者は通常、自分の仕事を容易にするために、ロギング・フレームワークを採用する。どの言語であっても、ログを管理するための、さまざまなフレームワークを見つけ出すことができる。

最も一般的でリッチなロギング・フレームワークの1つが、Apacheフレームワークの一部である Java の Log4j だ。Log4j は、開発者がログを書くときに、それを効率的にしてくれる。ログ・フレームワークは、ログ・レコードの複雑な解析を提供することも多々ある。 そこが、ユーザー入力を含むログ・エントリが、問題になるかもしれないところである。ユーザー入力がコマンドとして、ロギング・フレームワークにより処理されるならば、どうなるだろうか?それはマズイだろう。これが、まさに Log4Shell エクスプロイトで見られる現象である。

Log4j は、開発者の時間を節約して、その日々を楽にするための、数多くの機能を提供している。それらの機能の1つが、Lookups と呼ばれるものだ。これは、開発者がログに変数を挿入することを可能にするという、ハデな機能のことである。ログのある部分は一定であっても、ある部分は動的だ。たとえば開発者が、ログ・メッセージに現在の時刻を書き込もうとする場合、その動的な値は、コードが実行される時刻に依存する。したがって、開発者は Lookup を使って、現在の時刻などの変数をログに書き込んでいく。Lookups は、Log4j のテンプレート言語だと考えることができる。

Log4Shell

この脆弱性は、Alibaba Cloud チームの研究者により 2021年11月24日に報告され、Apache により 2021年12月9日に公開された。そして、この脆弱性が、数億台 (潜在的には数十億台) のデバイスに影響を与えるため、大規模な問題となっている。現時点では、影響の爆破半径は 100% 不明である。

この脆弱性はあまり目立たない。3種類の構成要素が、この脆弱性の悪用においては、それぞれの役割を果たしており、攻撃者が1つの HTTP コールを使用するだけで、Log4j を実行しているサーバー上でリモートコードを実行する方法が生じる。それでは、この非常に深刻な脆弱性をもたらしている、それぞれの構成要素を探っていこう。

Log4j と Lookups

Log4j は、開発者の時間を節約し、日々の作業を効率化する機能を、開発者に提供している。 これらの機能の1つに、Lookups と呼ばれるものがある。 これは、変数をログに挿入できるようにする優れた機能である。 ログの一部は一定だが、一部は動的だ。 たとえば、開発者が現在の時刻をログメッセージに書き込みたい場合、その動的な値はコードの実行時間に応じて異なるものになる。 開発者は Lookups を用いて、そのときの時刻などの変数をログに入れる。つまり、Lookups は Log4j のテンプレート言語だと考えることができる。

あるコードでは、開発者が、「問題が発生した」というエラー・メッセージと、Log4jが 動作している Docker コンテナの ID をログに書き込むことができる。ログ・メッセージの解析の一部として、Log4j は入力: ${docker:containerID} をコンテナ GUID に変換し、それがログに書き込まれる。このシナリオでは、Lookups は非常にクールであり、一貫したログ・エントリを提供するだけではなく、開発者がコーディングするときの時間を節約する。

Lookups 自体が問題だというわけではない。問題は、ユーザーがログに Lookup を挿入する機会がある場合だ。エンドユーザーが Lookup を使用して、ログに奇妙なエントリを書き込む方法を紹介する。ユーザーは、奇妙なユーザー名 ${java:os} を使ってウェブサイトにログインしようとするが、実際には Log4j の Lookup である。ユーザー名が存在しないので、API は、このアクティビティに対して “Login failed at 8:00 pm” という文字列と、ユーザー名をログに記録する。解析プロセスの間、Log4j ライブラリは、この入力を Lookup として扱い、変数の実際の値である “Windows Server 2008” に変換します。ライブラリは Java の関数 “java:os “を実行し、返された文字列を取得してログに保存する。このユーザー名からサーバー名へと向けられた解析は、悪意のものではなくても巧妙だ。攻撃者は、設計上公開すべきではない Java 関数にアクセスできる。

JNDI と Lookups

Lookups メカニズムは、様々な機能やプロトコルをサポートしている。最も興味深い (そして危険な) メカニズムの1つは、Java naming and directory interface (JNDI) である。このプロトコルは、Log4j フレームワークが Java オブジェクトをロードすることを可能にする。

攻撃者は、ユーザー名を送るのではなく、ファイル名を送るだけでよいのだ。そして、Log4j ライブラリは、JNDI を使用してローカル環境でこのファイルを見つけ、それを実行しようとする。これは良くないことだ。ユーザーとしては、サーバー上の Java ファイルを実行することは不可能なはずだ。ただし、それはサーバー上のローカル・ファイルに過ぎないので、極めて悪いということではない。

Local 対 LDAP

ここからが怖いところである。JNDI は、Java ファイルを取得するための、さまざまなプロトコルをサポートしている。その1つが LDAP であり、それにより、悪いシナリオが極悪なシナリオへと変化する。LDAP により、リモート・ロケーションからファイルを取得できる。JNDI で LDAP を使用する場合、Log4j ライブラリはリモートの LDAP サーバーから Java ファイルを読み込む。

つまり、ユーザー入力/JNDI Lookups/LDAP の組み合わせは、愛の三角関係を生み出す。そして、この三角関係がモンスターを生み出してしまった。それが、先週に私たちが目にした、Log4Shell ペイロードである。

Log4Shell のペイロード – 攻撃の流れ

攻撃者は通常、ペイロードを使用します。ドル記号と大括弧「${xxxxx}」は、Lookups をトリガーする。この Lookups の中で、攻撃者は JNDI-with-LDAP の組み合わせを呼び出し、Evil.com からリモートの Java ファイルを読み込む。

evil.com サーバーは、”malicious_Java”と呼ばれるファイルを保存している。犠牲者のサーバが脆弱な場合、Evil.com からリモートの Java オブジェクトをダウンロードして実行してしまる。これにより、攻撃者は被害者のサーバー上で、あらゆる Java コードを実行できるようになる。

TLDR: ゲームオーバーだ。

あなたのシステムは、攻撃者は完全にアクセスを許し、単にシステムをシャットダウンするだけではなく、リモートシェルを使用してサーバー上の全情報を抽出することも可能であり、暗号通貨のマイニングも自由である。つまり、をやりたい放題だ。

Log4Shell から身を守るには?

簡単な答えは、Log4j の最新バージョンにアップデートすることだ。しかし、すべてのプロダクション・システムに迅速にパッチを当てるのは、本当に難しいことかもしれない。それまでの間、リスクを軽減するためにできる、基本的な手順を紹介する。

  1. システムとログの綿密な監視を開始する (そして攻撃を受けていると想定する) 。
  2. JVM の設定を更新して Lookups の無効化を検討する:「log4j2.formatMsgNoLookups;」。
  3. 悪意のトラフィックを検出し、ブロックできるソリューションを探す。多くのソリューションが無料で提供されている

最後に考えたこと

これは、有史以来、インターネット上に起こった最悪の事態かもしれない。いまの問題は、攻撃者が高いモチベーションを持ち、この脆弱性を利用しようと競っていることだ。

この脆弱性は、あなたの環境の片隅にある、マイナーな言語に限定されていないことを覚えておいてほしい。Java はユビキタスであり、同様に Log4j も随所で利用されている。ルーターや、コンテナ管理コンポーネント、その他のインフラツールなどの、Log4j が使われているあらゆる場所で、この問題が発生することになるだろう。

私たちは、この問題を把握し、その結果に対処し始めたばかりである。いまこそ、私たち全員が一丸となって攻撃を検知/遮断し、システムに早急なパッチを適用する時である。

ログのハンドリングを効果的に行うための Log4j が、ログシステムを脆弱にして、そこに JNDI と LDAP が加わることで、今回の Log4Sell が発生していると、わかりやすく説明してくれる記事ですね。訳してみて、さまざまな情報の位置関係がハッキリしてきました。筆者の Inon Shkedy さんに感謝です。→ Log4j まとめページ

%d bloggers like this: