diff --git a/src/de/qtc/rmg/internal/CodebaseCollector.java b/src/de/qtc/rmg/internal/CodebaseCollector.java index e3806a0..f5fc1dc 100644 --- a/src/de/qtc/rmg/internal/CodebaseCollector.java +++ b/src/de/qtc/rmg/internal/CodebaseCollector.java @@ -49,6 +49,15 @@ * serialVersionUID. Since changing the serialVersionUID of an already existing class is not possible, we instead * create a new class where the full qualified class name is prefixed with an underscore. * + * From remote-method-guesser v4.5.0, this class has another purpose of handling custom socket factories. When the + * server exposes RMI objects with custom socket factory classes, this usually causes a ClassNotFound error, as + * we do not have the associated implementations on the client side. In this case, remote-method-guesser now attempts + * to create the socket factory class dynamically. Since the implementation is still unknown, it simply clones the + * default socket factory class TrustAllSocketFactory. This works surprisingly often, as most custom socket factory + * classes use simple socket implementations under the hood. This dynamic class creation is done for all classes that + * are unknown and contain "SocketFactory" within their class name or end with "Factory" or "SF". The user can also + * specify other patterns using the --socket-factory option. + * * Summarized: * * 1. Extract server specified codebases and store them within a HashMap for later use @@ -78,31 +87,48 @@ public class CodebaseCollector extends RMIClassLoaderSpi { public Class loadClass(String codebase, String name, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { Class resolvedClass = null; + long serialVersionUID = RMGOption.SERIAL_VERSION_UID.getValue(); addCodebase(codebase, name); codebase = null; + if (serialVersionUIDMap.containsKey(name)) + { + serialVersionUID = serialVersionUIDMap.get(name); + name = "_" + name; + } + try { if (name.endsWith("_Stub")) { - long serialVersionUID = RMGOption.SERIAL_VERSION_UID.getValue(); + RMGUtils.makeLegacyStub(name, serialVersionUID); + } + + else if (name.equals("sun.rmi.server.ActivatableRef")) + { + RMGUtils.makeActivatableRef(); + } - if (serialVersionUIDMap.containsKey(name)) + else if (!RMGOption.SOCKET_FACTORY.isNull()) + { + if (name.contains(RMGOption.SOCKET_FACTORY.getValue())) { - serialVersionUID = serialVersionUIDMap.get(name); - name = "_" + name; + RMGUtils.makeSocketFactory(name, serialVersionUID); } - - RMGUtils.makeLegacyStub(name, serialVersionUID); } - if (name.equals("sun.rmi.server.ActivatableRef")) - RMGUtils.makeActivatableRef(); + else if (name.contains("SocketFactory") || name.endsWith("Factory") || name.endsWith("SF")) + { + RMGUtils.makeSocketFactory(name, serialVersionUID); + } resolvedClass = originalLoader.loadClass(codebase, name, defaultLoader); - } catch (CannotCompileException | NotFoundException e) { + } + + catch (CannotCompileException | NotFoundException e) + { ExceptionHandler.internalError("loadClass", "Unable to compile unknown stub class."); } diff --git a/src/de/qtc/rmg/internal/RMGOption.java b/src/de/qtc/rmg/internal/RMGOption.java index 811032e..70a911b 100644 --- a/src/de/qtc/rmg/internal/RMGOption.java +++ b/src/de/qtc/rmg/internal/RMGOption.java @@ -104,8 +104,8 @@ public enum RMGOption { DGC_METHOD("--dgc-method", "method to use for dgc operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), REG_METHOD("--registry-method", "method to use for registry operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), SERIAL_VERSION_UID("--serial-version-uid", "serialVersionUID to use for RMI stubs", Arguments.store(), RMGOptionGroup.ACTION, "uid"), - PAYLOAD_SERIAL_VERSION_UID("--payload-serial-version-uid", "serialVersionUID to use for payload classes", Arguments.store(), RMGOptionGroup.ACTION, "uid"); - + PAYLOAD_SERIAL_VERSION_UID("--payload-serial-version-uid", "serialVersionUID to use for payload classes", Arguments.store(), RMGOptionGroup.ACTION, "uid"), + SOCKET_FACTORY("--socket-factory", "dynamically create a socket factory class with the specified name", Arguments.store(), RMGOptionGroup.ACTION, "classname"); public final String name; public final String description; diff --git a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java index 96dbd59..0934d40 100644 --- a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java +++ b/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java @@ -159,39 +159,54 @@ public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentExcept { Remote remoteObject = remoteObjectCache.get(boundName); - if( remoteObject == null ) { - - try { + if (remoteObject == null) + { + try + { remoteObject = rmiRegistry.lookup(boundName); remoteObjectCache.put(boundName, remoteObject); + } - } catch( java.rmi.ConnectIOException e ) { + catch (java.rmi.ConnectIOException e) + { ExceptionHandler.connectIOException(e, "lookup"); + } - } catch( java.rmi.ConnectException e ) { + catch (java.rmi.ConnectException e) + { ExceptionHandler.connectException(e, "lookup"); + } - } catch( java.rmi.UnknownHostException e ) { + catch (java.rmi.UnknownHostException e) + { ExceptionHandler.unknownHost(e, host, true); + } - } catch( java.rmi.NoSuchObjectException e ) { + catch (java.rmi.NoSuchObjectException e) + { ExceptionHandler.noSuchObjectException(e, "registry", true); + } - } catch( java.rmi.NotBoundException e ) { + catch (java.rmi.NotBoundException e) + { ExceptionHandler.notBoundException(e, boundName); + } - } catch( Exception e ) { - + catch( Exception e ) + { Throwable cause = ExceptionHandler.getCause(e); if (e instanceof UnmarshalException && cause instanceof InvalidClassException) { InvalidClassException invalidClassException = (InvalidClassException)cause; - if (stopLookupLoop || ! cause.getMessage().contains("serialVersionUID")) + if (stopLookupLoop || !cause.getMessage().contains("serialVersionUID")) + { ExceptionHandler.invalidClassException(invalidClassException); + } - try { + try + { String className = RMGUtils.getClass(invalidClassException); long serialVersionUID = RMGUtils.getSerialVersionUID(invalidClassException); @@ -208,16 +223,24 @@ public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentExcept } else if (e instanceof UnmarshalException && e.getMessage().contains("Transport return code invalid")) + { throw (UnmarshalException)e; + } if( cause instanceof ClassNotFoundException ) + { ExceptionHandler.lookupClassNotFoundException(e, cause.getMessage()); + } else if( cause instanceof SSRFException ) + { SSRFSocket.printContent(host, port); + } else + { ExceptionHandler.unexpectedException(e, "lookup", "call", true); + } } } diff --git a/src/de/qtc/rmg/operations/Operation.java b/src/de/qtc/rmg/operations/Operation.java index 9a1f510..c7f0b78 100644 --- a/src/de/qtc/rmg/operations/Operation.java +++ b/src/de/qtc/rmg/operations/Operation.java @@ -44,6 +44,7 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.SOCKET_FACTORY, }), CALL("dispatchCall", "", "Regularly calls a method with the specified arguments", new RMGOption[] { @@ -69,6 +70,7 @@ public enum Operation { RMGOption.CALL_ARGUMENTS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, }), CODEBASE("dispatchCodebase", " ", "Perform remote class loading attacks", new RMGOption[] { @@ -96,6 +98,7 @@ public enum Operation { RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, RMGOption.PAYLOAD_SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, }), ENUM("dispatchEnum", "[scan-action ...]", "Enumerate common vulnerabilities on Java RMI endpoints", new RMGOption[] { @@ -121,6 +124,7 @@ public enum Operation { RMGOption.ACTIVATION, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, }), GUESS("dispatchGuess", "", "Guess methods on bound names", new RMGOption[] { @@ -150,6 +154,7 @@ public enum Operation { RMGOption.NO_PROGRESS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, }), KNOWN("dispatchKnown", "", "Display details of known remote objects", new RMGOption[] { @@ -201,6 +206,7 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.SOCKET_FACTORY, }), ROGUEJMX("dispatchRogueJMX", "[forward-host]", "Creates a rogue JMX listener (collect credentials)", new RMGOption[] { @@ -258,6 +264,7 @@ public enum Operation { RMGOption.YSO, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, }), UNBIND("dispatchUnbind", "", "Removes the specified bound name from the registry", new RMGOption[] { @@ -276,6 +283,7 @@ public enum Operation { RMGOption.SSRF_STREAM_PROTOCOL, RMGOption.BIND_BOUND_NAME, RMGOption.BIND_BYPASS, + RMGOption.SOCKET_FACTORY, }); private Method method; diff --git a/src/de/qtc/rmg/utils/RMGUtils.java b/src/de/qtc/rmg/utils/RMGUtils.java index 453eb78..8f107b9 100644 --- a/src/de/qtc/rmg/utils/RMGUtils.java +++ b/src/de/qtc/rmg/utils/RMGUtils.java @@ -27,6 +27,7 @@ import de.qtc.rmg.internal.ExceptionHandler; import de.qtc.rmg.internal.MethodArguments; import de.qtc.rmg.internal.MethodCandidate; +import de.qtc.rmg.internal.RMGOption; import de.qtc.rmg.internal.RMIComponent; import de.qtc.rmg.io.Logger; import de.qtc.rmg.io.MaliciousOutputStream; @@ -212,6 +213,44 @@ public static Class makeActivatableRef() throws CannotCompileException return null; } + /** + * Dynamically create a socket factory class that implements RMIClientSocketFactory. This function is used when + * the RMI server uses a custom socket factory class. In this case, rmg attempts to connect with it's default + * TrustAllSocketFactory, which works if the custom socket factory provided by the server is not too different. + * + * To achieve this, rmg just clones TrustAllSocketFactory and assigns it a new name. As in the case of Stub + * classes with unusual serialVersionUIDs, the serialVersionUID is determined error based. The factory is first + * created using a default serialVersionUID. This should cause an exception revealing the actual serialVersionUID. + * This is then used to recreate the class. + * + * Check the CodebaseCollector class documentation for more information. + * + * @return socket factory class that implements RMIClientSocketFactory + * @throws CannotCompileException + */ + public static Class makeSocketFactory(String className, long serialVersionUID) throws CannotCompileException + { + try + { + return Class.forName(className); + } + + catch (ClassNotFoundException e) {} + + CtClass ctClass = null; + + try + { + ctClass = pool.getAndRename("de.qtc.rmg.networking.TrustAllSocketFactory", className); + ctClass.addInterface(serializable); + addSerialVersionUID(ctClass, serialVersionUID); + } + + catch (NotFoundException e) {} + + return ctClass.toClass(); + } + /** * 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