
Byte Buddy ベースのJavaエージェントによるCVE-2021-44228(log4j 2.xの「JNDI LDAP」脆弱性)の修正です。
次の3つのことを行います:
jndi: フォーマット文字列(ルックアップ)の内部メソッドハンドラを無効にします。System.err(stderr)に記録します(試行されたフォーマット文字列を含み、 ${} 文字はサニタイズされて転送インジェクションを防止します)。"(log4j jndi disabled)" に解決します(転送インジェクションを防止するため)。-javaagent:path/to/log4j-jndi-be-gone-1.0.0-standalone.jar を java コマンドに追加してください。
注意: 既にクラスパスにByte Buddyがある場合は、log4j-jndi-be-gone-1.0.0.jar を使用してみてください。
$ java -javaagent:path/to/log4j-jndi-be-gone-1.0.0-standalone.jar -jar path/to/some.jar
バージョン1.1.0以降、log4j-jndi-be-goneはデフォルトで、アプリケーションの依存関係と依存関係自身の依存関係との間の衝突を防ぐために、別のパッケージ名でJAR内に埋め込まれている可能性がある、リパッケージ(シェーディング)されたlog4jのバージョンを処理しようとします。ただし、log4jは、静的なクラス名や埋め込み設定ファイルからのクラス名を使ったリフレクションの使用により、別のパッケージ名/プレフィックスの下で簡単にリパッケージできないことに注意する必要があります。
この動作は、-javaagent: 引数のエージェントJARパスの後に =structureMatch=0 を置くことで無効にできます。例:
-javaagent:path/to/log4j-jndi-be-gone-1.0.0-standalone.jar=structureMatch=0
これにより、1.0.0と同じマッチング動作、つまりクラス名に対する単純な完全文字列比較が行われます。
JARは ./gradlew でビルドするか(build/libs/log4j-jndi-be-gone-1.0.0(-standalone).jar)、リリースページから入手できます。
log4j-jndi-be-goneエージェントJARはJava 6~17以上をサポートしています。
実装はまず、org.apache.logging.log4j.core.lookup.JndiLookup の最も内側のサブパッケージとクラス名に一致するサフィックスを持つクラス、つまり lookup.JndiLookup とのマッチングから開始します。これは、org.apache.logging.log4j.core がパッケージ名を保持しないリパッケージルールによって改変されている可能性が合理的に考えられるためです。さらに、他のすべての予想されるlog4jタイプに対して単に同様のチェックを実行するだけでなく、それらが同じベースパッケージの下に存在することも確認します。
次に実装は、特定された可能性のあるlog4jの lookup.JndiLookup クラスの構造を調べ、以下に対して検証を試みます:
org.apache.logging.log4j.core.config.plugins.Plugin アノテーション(アノテーションパラメータとその値を含む)lookup() (その修飾子と型シグネチャと照合し、2.0の1引数バージョンは無視)convertJndiName() (その修飾子と型シグネチャと照合)CONTAINER_JNDI_RESOURCE_PATH_PREFIX (その修飾子と照合)log4jライブラリが難読化されている場合、または基本的なリパッケージ(シェーディング)以外でクラスパッケージ/名前が変更されている場合、log4j-jndi-be-goneは動作しません。
log4j-jndi-be-gone-1.0.0-standalone.jar はByte Buddyをバンドルしています。すでにByte Buddyを使用している場合、問題が発生する可能性があります。代わりに バージョン1.1.0以降、log4j-jndi-be-goneのスタンドアロンJARは、独自のパッケージプレフィックスの下にリパッケージされたByte Buddyをバンドルしています。これにより、競合を防ぐことができます。log4j-jndi-be-gone-1.0.0.jar を使用してみてください。ただし、log4j-jndi-be-goneはByte Buddy 1.12.xを期待していることに注意してください。
JndiLookup クラスをハニーポットや lookup() 呼び出しのログ記録を試みる実装に置き換えた場合、log4j-jndi-be-goneはそれらの lookup メソッドを無効にして動作しなくなる可能性があります。
tests/jnditest ディレクトリには、log4jのログ呼び出しがJNDI LDAPフォーマット文字列を渡す簡単なテストケースがあります。また、log4jによって接続試行が行われたかどうかを判断するために独自のポートリスナーを設定し、接続を受信した場合にテストを失敗とします。
$ ./tests/jnditest/test-uninstrumented.sh
BUILD SUCCESSFUL in 1s
6 actionable tasks: 5 executed, 1 up-to-date
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date
JUnit version 4.12
.16:08:49.547 [main] ERROR trust.nccgroup.jnditest.test.JndiTest - Hello, _${jndi:ldap://127.0.0.1:8899/evil}_!
E
Time: 0.929
There was 1 failure:
1) logging(trust.nccgroup.jnditest.test.JndiTest)
java.lang.AssertionError: jndi ldap connection received
at org.junit.Assert.fail(Assert.java:88)
at trust.nccgroup.jnditest.test.JndiTest.logging(JndiTest.java:55)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at org.junit.runner.JUnitCore.runMain(JUnitCore.java:77)
at org.junit.runner.JUnitCore.main(JUnitCore.java:36)
at trust.nccgroup.jnditest.Main.main(Main.java:24)
FAILURES!!!
Tests run: 1, Failures: 1
$ ./tests/jnditest/test-instrumented.sh
BUILD SUCCESSFUL in 1s
6 actionable tasks: 5 executed, 1 up-to-date
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date
JUnit version 4.12
.log4j jndi lookup attempted: (sanitized) ldap://127.0.0.1:8899/evil
16:09:06.064 [main] ERROR trust.nccgroup.jnditest.test.JndiTest - Hello, _(log4j jndi disabled)_!
Time: 1.362
OK (1 test)
Apache 2ライセンスの下でライセンスされています。
log4j-jndi-be-goneは、OpenJDK 6、8、11、17、およびHotSpotおよびOpenJ9 JVMでテストされています。