log4j2漏洞解决方案:log4j2转换为logback

背景

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之前,必须对它们进行格式化。

适用情况(同时满足以下场景):

  1. 无法进行log4j2升级
  2. 依赖log4j2的模块为子模块,或者是第三方依赖
  • maven依赖
    <dependencies>
        <dependency>
            <groupId>com.vhicool.test</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.11.2</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </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

  • maven依赖
    log4f版本为2.0-2.15
    <properties>
        <log4j-version>2.8.2</log4j-version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j-version}</version>
        </dependency>
    </dependencies>

  • log4j配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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)
...

  • 结论
    通过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
Logger PatternLayout Interpolator JndiLookup InitialContext 输出日志 将日志翻译成文本 获取字符串变量($prefix:$key)解析器 解析字符串变量值$key 查找JNDI资源的值 Logger PatternLayout Interpolator JndiLookup InitialContext

$prefix是jndi时,JndiLookup解析字符串变量$key为JNDI执行语义。例如$key=ldap://127.0.0.1:8080/Log4jRCE的JNDI解析流程如下:

InitialContext#look($key)首先根据$key对应的创建对象的工厂ObjectFactory,查找逻辑:
(1). 解析 k e y 获 取 key获取 keyschema:ldap
(2). 生成ObjectFactory类全限定名:com.sun.jndi.url.ldap.ldapURLContextFactory,生成格式如下:

    "com.sun.jndi.url."+$schema+"."+$schema+"URLContextFactory"
InitialContext ldapURLContextFactory ldapURLContext LdapCtx 生成ldapURLContext ldapURLContext lookup:获取当前命名空间对象 调用ldap服务,实现远程调用 InitialContext ldapURLContextFactory ldapURLContext LdapCtx
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值