Log4Shell 2021年 ログの文字列から走るコード

ログに書いた利用者の入力の中の ${...} を、Log4j 2 が置き換えの指示として評価し、JNDI で外の LDAP から読んだコードを走らせた。弱点は1行の直しで消えるが、どこに Log4j があるかを知らない組織は、直す場所を探すところで止まった。

CVE-2021-44228。CVSS 3.1 の基本値は10.0[1][2]。影響は log4j-core の 2.0-beta9 から 2.15.0 の手前まで。ただし Java 7 向けの 2.12.2 と Java 6 向けの 2.3.1 を除く[1]。開発元への届け出は2021年11月24日、公開と修正版 2.15.0 は12月10日[3]。続く修正は12月28日の 2.17.1 までで、開発元は Java 8 以降に 2.17.1、Java 7 に 2.12.4、Java 6 に 2.3.2 を勧める[6][8]。修正は出揃ったが、CSRB は2022年7月に、弱い版は10年かそれ以上残り得ると評した[3]。2026年11月時点の公開資料で書いています。

要点

  • 原因は、2013年に入った JNDI の lookup の機能と、ログのメッセージの中の lookup を既定で評価する作りの組み合わせだった。入力を書くだけで、外へつなぎに行く。
  • 悪用に要るのは、ログに残る所へ文字列を送ることだけで、認証も利用者の操作も要らない[3]。ログに書く入力は、ヘッダ、ユーザー名、検索語など、ほぼすべての入口にある。
  • 2.15.0 の直しは既定でない設定で足りず、12月中に CVE が3つ続いた。CSRB は、続く弱点が混乱と修正の疲れを招いたと書く[3]。
  • 守る側を止めたのは、Log4j がどの製品のどこに入っているかの一覧が無いことだった[3]。製品に埋め込まれた部品は、直接の依存の一覧に出ない。
  • 根本の守りは、部品の棚卸しを先に持つこと、修正版へ上げること、サーバーから外への通信を既定で止めること。

何が起きたか

2013年の機能の追加、2021年11月24日の届け出、12月9日と10日の公開、12月14日から28日の続く修正の4つの段 1 機能が入る 2013年7月 JNDI の lookup 2.0-beta9 から 2 届け出 2021年11月24日 開発元へ電子メール 公の場で直しを作る 3 公になる 12月9日と10日 2.15.0 と CVE 走査と悪用が広がる 4 続く修正 12月14日から28日 2.16.0、2.17.0 2.17.1 1から2に8年余り。2から3は16日。3から4の18日で修正の版が3つ 最も早い悪用の跡は12月1日。公開の8日前
図1 機能の追加から修正が出揃うまで
日付出来事
2013年7月18日利用者の側から出た JNDI の lookup の追加の案を、Log4j の開発の組が点検して取り込む。2.0-beta9 に入る[3]
2021年11月24日Alibaba Cloud の安全の部門の技術者が、ASF に電子メールで届け出る。説明、弱いコードの場所、概念実証、画面写真を添えた[3]
2021年11月29日から30日ASF が、メッセージの lookup を既定で止める変更と、JNDI の LDAP を絞る変更を、公開の pull request で進める[3]
2021年12月1日ある技術サービスの会社が、知られている最も早い悪用を観測する。届け出た技術者の概念実証の試しと合う動きだった[3]
2021年12月5日から6日直しをリポジトリに入れ、2.15.0 の候補版に印を付ける。この時点で勧告は出していない[3]
2021年12月9日届け出た技術者が、弱点が WeChat で話題になっていると ASF に知らせる。中国の安全の会社の投稿に、伏せ字の概念実証の画面写真があった[3]
2021年12月10日ASF が予定を早めて 2.15.0 を出し、CVE を公開する[3]。NVD に載る[2]。CISA が声明を出し、悪用されている脆弱性の一覧 KEV に加える[2][3]
2021年12月11日JPCERT/CC が注意を呼びかける[8]
2021年12月14日CVE-2021-45046 が公開される。直しの 2.16.0 は、メッセージの lookup の対応を外し、JNDI を既定で止めた[4]
2021年12月17日CISA が緊急指令 ED 22-02 を出し、連邦の文民の機関に対応を命じる[3]
2021年12月18日CVE-2021-45105 が公開される。自分を参照する lookup の無限の再帰で止まる。2.17.0 で直る[5]
2021年12月22日CISA、FBI、NSA と、豪州、カナダ、ニュージーランド、英国の機関が、共同の勧告 AA21-356A を出す[7]
2021年12月28日CVE-2021-44832 が公開される。設定を書き換えられる者が JDBC の出力先で JNDI を使う形。2.17.1 で直る[6]
2022年7月11日米国の Cyber Safety Review Board が、最初の調査の報告として Log4j の件を公表する[3]
  • 公開から5日後、Cloudflare は毎秒400回の悪用の試みを観測した[3]。研究者と守る側の走査も多く、悪意のある走査と見分けにくかった[3]。
  • CSRB は、報告を書いた時点で、重要な社会基盤への大きな攻撃は把握しておらず、悪用は専門家の予想より少なかったと書く。一方で、悪用の傾向を地域や業種で見渡せる信頼できる情報源は無いとも書く[3]。
  • ある連邦の省は、この弱点への対応に33,000時間を使ったと CSRB に報告した[3]。

