mirror of
https://github.com/BishopFox/rmiscout.git
synced 2026-11-12 18:16:31 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf05aa1709 | ||
|
|
f2ea8cb5f5 | ||
|
|
831d81bff1 |
No files matched your search
@@ -51,7 +51,7 @@ Read a [full technical writeup here](https://know.bishopfox.com/research/rmiscou
|
||||
|
||||
```
|
||||
# Perform wordlist-attack against remote RMI service using wordlist of function prototypes
|
||||
./rmiscout.sh wordlist -i prototypes.txt <host> <port>
|
||||
./rmiscout.sh wordlist -i lists/prototypes.txt <host> <port>
|
||||
|
||||
# Bruteforce using method wordlist and other options
|
||||
./rmiscout.sh bruteforce -i lists/methods.txt -r void,boolean,long -p String,int -l 1,4 <host> <port>
|
||||
@@ -68,7 +68,7 @@ Read a [full technical writeup here](https://know.bishopfox.com/research/rmiscou
|
||||
|
||||
Use the included `rmiscout.sh` script to automatically build the project and as a convenient wrapper around `java -jar` syntax:
|
||||
```bash
|
||||
./rmiscout.sh wordlist -i prototypes.txt <host> <port>
|
||||
./rmiscout.sh wordlist -i lists/prototypes.txt <host> <port>
|
||||
```
|
||||
|
||||
Alternatively, build the project manually and use traditional `java -jar` syntax:
|
||||
@@ -77,7 +77,7 @@ Alternatively, build the project manually and use traditional `java -jar` syntax
|
||||
# Manually build JAR
|
||||
./gradlew shadowJar
|
||||
|
||||
java -jar build/libs/rmiscout-1.0-SNAPSHOT-all.jar wordlist -i prototypes.txt <host> <port>
|
||||
java -jar build/libs/rmiscout-1.01-SNAPSHOT-all.jar wordlist -i lists/prototypes.txt <host> <port>
|
||||
```
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'com.bishopfox.rmiscout'
|
||||
version '1.0-SNAPSHOT'
|
||||
version '1.01-SNAPSHOT'
|
||||
|
||||
mainClassName = 'com.bishopfox.rmiscout.RMIScout'
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ ! -f build/libs/rmiscout-1.0-SNAPSHOT-all.jar ]]; then
|
||||
if [[ ! -f build/libs/rmiscout-1.01-SNAPSHOT-all.jar ]]; then
|
||||
./gradlew shadowJar
|
||||
fi
|
||||
java -jar build/libs/rmiscout-1.0-SNAPSHOT-all.jar "$@"
|
||||
java -jar build/libs/rmiscout-1.01-SNAPSHOT-all.jar "$@"
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.bishopfox.rmiscout;
|
||||
|
||||
import javassist.*;
|
||||
import javassist.Modifier;
|
||||
import javassist.bytecode.DuplicateMemberException;
|
||||
import sun.misc.Unsafe;
|
||||
import ysoserial.payloads.ObjectPayload;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.rmi.*;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
@@ -23,6 +23,7 @@ public class RMIConnector {
|
||||
private boolean allowUnsafe;
|
||||
private Class dummyClass;
|
||||
private static final int parameterTypesOffset = 52;
|
||||
private boolean isActivationServer;
|
||||
|
||||
|
||||
private static class Colors {
|
||||
@@ -49,9 +50,11 @@ public class RMIConnector {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public RMIConnector(String host, int port, String remoteName, List<String> signatures, boolean allowUnsafe) {
|
||||
public RMIConnector(String host, int port, String remoteName, List<String> signatures, boolean allowUnsafe, boolean isActivationServer) {
|
||||
try {
|
||||
this.allowUnsafe = allowUnsafe;
|
||||
this.isActivationServer = isActivationServer;
|
||||
|
||||
this.registry = LocateRegistry.getRegistry(host, port);
|
||||
this.remoteRefs = new HashMap<>();
|
||||
|
||||
@@ -76,17 +79,60 @@ public class RMIConnector {
|
||||
|
||||
// For each registry create interface bound with test methods
|
||||
for (String regName : regNames) {
|
||||
CtClass nInterface = defaultClassPool.makeInterface(regName, remoteInterface);
|
||||
|
||||
// Get remote interface name from exception
|
||||
String interfaceName = "";
|
||||
try {
|
||||
registry.lookup(regName);
|
||||
} catch (UnmarshalException e) {
|
||||
if (e.detail instanceof ClassNotFoundException) {
|
||||
interfaceName = e.detail.getMessage().split(" ")[0];
|
||||
} else {
|
||||
System.err.println("Error retrieving remote interface className for name '" + regName + "'");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
String stubName = "";
|
||||
if (isActivationServer) {
|
||||
stubName = interfaceName;
|
||||
interfaceName = "ActivationStub";
|
||||
}
|
||||
|
||||
CtClass nInterface = defaultClassPool.makeInterface(interfaceName, remoteInterface);
|
||||
generateStubs(nInterface, signatures);
|
||||
|
||||
if (isActivationServer) {
|
||||
// Example ActivationStub
|
||||
//
|
||||
// All stubs have same hardcoded serialVersionUID = 2; https://bugs.openjdk.java.net/browse/JDK-4066716
|
||||
//
|
||||
// public class MyClass_Stub extends RemoteStub implements Remote
|
||||
// {
|
||||
// private static final long serialVersionUID = 2L;
|
||||
// }
|
||||
|
||||
CtClass activationStub = defaultClassPool.makeClass(stubName, defaultClassPool.getCtClass(RemoteStub.class.getName()));
|
||||
activationStub.setInterfaces(new CtClass[]{nInterface});
|
||||
|
||||
CtField f = new CtField(CtPrimitiveType.longType, "serialVersionUID", activationStub);
|
||||
f.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
|
||||
activationStub.addField(f, CtField.Initializer.constant(2L));
|
||||
activationStub.toClass(customClassLoader);
|
||||
}
|
||||
|
||||
originalClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
Thread.currentThread().setContextClassLoader(customClassLoader);
|
||||
this.remoteRefs.put(regName, registry.lookup(regName));
|
||||
this.remoteRefs.put(interfaceName, registry.lookup(regName));
|
||||
}
|
||||
|
||||
} catch (RemoteException e) {
|
||||
System.err.println(e.getMessage());
|
||||
if (e.getMessage().contains("class invalid for deserialization")) {
|
||||
System.err.println("RMI Activation Server detected. Re-run with --activation-server");
|
||||
} else {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -230,9 +276,19 @@ public class RMIConnector {
|
||||
Arrays.fill(fakeParameterTypes, payload.getClass());
|
||||
|
||||
// Bypass internal call flow for custom params
|
||||
Field f = Proxy.class.getDeclaredField("h");
|
||||
f.setAccessible(true);
|
||||
RemoteObjectInvocationHandler ref = (RemoteObjectInvocationHandler) f.get(stub);
|
||||
RemoteRef ref = null;
|
||||
if (this.isActivationServer) {
|
||||
Field f = RemoteObject.class.getDeclaredField("ref");
|
||||
f.setAccessible(true);
|
||||
ref = (RemoteRef) f.get(stub);
|
||||
} else {
|
||||
Field f = Proxy.class.getDeclaredField("h");
|
||||
f.setAccessible(true);
|
||||
ref = ((RemoteObjectInvocationHandler) f.get(stub)).getRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Calculate MethodHash
|
||||
Method m = RemoteObjectInvocationHandler.class.getDeclaredMethod("getMethodHash", Method.class);
|
||||
@@ -254,7 +310,7 @@ public class RMIConnector {
|
||||
}
|
||||
|
||||
// Invoke remote method
|
||||
Object response = ref.getRef().invoke(stub, me, params, methodHash);
|
||||
Object response = ref.invoke(stub, me, params, methodHash);
|
||||
|
||||
// This next line should only occur when allowUnsafe or when launching exploits
|
||||
System.out.println("Executed Payload: " + methodSignature);
|
||||
@@ -306,6 +362,10 @@ public class RMIConnector {
|
||||
try {
|
||||
CtClass ctClass = defaultClassPool.getCtClass(registryName);
|
||||
ctClass.detach();
|
||||
|
||||
if (this.isActivationServer) {
|
||||
defaultClassPool.getCtClass(pair.getValue().getClass().getName()).detach();
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -90,11 +90,14 @@ public class RMIScout {
|
||||
|
||||
wordlistMode.addArgument("-i", "--input").type(File.class).required(true).help("Wordlist of function prototypes");
|
||||
wordlistMode.addArgument("--allow-unsafe").setDefault(false).action(Arguments.storeTrue()).help("Allow execution of void function guesses.");
|
||||
wordlistMode.addArgument("--activation-server").setDefault(false).action(Arguments.storeTrue()).help("Support for deprecated RMI Activation Systems");
|
||||
wordlistMode.addArgument("-n", "--registry-name").help(registryHelp);
|
||||
wordlistMode.addArgument("host").help(hostHelp);
|
||||
wordlistMode.addArgument("port").type(Integer.class).help(portHelp);
|
||||
|
||||
bruteforceMode.addArgument("-i", "--input").type(File.class).required(true).help("Wordlist of candidate method names");
|
||||
bruteforceMode.addArgument("--allow-unsafe").setDefault(false).action(Arguments.storeTrue()).help("Allow execution of void function guesses.");
|
||||
bruteforceMode.addArgument("--activation-server").setDefault(false).action(Arguments.storeTrue()).help("Support for deprecated RMI Activation Systems");
|
||||
bruteforceMode.addArgument("-r", "--return-types").required(true).help("Set of candidate return types");
|
||||
bruteforceMode.addArgument("-p", "--parameter-types").required(true).help("Set of candidate parameter types");
|
||||
bruteforceMode.addArgument("-l", "--parameter-length").required(true).help("Candidate parameter length range expressed as a comma-delimited range EX: 1,4");
|
||||
@@ -128,7 +131,7 @@ public class RMIScout {
|
||||
// Split by types
|
||||
HashMap<String, ArrayList<String>> sigMap = readFileToMap(ns.get("input"));
|
||||
for (Map.Entry<String, ArrayList<String>> entry : sigMap.entrySet()) {
|
||||
process(ns.get("host"), ns.get("port"), ns.get("registry_name"), entry.getValue(), ns.get("allow_unsafe"));
|
||||
process(ns.get("host"), ns.get("port"), ns.get("registry_name"), entry.getValue(), ns.get("allow_unsafe"), ns.get("activation_server"));
|
||||
}
|
||||
break;
|
||||
case "bruteforce":
|
||||
@@ -150,7 +153,7 @@ public class RMIScout {
|
||||
// Java Interfaces disallow different return types for otherwise identical prototypes
|
||||
for (int i = 0; i < returnTypes.length; i++) {
|
||||
signatures = generateMethods(ns.get("input"), returnTypes[i], paramRange, paramTypes);
|
||||
process(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, false);
|
||||
process(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, ns.get("allow_unsafe"), ns.get("activation_server"));
|
||||
}
|
||||
break;
|
||||
case "exploit":
|
||||
@@ -158,7 +161,7 @@ public class RMIScout {
|
||||
signatures = new ArrayList<>();
|
||||
signatures.add(ns.get("signature"));
|
||||
|
||||
rmisearch = new RMIConnector(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, false);
|
||||
rmisearch = new RMIConnector(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, false, ns.get("activation_server"));
|
||||
rmisearch.exploit(ns.get("payload"), ns.get("command"));
|
||||
rmisearch.cleanup();
|
||||
break;
|
||||
@@ -171,7 +174,7 @@ public class RMIScout {
|
||||
GadgetProbe gp = new GadgetProbe(ns.get("dns_endpoint"));
|
||||
List<String> classnames = readFileToList(ns.get("input"));
|
||||
|
||||
rmisearch = new RMIConnector(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, false);
|
||||
rmisearch = new RMIConnector(ns.get("host"), ns.get("port"), ns.get("registry_name"), signatures, false, ns.get("activation_server"));
|
||||
for (String classname : classnames) {
|
||||
rmisearch.execute(gp.getObject(classname));
|
||||
}
|
||||
@@ -184,13 +187,13 @@ public class RMIScout {
|
||||
}
|
||||
}
|
||||
|
||||
private static void process(String host, int port, String registryName, List<String> signatures, boolean allowUnsafe) {
|
||||
private static void process(String host, int port, String registryName, List<String> signatures, boolean allowUnsafe, boolean isActivationServer) {
|
||||
// Perform batches of 1,000 dynamic methods to not exceed codesize limit of Proxy interface
|
||||
final int batch_size = 1000;
|
||||
for (int i = 0; i < (signatures.size()/batch_size)+1; i++) {
|
||||
int lower = i*batch_size;
|
||||
int upper = Integer.min(signatures.size(), (i+1)*batch_size);
|
||||
RMIConnector rmisearch = new RMIConnector(host, port, registryName, signatures.subList(lower, upper), allowUnsafe);
|
||||
RMIConnector rmisearch = new RMIConnector(host, port, registryName, signatures.subList(lower, upper), allowUnsafe, isActivationServer);
|
||||
rmisearch.checkIfPresent();
|
||||
rmisearch.cleanup();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user