1.基本信息

CVE-2021-44228
jdk8u65 

 <dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-core</artifactId>
     <version>2.14.1</version>
 </dependency>
<dependency>
     <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-api</artifactId>
     <version>2.14.1</version>
</dependency>
堆键
lookup:207, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)
log:481, LoggerConfig (org.apache.logging.log4j.core.config)
log:456, LoggerConfig (org.apache.logging.log4j.core.config)
log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2034, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1899, AbstractLogger (org.apache.logging.log4j.spi)
info:1444, AbstractLogger (org.apache.logging.log4j.spi)
main:18, HelloServlet (com.example.log4j2)

2.开发基础学习

log4j2是一个日志记录的组件,这里开发还是很简单的组件使用其实都挺简单的, log4j2 的一些实现方式有很多 xml yami  properties
 还有很多但是都是不常用的,下面我们在src\main\resources目录下面必须创建log4j2.xml文件内容是代码一,主要作用是打印内容和
 存储日志存储到logs/myLog.log文件里面,下面在看代码二,运行之后发现获取了java版本信息(图一),这是因为log4j2自己设计的,
 去官网文档看一下就知道了
[https://logging.apache.org/log4j/2.x/manual/lookups.html#JavaLookup](https://logging.apache.org/log4j/2.x/manual/lookups.html#JavaLookup)
运行之后内容会写到logs/myLog.log日志文件中(图二),其实使用还是很简单,下面分析中我基本就是一步一步的讲给大家自己分析的时候
可以跳着看,主要是为了帮助初学者和代码理解不是很好的人。
<?xml version="1.0" encoding="UTF-8"?>

<configuration status="info">
    <Properties>
        <Property name="pattern1">[%-5p] %d %c - %m%n</Property>
        <Property name="pattern2">
            =========================================%n 日志级别:%p%n 日志时间:%d%n 所属类名:%c%n 所属线程:%t%n 日志信息:%m%n
        </Property>
        <Property name="filePath">logs/myLog.log</Property>
    </Properties>
    <appenders> <Console name="Console" target="SYSTEM_OUT">
        <PatternLayout pattern="${pattern1}"/>
    </Console> <RollingFile name="RollingFile" fileName="${filePath}"
                            filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
        <PatternLayout pattern="${pattern2}"/>
        <SizeBasedTriggeringPolicy size="5 MB"/>
    </RollingFile>
    </appenders>
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

package com.example.log4j2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.LongFunction;


public class HelloServlet {
    public static void main(String[] args) {
        Logger logger = LogManager.getLogger(LongFunction.class);

        String username = "${java:version}";
        if (username != null) {
            logger.info("User {} login in!", username);
            logger.error("User {} not exists", username);
        }
        else {
            logger.error("User {} not exists", username);
        }
    }
}

3.漏洞分析

在网上的分析说在`PatternLayout` 这个类下的 `toSerializable()` 方法下开始,要是作为初学者我还是不建议的,
虽然在这个之前的代码都不是特别重要,但是跟一边就当练习代码熟悉度,反正我刚学就是从头开始的,在下面分析中如果不是很重要
的我就直接过了不细讲,进入AbstractLogger类的info方法,在进入logIfEnabled方法(图一),在进入logMessage方法,
在跟进newMessage方法(图二),这个方法目的就是我们的内容给params(图三),之后封装返回msg,接着就是跟进logMessageSafely,
这里说这里的目的是因为logMessageSafely第5个参数msg内容可控。

下面就走到logMessageSafely方法(图一),接着进入到logMessageTrackRecursion(图二),在走进tryLogMessage,
下面我就不多说废话了,(图三)Logger.tryLogMessage,(图四)AwaitCompletionReliabilityStrategy.log,
(图五)AwaitCompletionReliabilityStrategy.log,(图六)LoggerConfig.log,后面我就直给图了因为后面的参数
都没变一直跟就行。

这里唯一难以理解的就是toSerializable方法进行遍历(图一),那么看一下formatters遍历的类是谁发现9个都是PatternFormatter
这个类(图二),那么也就是遍历9次都执行PatternFormatter类下面的format方法,那我们先跟进去第一个看看呗(图三),
再次执行了format方法那么执行的那个类的format方法,就需要知道converter常量是谁,图二中我们可以得到答案(图四)
可以看到其他的都是LiteralPatternConverter这个类只有7是MessagePatternConverter,那我们就直接看
MessagePatternConverter类下面的format方法了,还有一个原因就是因为我们是上帝视角,作者在审的时候肯定是可看了
LiteralPatternConverter了。

我们走进去MessagePatternConverter类下面的format方法(图一),这里主要看128行到131行,获取到${
接着获取所有内容接这走进replace方法(图二)接着就是最关键的了substitute方法跟进去会发现一段很长的代码可以都不看,
因为我们有上帝视角,接着走进resolveVariable(图四)。

我们看一下215行代码,发现我们可以使用JNDI,并且如果是JNDI就会执行JndiLookup类的lookup方法,
java就会执行JavaLookup的lookup方法(图三),现在我们终于明白为什么执行${java:version}会出现版本了,
下面我们改成JNDI注入看看,下面就会走到JndiLookup类的lookup方法(图四),接着进入convertJndiName方法
这个就是看是不是java:comp/env/开头并且查看里面有没有:JNDI注入ldap://134.122.0.0:1389/kjjrrj,本身就有的(图五),
这里也就不会添加没用的,图四56行跟进去接着跟就到原生JNDI类javax.naming.InitialContext(图六)。

建议自己手动也玩玩,其实不是很难跟着代码走跟着文章看。