起きた仕組み

Log4j 2 は、設定やメッセージの中の ${種類:名前} の形を lookup として、その場で値に置き換える。種類の1つが JNDI で、名前に書いた宛先へ問い合わせ、返ってきた物を Java の物として読み込む。2.14 以前は、ログのメッセージに入った値まで既定で評価していた[1][2]。

利用者の入力がログに書かれ、Log4j が lookup を評価して外の LDAP に問い合わせ、返った参照から読んだコードがアプリのプロセスで走る 送る側 ヘッダや入力欄に lookup の形を入れる app.example.com のプロセスの中 アプリ 入力をそのままログへ Log4j 2 メッセージの lookup を評価 JNDI で 外へ問い合わせ 外への通信を 止めれば切れる 読み込んだクラス アプリの権限で走る 外の LDAP 203.0.113.50 コードの在りかを返す 返った参照から クラスを読む
図2 ログに書いた入力からコードが走るまで

架空のアプリ。ログに入力を書く、どこにでもある1行

// LoginController.java 架空の例
String user = request.getParameter("user");
log.warn("ログインの失敗 user={}", user);   // 引数として渡しても、2.14 以前は整形後のメッセージを評価した

コードは正しい書き方で、引数として渡している。それでも、整形が終わったメッセージの中の ${...} を Log4j が評価したため、書く側では防げなかった。NVD の説明も、ログのメッセージかその引数を操れる者が、とする[2]。

評価の形 架空の種類の名前で示す。動く文字列ではない

# 受け取った user の値
taro${種類:名前}
# 2.14 以前の既定の動き
1 メッセージを整形する     ログインの失敗 user=taro${種類:名前}
2 ${...} を lookup として探す
3 種類に応じて値を取り、置き換える。種類が JNDI なら、名前に書いた宛先へ問い合わせる
# 2.15.0 以降の既定の動き
1 メッセージを整形する     ログインの失敗 user=taro${種類:名前}   置き換えない

続いた3つの CVE

最初の直しは、既定の設定でメッセージの lookup を止めるものだった。既定でない書式の設定では、別の道で同じ評価に届いた。

CVE公開中身直した版
CVE-2021-4422812月10日メッセージとその引数の lookup から JNDI へ。CVSS 10.0[2]2.15.0 で既定で止め、2.16.0 で機能を外す[2]
CVE-2021-4504612月14日2.15.0 の直しが、既定でない書式で足りない。スレッドの文脈の値を操れれば、Context Lookup などの書式から JNDI に届く。CVSS 9.0[4]2.16.0。メッセージの lookup の対応を外し、JNDI を既定で止める[4]
CVE-2021-4510512月18日自分を参照する lookup で無限に再帰し、止まる。CVSS 5.9[5]2.17.0、2.12.3、2.3.1[5]
CVE-2021-4483212月28日ログの設定を書き換えられる者が、JDBC の出力先で JNDI の LDAP を使わせる。CVSS 6.6[6]2.17.1、2.12.4、2.3.2。JNDI のデータ元の名前を java の形に限る[6]
起動の設定で止める回避策は足りなかった

最初の数日に広まった回避策の1つが、メッセージの lookup を止める起動の設定だった。ASF は、一部の回避策は攻撃の手口によっては足りないとして、版を上げられない時は JndiLookup のクラスを外す方を勧めた[8]。CVE-2021-45046 は、メッセージの外の書式から評価に届く道だった[4]。回避策は時間を稼ぐもので、版の上げの代わりにならない。

どこで止められたか

