Log4j2的JNDI注入漏洞原理分析与思考

本文深入分析了Log4j2的JNDI注入漏洞(CVE-2021-44228)的原理,介绍了Log4j2、JNDI、JNDI注入的概念,并通过漏洞复现、补丁分析和思考,揭示了漏洞的形成原因和潜在风险。文章指出,Log4j2团队在2.15.0-rc1和rc2版本中分别采取了不同的补丁策略,但rc1仍存在可被绕过的风险。最后,作者强调了安全编程的重要性,并讨论了应对这种广泛依赖的基础类库漏洞的挑战。
摘要由CSDN通过智能技术生成

前言

最近Log4j2的JNDI注入漏洞(CVE-2021-44228)可以称之为“核弹”级别。Log4j2作为类似JDK级别的基础类库,几乎没人能够幸免。极盾科技技术总监对该漏洞进行复现和分析其形成原理。在此分享。

以下涉及的代码,均在mac OS 10.14.5,JDK1.8.0_91环境下成功运行。

一、 前置知识

1.1 Log4j2

Log4j2是一个Java日志组件,被各类Java框架广泛地使用。它的前身是Log4j,Log4j2重新构建和设计了框架,可以认为两者是完全独立的两个日志组件。本次漏洞影响范围为Log4j2最早期的版本2.0-beta9到2.15.0。

因为存在前身Log4j,而且都是Apache下的项目,不管是jar包名称还是package名称,看起来都很相似,导致有些人分不清自己用的是Log4j还是Log4j2。这里给出几个辨别方法:

  1. Log4j2分为2个jar包,一个是接口 log4j-api-${版本号}.jar ,一个是具体实现 log4j-core-${版本号}.jar 。Log4j只有一个jar包 log4j-${版本号}.jar 。
  2. Log4j2的版本号目前均为2.x。Log4j的版本号均为1.x。
  3. Log4j2的package名称前缀为 org.apache.logging.log4j 。Log4j的package名称前缀为 org.apache.log4j 。

1.2 Log4j2 Lookup

Log4j2的Lookup主要功能是通过引用一些变量,往日志中添加动态的值。这些变量可以是外部环境变量,也可以是MDC中的变量,还可以是日志上下文数据等。

下面是一个简单的Java Lookup例子和输出:

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

public class Log4j2Lookup {
    public static final Logger LOGGER = LogManager.getLogger(Log4j2RCEPoc.class);

    public static void main(String[] args) {
        ThreadContext.put("userId", "test");
        LOGGER.error("userId: ${ctx:userId}");
    }
}
10:21:19.618 [main] ERROR Log4j2RCEPoc - userId: test

从上面的例子可以看到,通过在日志字符串中加入"${ctx:userId}",Log4j2在输出日志时,会自动在Log4j2的 ThreadContext 中查找并引用 userId 变量。格式类似"${type:var}",即可以实现对变量var的引用。type可以是如下值:

ThreadContext
${env:USER}
${java:version}

其中和本次漏洞相关的便是jndi,例如: ${
jndi:rmi//127.0.0.1:1099/a} ,表示通过JNDI Lookup功能,获取 rmi//127.0.0.1:1099/a 上的变量内容。

1.3 JNDI

JNDI(Java Naming and Directory Interface,Java命名和目录接口),是Java提供的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象 。

例如使用数据库,需要在各个应用中配置各种数据库相关的参数后使用。通过JNDI,可以将数据库相关的配置在一个支持JNDI服务的容器(通常Tomat等Web容器均支持)中统一完成,并暴露一个简洁的名称,该名称背后绑定着一个 DataSource 对象。各个应用只需要通过该名称和JNDI接口,获取该名称背后的 DataSource 对象。当然,现在SpringBoot单体发布模式,极少会使用这种方式了。

再举个更简单的例子,这有点类似DNS提供域名到IP地址的解析服务。域名简洁易懂,便于普通用户使用,背后真正对应的是一个复杂难记的IP,甚至还可能是多个IP。DNS即JNDI服务,域名即可用于绑定和查找的名称,IP即该名称绑定的真正对象。用现代可以类比的技术来说,JNDI就是一个对象注册中心。

JNDI由三部分组成:JNDI API、Naming Manager、JNDI SPI。JNDI API是应用程序调用的接口,JNDI SPI是具体实现,应用程序需要指定具体实现的SPI。下图是官方对JNDI介绍的架构图:

Log4j2的JNDI注入漏洞、原理分析与思考

下面是一个简单的例子:

public interface Hello extends java.rmi.Remote {
    public String sayHello(String from) throws java.rmi.RemoteException;
}
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    public HelloImpl() throws java.rmi.RemoteException {
        super();
    }

    @Override
    public String sayHello(String from) throws java.rmi.RemoteException {
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值