Log4Shell 2021年 ログの文字列から走るコード
ログに書いた利用者の入力の中の ${...} を、Log4j 2 が置き換えの指示として評価し、JNDI で外の LDAP から読んだコードを走らせた。弱点は1行の直しで消えるが、どこに Log4j があるかを知らない組織は、直す場所を探すところで止まった。
要点
- 原因は、2013年に入った JNDI の lookup の機能と、ログのメッセージの中の lookup を既定で評価する作りの組み合わせだった。入力を書くだけで、外へつなぎに行く。
- 悪用に要るのは、ログに残る所へ文字列を送ることだけで、認証も利用者の操作も要らない[3]。ログに書く入力は、ヘッダ、ユーザー名、検索語など、ほぼすべての入口にある。
- 2.15.0 の直しは既定でない設定で足りず、12月中に CVE が3つ続いた。CSRB は、続く弱点が混乱と修正の疲れを招いたと書く[3]。
- 守る側を止めたのは、Log4j がどの製品のどこに入っているかの一覧が無いことだった[3]。製品に埋め込まれた部品は、直接の依存の一覧に出ない。
- 根本の守りは、部品の棚卸しを先に持つこと、修正版へ上げること、サーバーから外への通信を既定で止めること。
何が起きたか
| 日付 | 出来事 |
|---|---|
| 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]。
架空のアプリ。ログに入力を書く、どこにでもある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-44228 | 12月10日 | メッセージとその引数の lookup から JNDI へ。CVSS 10.0[2] | 2.15.0 で既定で止め、2.16.0 で機能を外す[2] |
| CVE-2021-45046 | 12月14日 | 2.15.0 の直しが、既定でない書式で足りない。スレッドの文脈の値を操れれば、Context Lookup などの書式から JNDI に届く。CVSS 9.0[4] | 2.16.0。メッセージの lookup の対応を外し、JNDI を既定で止める[4] |
| CVE-2021-45105 | 12月18日 | 自分を参照する lookup で無限に再帰し、止まる。CVSS 5.9[5] | 2.17.0、2.12.3、2.3.1[5] |
| CVE-2021-44832 | 12月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]。回避策は時間を稼ぐもので、版の上げの代わりにならない。
どこで止められたか
| 止める所 | この件で | 限り |
|---|---|---|
| データを命令として評価しない | 機能は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]。
既知の弱点を持つ部品を推移的な依存から見つける考え方は A03 ソフトウェアサプライチェーンの不備 の型1に、入力を命令として評価させない考え方は A05 インジェクション に、ログに書く値の扱いは A09 セキュリティログとアラートの不備 にある。
参照
- Apache Logging Services security Apache Software Foundation
- CVE-2021-44228 NIST NVD、2021年12月10日公開
- Review of the December 2021 Log4j Event Cyber Safety Review Board、2022年7月11日
- CVE-2021-45046 NIST NVD、2021年12月14日公開
- CVE-2021-45105 NIST NVD、2021年12月18日公開
- CVE-2021-44832 NIST NVD、2021年12月28日公開
- Mitigating Log4Shell and Other Log4j-Related Vulnerabilities AA21-356A CISA ほか、2021年12月22日
- Apache Log4jの任意のコード実行の脆弱性 CVE-2021-44228 に関する注意喚起 JPCERT/CC、2021年12月11日公開、2022年1月4日更新
- Apache Log4j Vulnerability Guidance CISA、2022年4月8日更新