部品の設計、部品の一覧、版の上げ、外への通信、検知の5つの段に、それぞれ止める所を置く 部品の設計 部品の一覧 版の上げ 外への通信 検知 1 データを命令として評価しない ログの値の中を解釈する機能を既定で持たない 2 どこに入っているかを先に知る 製品の中の部品まで。CSRB が勧めた資産の一覧 3 修正版へ上げる 2.17.1、2.12.4、2.3.2。回避策は上げるまでの間だけ 4 サーバーから外へ出さない LDAP の問い合わせが外へ届かなければ、読み込めない 5 侵害されたものとして調べる 公開の前から悪用はあった。上げただけで終えない
図3 段ごとの止める所
止める所この件で限り
データを命令として評価しない機能は2013年に、決まった手続きで点検されて入った[3]。手続きは守られていたが、ログの値を評価する危うさは見落とされた使う側が部品の中の機能を選べない。部品の側の設計の問題
どこに入っているかを先に知るLog4j の利用者の一覧も、組み込んだ先の一覧も無く、守る側の進みを妨げた。最もよく対応できた組織は、自分の Log4j の使い所を知っていた[3]SBOM はまだ限られ、書き方の揺れがあると CSRB は書く[3]
修正版へ上げる12月10日から28日までに、Java 8 向けの修正の版が 2.15.0 から 2.17.1 まで4つ出た[1][6]。上げること自体が、止まる危険と早さを比べる判断になった[3]他社の製品に入った物は、その会社の修正を待つしかない
サーバーから外へ出さない守る側は、弱い版を使う機械の外向きの通信を絞った[3]。CISA の勧告も、外向きの TCP と UDP を止めるよう求めた[7]プロセスの中で評価が走ることは変わらない。弱点を消すものではない
侵害されたものとして調べるCISA は、弱い機械を侵害されたものとして扱い、確かめるまで切り離すよう求めた[7]ログに残った入力の文字列で探せるが、難読化された形は見逃す

WAF で悪用の文字列を止める策も広く使われた[3]。lookup は入れ子にでき、同じ意味の書き方が多いので、文字列の照合では漏れる。止まるのは典型の形だけと考える。

守り方

根本の直し方
  • log4j-core を 2.17.1 以降、Java 7 なら 2.12.4、Java 6 なら 2.3.2 へ上げる[8]。CISA は、緩和の策ごとに状態を追う手間がかかるので、上げる方を優先するよう書く[9]。
  • 直接の依存だけでなく、推移的な依存、製品に埋め込まれた物、内部で作ったソフト、外に向いていない機械まで、部品の一覧を持つ。CISA は、範囲を初めの想定より広げて洗い出すよう求めた[9]。一覧が先にあれば、次の弱点では探す時間が要らない。
  • 製品を買う時は、部品の一覧を出せるかと、弱点が出た時の告知の窓口を確かめる。CSRB は、作る側が SBOM を作って添えるよう勧めた[3]。
  • サーバーから外への通信を既定で止め、要る宛先だけを開ける。アプリのサーバーが外の LDAP に話しかける用は無い。

架空の機械 app01 で、ファイルの名前と、まとめた jar の中を探す

find / -xdev -name 'log4j-core-*.jar' 2>/dev/null
/opt/report/lib/log4j-core-2.14.1.jar              # ファイルの名前で見つかる

# 1つにまとめた jar の中は名前で見えない。中の一覧を見る
for j in $(find / -xdev -name '*.jar' 2>/dev/null); do
  unzip -l "$j" 2>/dev/null | grep -q 'org/apache/logging/log4j/core/lookup/JndiLookup.class' && echo "$j"
done
/opt/billing/billing-app.jar                       # 中に JndiLookup がある。版を確かめる
  • jar の中に jar を入れた形や、war、ear の中は、この探し方でも届かないことがある。手で探すのは一覧が無い時の間に合わせで、ビルドの時に部品の一覧を出す方が確か。
  • 見つけた物は、版、更新した日、責任者、置いた場所と一緒に記録する。CISA の勧告が棚卸しに求めた項目である[7]。
緩和の策
  • 上げられない時は、JndiLookup のクラスをクラスパスから外す。ASF が勧めた回避策である[8]。
  • 弱い版を使う機械の外向きの通信を止める。上げるまでの間、ネットワークから切り離すのも選択肢になる[3][9]。
  • ログに書く値の中の制御の文字と改行を符号化し、ログは構造化した項目として書く。部品の解釈を止めるものではないが、ログを読む道具の側の誤読も減らす。
  • WAF の規則は、上げるまでの時間を稼ぐ所として置き、すり抜けを前提にする。

既知の弱点を持つ部品を推移的な依存から見つける考え方は A03 ソフトウェアサプライチェーンの不備 の型1に、入力を命令として評価させない考え方は A05 インジェクション に、ログに書く値の扱いは A09 セキュリティログとアラートの不備 にある。

参照

  1. Apache Logging Services security Apache Software Foundation
  2. CVE-2021-44228 NIST NVD、2021年12月10日公開
  3. Review of the December 2021 Log4j Event Cyber Safety Review Board、2022年7月11日
  4. CVE-2021-45046 NIST NVD、2021年12月14日公開
  5. CVE-2021-45105 NIST NVD、2021年12月18日公開
  6. CVE-2021-44832 NIST NVD、2021年12月28日公開
  7. Mitigating Log4Shell and Other Log4j-Related Vulnerabilities AA21-356A CISA ほか、2021年12月22日
  8. Apache Log4jの任意のコード実行の脆弱性 CVE-2021-44228 に関する注意喚起 JPCERT/CC、2021年12月11日公開、2022年1月4日更新
  9. Apache Log4j Vulnerability Guidance CISA、2022年4月8日更新