一个基于 Byte Buddy Java agent 的修复方案,用于 CVE-2021-44228,即 log4j 2.x 的 "JNDI LDAP" 漏洞。
它做三件事:
jndi: 格式字符串("lookups")的内部方法处理器。System.err(即 stderr)记录一条消息,表明发生了 log4j JNDI 尝试(包括尝试的格式字符串,其中任何 ${} 字符均被清理以防止传递注入)。"(log4j jndi disabled)"(以防止传递注入)。将 -javaagent:path/to/log4j-jndi-be-gone-1.0.0-standalone.jar 添加到你的 java 命令中。
注意: 如果你的 classpath 中已有 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 中的重新打包(即 "shaded")版本的 log4j,以避免应用程序的依赖版本与某个依赖自身的相同依赖版本之间发生冲突。但需要注意的是,由于使用了带有静态类名和/或来自嵌入式配置文件的类名的反射,log4j 似乎不容易在备用包名/前缀下重新打包。
可以通过在 -javaagent: 参数中的 agent JAR 路径后添加 =structureMatch=0 来禁用此行为,例如:
-javaagent:path/to/log4j-jndi-be-gone-1.0.0-standalone.jar=structureMatch=0
这将产生与 1.0.0 相同的匹配行为,即对类名进行简单的精确字符串比较。
你可以使用 ./gradlew 构建 JAR(build/libs/log4j-jndi-be-gone-1.0.0(-standalone).jar),或者从 releases 页面 获取。
log4j-jndi-be-gone agent 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 中的单参数版本)convertJndiName(),匹配其修饰符和类型签名CONTAINER_JNDI_RESOURCE_PATH_PREFIX,匹配其修饰符如果 log4j 库已被混淆,或者其类包/名称除基本重新打包(即 "shading")之外被修改,log4j-jndi-be-gone 将无法工作。
log4j-jndi-be-gone-1.0.0-standalone.jar 内嵌了 Byte Buddy。如果你已经在使用 Byte Buddy,可能会遇到问题。请改用 自 1.1.0 版本起,log4j-jndi-be-gone standalone 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 上进行了测试。