Log4Shell(CVE-2021-44228)
ログに一行書かれただけでサーバーが乗っ取られた事件から、ログ機能がなぜ任意コード実行になるのか、推移的依存の恐ろしさ、複数回に及んだ段階的修正までを一気に理解できる。
- CVE-2021-44228はLog4j 2のCVSS 10.0の欠陥。攻撃文字列がログへ入るだけでメッセージ検索がJNDIを起動し、LDAPからクラスを読み込み任意コード実行に至る。
- 根因はログ出力時にメッセージ本文中の ${...} を再帰展開する設計と、JNDIがリモートのオブジェクトファクトリを取得・実行できる仕様の組み合わせ。User-Agentや検索欄など、信頼できない入力がログに載る全経路が攻撃面になった。
- 2.15.0はJNDI検索を既定無効化、2.16.0はメッセージ検索を削除、2.17.0は別のDoSを修正した。教訓は不審な入力をログで解釈せず、推移的依存をSBOMで可視化すること。
何が起きたか(影響範囲・深刻度)
2021年12月9日、Javaのデファクト標準ロギングライブラリ Apache Log4j 2 に、極めて深刻なリモートコード実行(RCE)脆弱性が公になりました。CVE-2021-44228、通称 Log4Shell です。CVSS v3.1のスコアは最大値の 10.0(AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)で、ネットワーク越しに、認証もユーザー操作も不要で、攻撃条件が低く、しかも影響がスコープ外のシステムにまで及ぶ(Scope: Changed)という、ほぼ全項目が最悪の組み合わせでした。
深刻さの本質は「攻撃対象が広すぎる」点にあります。攻撃者が制御できる文字列を、脆弱なアプリケーションが Log4j でログに書き出しさえすれば成立するため、HTTPの User-Agent ヘッダ、認証フォームのユーザー名、検索クエリ、チャットのメッセージ本文など、値がログに載るあらゆる入力経路が攻撃面になりました。Log4j はJavaエコシステムのGradle/Maven依存の奥深くに広く埋め込まれており、多くの組織は自分が Log4j を使っている自覚すらないまま影響を受けました。公開直後から大規模なスキャンと悪用が観測され、緊急対応が世界規模で発生しました。
脆弱性の原理(なぜ起きるか)
引き金は独立した2つの機能が連鎖することにあります。
第一に、Log4j 2 のメッセージルックアップ置換です。ログに出力される文字列の中に ${...} という記法があると、Log4j はそれをプレースホルダとして解釈し、値に展開します。本来は ${env:HOME} や ${sys:user.name} のように環境変数やシステムプロパティを埋め込む便利機能でした。問題は、この展開がログメッセージの本文に対しても行われた点です。つまり、外部から来た文字列をそのままログに渡すと、その中の ${...} が実行時に評価されてしまいます。
第二に、その展開先として jndi ルックアップが使えたことです。JNDI(Java Naming and Directory Interface)は名前からオブジェクトを解決するAPIで、バックエンドとしてLDAPやRMIなどを選べます。攻撃者が jndi:ldap 形式のURLをルックアップに渡すと、JavaプロセスがそのLDAPサーバーへ問い合わせに行きます。ここでLDAPサーバーは、オブジェクトの実体ではなく「このクラスをこのHTTP URLから取得せよ」という参照(javaNamingReference 相当のディレクトリエントリ)を返すことができます。古いJavaランタイムやLog4jの既定設定では、この参照に従ってリモートのクラスファイルをダウンロードし、ロードし、初期化時にコードを実行してしまいました。リモートクラスロードです。
整理すると、信頼できない入力 → ログ出力 → ${...}展開 → JNDIルックアップ → LDAPが悪性クラスの参照を返す → リモートからクラス取得 → 実行 という連鎖になります。設計上どれも単体では意図された機能でしたが、「ログに書くだけ」の行為が「攻撃者の指定したサーバーからコードを取ってきて実行する」ことと等価になってしまったのが致命的でした。
多くの開発者はログ出力を副作用のない安全な操作だと考えていました。しかし本件では、ログライブラリがメッセージ本文を式として再帰的に評価したため、ログ出力そのものがコード実行のトリガになりました。信頼できない入力を、解釈・評価する能力を持つコンポーネントへ渡すこと自体が危険なのです。
エクスプロイトの流れ
横にスクロール
実際の悪用は概念的には次の手順で進みます(そのまま使えるペイロードは示しません)。
- 攻撃者が自分の制御下にLDAPサーバーと、悪性クラスを配信するHTTPサーバーを用意する。
${jndi:ldap:...}の形をした文字列を、標的アプリがログに記録しそうな入力欄に送り込む。典型的にはUser-AgentなどのHTTPヘッダに埋める。- アプリがその文字列をログに書くと、Log4j が
${...}を評価し、JNDIが攻撃者のLDAPサーバーへ接続する。 - LDAPサーバーがリモートクラスへの参照を返し、脆弱なJavaプロセスがそのクラスを取得・ロード・実行する。
攻撃文字列は ${lower:j} のようなネストしたルックアップで文字を組み立てて難読化でき、単純な文字列マッチのWAFルールを回避できました。だからこそ入力フィルタだけでは守り切れず、ライブラリ側の修正が本質的な対策になります。フィルタは時間稼ぎにはなっても、根本解決にはなりません。
なお com.sun.jndi.ldap.object.trustURLCodebase などの既定値によって、リモートコードベースからのクラスロードは比較的新しいJDKでは既に制限されていました。しかしその場合でもJNDIルックアップ自体は発生するため、外部への接続を用いた探知(コールバック)や、ローカルのクラスパスに存在するクラスを悪用する亜種(ガジェットチェーン)など、影響を完全には避けられませんでした。「JDKが新しいから安全」という判断は誤りでした。
修正と対策
Apacheの修正は一度で終わらず、段階的に進みました。この経緯自体が重要な教訓です。
| バージョン | 対応内容 | 残った問題 |
|---|---|---|
| 2.14.1 以前 | 脆弱。メッセージルックアップとJNDIが有効 | CVE-2021-44228(RCE, CVSS 10.0) |
| 2.15.0 | JNDIルックアップを既定で無効化、接続先をlocalhost等に制限 | 特定設定でなおJNDI悪用の余地(CVE-2021-45046) |
| 2.16.0 | メッセージルックアップ機能そのものを削除、JNDIを既定無効 | 再帰ルックアップによるDoS(CVE-2021-45105) |
| 2.17.0 | 自己参照ルックアップの無限再帰を防止 | この系統の主要問題を解消 |
2.15.0 はJNDIルックアップをオプトインに変えましたが、一部のロギング設定では回避経路が残り、追加のCVE-2021-45046(当初は低評価、後にRCE可能性ありと再評価)が割り当てられました。2.16.0 は問題の根であるメッセージルックアップ機能を丸ごと削除し、より堅牢になりました。その後、細工した入力で自己参照的なルックアップを誘発しスタックオーバーフローに至らせるDoS(CVE-2021-45105、CVSS 7.5前後)が見つかり、2.17.0 で修正されました。最初の一発で完全に直ったわけではなく、複数回の追随が必要だったことは、この種の設計欠陥の根深さを示します。
即時に取れる緩和策としては、当初 log4j2.formatMsgNoLookups=true の設定や JndiLookup クラスの削除が案内されましたが、これらは特定バージョンでは不完全であることが判明しました。したがって最終的な正解は2.17.0以降へのアップグレードであり、緩和策は恒久対応までの時間稼ぎと位置づけるべきです。
資格試験や実務確認では次を押さえます。CVE-2021-44228はCVSS 10.0のRCE、対象はLog4j 2系(1.x系の該当機能は別扱い)。2.15.0でJNDI既定無効、2.16.0でメッセージルックアップ削除、2.17.0でDoS(CVE-2021-45105)修正。恒久対策はアップグレードで、設定変更や単一クラス削除は不完全な場合がある点に注意。
教訓(一般化できる原則)
Log4Shell から一般化できる原則は3つあります。
第一に、信頼できない入力を、解釈・評価する能力を持つ層へ渡さないこと。ログ、テンプレートエンジン、式評価器、シリアライザなど、入力を「データ」ではなく「命令」として扱いうるコンポーネントに外部入力を素通しすると、注入(インジェクション)が成立します。ログは無害という前提を捨て、記録される値と展開される式を明確に分離する設計が必要です。SQLインジェクションやコマンドインジェクションと根は同じで、境界での解釈が敵に握られています。
第二に、推移的依存(transitive dependency)の管理です。多くの被害者は Log4j を直接使っておらず、依存する何かの、そのまた依存として引き込んでいました。自分のビルドに実際に何が含まれるかを知るには、SBOM(Software Bill of Materials)の生成と、既知脆弱性データベースとの継続的な突合が要ります。「使っていないつもり」は防御になりません。依存グラフ全体を可視化して初めて、影響範囲を即座に答えられます。
第三に、多層防御と迅速なパッチ運用です。WAFによる入力フィルタは難読化で回避され、単発の緩和策は不完全でした。egress(外向き通信)の制限で不審なLDAP/RMI接続を止める、脆弱な依存を素早く更新できるパイプラインを整える、といった重ねた備えが被害を左右しました。設計上の欠陥がコード実行に直結する構図は、投機的実行のSpectre/Meltdownにも通じます。より広い攻撃面の考え方はセキュリティ、依存とビルドの衛生はプログラミングの各トピックも参照してください。
脆弱性の解剖の記事ガイド
Log4Shell(CVE-2021-44228)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Log4Shell
比較で見る軸
難易度: advanced / カテゴリ: 脆弱性の解剖 / タグ数: 6
導入後に効く点
根因はログ出力時にメッセージ本文中の ${...} を再帰展開する設計と、JNDIがリモートのオブジェクトファクトリを取得・実行できる仕様の組み合わせ。User-Agentや検索欄など、信頼できない入力がログに載る全経路が攻撃面になった。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 脆弱性の解剖
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Log4Shell / Log4j」に近いか確認する。
- 強みである「CVE-2021-44228はLog4j 2のCVSS 10.0の欠陥。攻撃文字列がログへ入るだけでメッセージ検索がJNDIを起動し、LDAPからクラスを読み込み任意コード実行に至る。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。