解决方案 将log4j2转换为logbackApache Log4j2是项目中常用的Java日志框架。在2021年11月,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。在Log4j 2.15.0-rc2 版本之后,问题解决。但是部分项目由于历史代码原因,直接使用的Log4j2依赖包的类,在升级log4j2版本时,代码出现冲突,在这种情况下,我们可以log4j2转换为logback,在log4j2依赖不变更的情况下,解决安全问题。
适用情况(同时满足以下场景):可以通过log4j-to-slf4j工具,将log4j2转换为logback。log4j-to-slf4j 允许将编码到Log4j2 API的应用程序路由到SLF4J。使用这个适配器可能会导致一些性能损失,因为在将Log4j2消息传递给SLF4J之前,必须对它们进行格式化。
- 无法进行log4j2升级依赖log4j2的模块为子模块,或者是第三方依赖
maven依赖
com.vhicool.test common1.0-SNAPSHOT org.apache.logging.log4j log4j-coreorg.apache.logging.log4j log4j-apiorg.apache.logging.log4j log4j-to-slf4j2.11.2 ch.qos.logback logback-classic1.2.3 ch.qos.logback logback-core1.2.3
测试类
public class Main { public static void main(String[] args) { //LoggerUtil为com.vhicool.test:test-common包中的工具类 LoggerUtil.info("${jndi:ldap://127.0.0.1:8080/Log4jRCE}"); } }
LoggerUtil.java
public class LoggerUtil { private static final Logger logger = LogManager.getLogger(LoggerUtil.class); public static void info(String msg){ System.out.println(logger.getClass()); logger.info(msg); } }
Logger : class org.apache.logging.slf4j.SLF4JLogger [main] INFO com.vhicoo.test.common.LoggerUtil - ${jndi:ldap://127.0.0.1:8080/Log4jRCE}
结论
从结果可以看出,经转换后,日志最终使用org.apache.logging.slf4j.SLF4JLogger输出。同时也没有触发日志注入的问题。
问题复现
当前模块:com.vhicool.test:test-common:1.0-SNAPSHOT
maven依赖
log4f版本为2.0-2.15
2.8.2 org.apache.logging.log4j log4j-core${log4j-version} org.apache.logging.log4j log4j-api${log4j-version}
log4j配置文件
测试方法
日志输出时,实际会调用远程地址ldap://127.0.0.1:8080/Log4jRCE,本身这个地址端口不存在,因此会出现网络异常。
public class Main { private static final Logger logger = LogManager.getLogger(Main.class); public static void main(String[] args) { System.out.println("Logger : "+logger.getClass()); logger.info("${jndi:ldap://127.0.0.1:8080/Log4jRCE}"); } }
输出
Logger : class org.apache.logging.log4j.core.Logger main WARN Error looking up JNDI resource [ldap://127.0.0.1:8080/Log4jRCE]. javax.naming.CommunicationException: 127.0.0.1:8080 [Root exception is java.net.ConnectException: Connection refused (Connection refused)] at com.sun.jndi.ldap.Connection.(Connection.java:238) at com.sun.jndi.ldap.LdapClient. (LdapClient.java:137) at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1609) at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749) at com.sun.jndi.ldap.LdapCtx. (LdapCtx.java:319) ...
结论
通过log4j2输出的日志,会解析${jndi:}中的变量为网络连接,并调用。其中打印日志类为:org.apache.logging.log4j.core.Logger
分析
为什么会导致日志jndi注入的问题
使用log4j2输出日志,如果日志输出串中,包含${}时,log4j会解析变量值的内部方法,内部方法的格式为$prefix:$key。默认支持的内部方法$prefix有:date, ctx, main, env, sys, sd, java, marker, jndi, jvmrunargs, bundle, map, log4j。
调用链:调用入口 org.apache.logging.log4j.core.Logger#logMessage
当$prefix是jndi时,JndiLookup解析字符串变量$key为JNDI执行语义。例如$key=ldap://127.0.0.1:8080/Log4jRCE的JNDI解析流程如下:
InitialContext#look($key)首先根据$key对应的创建对象的工厂ObjectFactory,查找逻辑:
(1). 解析
k
e
y
获
取
key获取
key获取schema:ldap
(2). 生成ObjectFactory类全限定名:com.sun.jndi.url.ldap.ldapURLContextFactory,生成格式如下:
"com.sun.jndi.url."+$schema+"."+$schema+"URLContextFactory"
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)