log4j漏洞复现及详细分析

log4j漏洞复现及详细分析

一、前置知识1.1漏洞成因

该漏洞的主要原因是log4j在日志输出中,未对字符合法性进行严格的限制,执行了JNDI协议加载的远程恶意脚本,从而造成RCE。这里面有一个关键点就是,什么是JNDI,为什么JNDI可以造成RCE

关于什么是JNDI注入,请看下面分析JNDI基本介绍

JNDI(Java Naming and Directory Interface–Java命名和目录接口)是Java中为命名和目录服务提供接口的API,通过名字可知道,JNDI主要由两部分组成:Naming(命名)和Directory(目录),其中Naming是指将对象通过唯一标识符绑定到一个上下文Context,同时可通过唯一标识符查找获得对象,而Directory主要指将某一对象的属性绑定到Directory的上下文DirContext中,同时可通过名字获取对象的属性同时操作属性。

JNDI架构图

JNDI主要由JNDI API和JNDI SPI两部分组成,Java应用程序通过JNDI API访问目录服务,而JNDI API会调用Naming Manager实例化JNDI SPI,然后通过JNDI SPI去操作命名或目录服务其如LDAP, DNS,RMI等,JNDI内部已实现了对LDAP,DNS, RMI等目录服务器的操作API。

JNDI核心API

类名

描述

Context

命名服务的接口类,由很多的name-to-object的健值对组成,可以通过该接口将健值对绑定到该类中,也可通过该类根据name获取其绑定的对象

InitialContext

Naming(命名服务)操作的入口类,通过该类可对命名服务进行相关的操作

DirContext

Directory目录服务的接口类,该类继承自Context,在Naming服务的基础上扩展了对于对象属性的绑定和获取操作

InitialDirContext

Directory目录服务相关操作的入口类,通过该类可进行目录相关服务的操作

1. 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;2. DirContext核心方法/**可根据Name或者name获取绑定对象的所有已关联的属性*/ public Attributes getAttributes(Name name) throws NamingException; public Attributes getAttributes(String name) throws NamingException;/**可根据Name或者name和属性标识符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;JNDI操作目录服务代码编写

下面我将写一个实例和案例来对比,更直观理解JNDI的实际场景

实例(以下过程是本地加载实例对象)

pom.xml文件

nihao.java

JndiServer.java

关于Reference类讲解:

其中ReferenceWrapper 类需要继承UnicastRemoteObject类,即实现远程调用其他类

这个程序,开启一个rmi服务,绑定了nihao类,并且只会加载本地nihao类。对外暴露的远程服务是:jndi:rmi://localhost:1099/evil。当远程客户端调用这个服务,nihao类就被初始化,并执行static代码块中的打印功能。

JndiTest.java

先启动远程服务类JndiServer,然后运行JndiTest,控制台打印如下:

从上面的打印结果来看,我们在客户端里面其实就是想输出日志,但是我们通过一些表达式,比如:LOGGER.error("hello,{}","${jndi:rmi://localhost:1099/evil}")来拼接日志信息,日志会进行格式化,在格式化的时候,会查找一些lookup,这里面有如下的lookup:

正好就有jdni这个lookup,所以这里示例最后就根据表达式执行rmi操作。

还可以试试upper格式化操作:

以上就是JNDI的一个简单调用过程

下面我将换成案例来演示,对于不大懂Java的同学看了之后更直观

案例(以下过程是远程加载实例对象)

和上面步骤一样,只是bind的对象不一样

exp.java

为何没有包名,因为我接下来注册服务时Reference.factory的第二个参数不指定路径名

将exp编译一下,可以使用cmd命令javac exp

JndiServer.java

此时我指定了要加载代码的远程地址

JndiTest.java

先启动远程服务类JndiServer,然后在exp.class目录下开启一个web服务,此次我用python开启,然后运行JndiTest,控制台打印如下:

1.2漏洞影响版本log4j-1.2.xlog4j:1.2.17及之前版本log4j-1.2.8log4j:1.2.14log4j:1.2.12log4j:1.2.11log4j:1.2.9log4j:1.2.3log4j-1.2.17log4j:1.2.15log4j:1.2.13log4j:1.2.16log4j:1.2.10log4j:1.2.7log4j:1.2.1log4j-1.3-Alpha (当时的实验版本,已经停止开发)log4j-1.4.xlog4j:1.4.2至1.4.17log4j:1.4.1log4j:1.4log4j-1.5.x及以上版本log4j:1.5.0至1.5.16log4j:1.5.17至1.5.20log4j:1.5.21至1.5.22log4j:1.5.23log4j:1.5.24log4j-1.6.x及以上版本log4j-2.x版本log4j:2.0至2.17 (获取Reference实例-->判断JNDI的绑定对象是否在本地(Naming)-->是在本地就从本地加载--->不是在本地就从远程加载

在RegistryContext类中获得exp类的实例对象

跟进getObjectInstance方法,如果ref不为空,则从ref中获取exp的工厂类

进入getObjectFactoryFromReference方法,先判断本地是否有exp类,有则从本地加载,没有,则从远程加载

继续跟进,进入loadClass方法

class.forName反射调用exp类,且第二个参数为true,则会加载exp类的静态方法。


比丘资源网 » log4j漏洞复现及详细分析

发表回复

提供最优质的资源集合

立即查看 了解详情