Apache Log4j2是项目中常用的Java日志框架。在2021年11月,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。在Log4j 2.15.0-rc2 版本之后,问题解决。但是部分项目由于历史代码原因,直接使用的Log4j2依赖包的类,在升级log4j2版本时,代码出现冲突,在这种情况下,我们可以log4j2转换为logback,在log4j2依赖不变更的情况下,解决安全问题。
log4j2
转换为logback
可以通过
log4j-to-slf4j
工具,将log4j2
转换为logback
。log4j-to-slf4j 允许将编码到Log4j2 API的应用程序路由到SLF4J。使用这个适配器可能会导致一些性能损失,因为在将Log4j2消息传递给SLF4J之前,必须对它们进行格式化。
<dependencies>
<dependency>
<groupId>com.vhicool.testgroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
exclusion>
<exclusion>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-to-slf4jartifactId>
<version>2.11.2version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.3version>
dependency>
dependencies>
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
<properties>
<log4j-version>2.8.2log4j-version>
properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>${log4j-version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>${log4j-version}version>
dependency>
dependencies>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="%-5p %-60c %x %X{orderId} - %m%n" />
Console>
Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
Root>
Loggers>
Configuration>
日志输出时,实际会调用远程地址
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.<init>(Connection.java:238)
at com.sun.jndi.ldap.LdapClient.<init>(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.<init>(LdapCtx.java:319)
...
${jndi:}
中的变量为网络连接,并调用。其中打印日志类为:org.apache.logging.log4j.core.Logger
使用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"