Skip to content

Commit

Permalink
Add first draft for activation support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
qtc-de committed May 5, 2022
1 parent 2bb957f commit 21d91d0
Show file tree
Hide file tree
Showing 14 changed files with 492 additions and 132 deletions.
20 changes: 19 additions & 1 deletion src/de/qtc/rmg/internal/CodebaseCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand All @@ -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) {
Expand All @@ -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
{
Expand Down
2 changes: 2 additions & 0 deletions src/de/qtc/rmg/internal/RMGOption.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
49 changes: 44 additions & 5 deletions src/de/qtc/rmg/io/Formatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -57,7 +59,7 @@ public void listBoundNames(RemoteObjectWrapper[] remoteObjects)
Logger.printlnPlainMixedPurple("", "(unknown class)");
}

printLiveRef(remoteObject);
printRemoteRef(remoteObject);
Logger.decreaseIndent();
}

Expand Down Expand Up @@ -236,12 +238,27 @@ private void listVulnerabilities(List<Vulnerability> 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;
Expand All @@ -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);
}
}
2 changes: 1 addition & 1 deletion src/de/qtc/rmg/networking/RMIRegistryEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 30 additions & 1 deletion src/de/qtc/rmg/operations/ActivationClient.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
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;
import de.qtc.rmg.internal.RMIComponent;
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
Expand Down Expand Up @@ -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<? extends Remote>) 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);
}
}
5 changes: 3 additions & 2 deletions src/de/qtc/rmg/operations/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -549,6 +549,7 @@ public void dispatchEnum()
public void dispatchGuess()
{
Formatter format = new Formatter();
UnicastWrapper[] wrappers = RemoteObjectWrapper.getUnicastWrappers(remoteObjects);

try {
obtainBoundObjects();
Expand All @@ -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<RemoteObjectClient> results = guesser.guessMethods();
Expand Down
15 changes: 8 additions & 7 deletions src/de/qtc/rmg/operations/MethodGuesser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<MethodCandidate> candidates)
public MethodGuesser(UnicastWrapper[] remoteObjects, Set<MethodCandidate> candidates)
{
this.candidates = candidates;

Expand All @@ -82,15 +83,15 @@ public MethodGuesser(RemoteObjectWrapper[] remoteObjects, Set<MethodCandidate> c
*
* @param remoteObjects Array of looked up remote objects from the RMI registry
*/
private List<RemoteObjectClient> initClientList(RemoteObjectWrapper[] remoteObjects)
private List<RemoteObjectClient> initClientList(UnicastWrapper[] remoteObjects)
{
List<RemoteObjectClient> remoteObjectClients = new ArrayList<RemoteObjectClient>();
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);
Expand Down Expand Up @@ -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<RemoteObjectWrapper> unknown = new ArrayList<RemoteObjectWrapper>();

for(RemoteObjectWrapper o : remoteObjects) {
for(UnicastWrapper o : remoteObjects) {

if(!o.isKnown())
unknown.add(o);
Expand Down Expand Up @@ -204,7 +205,7 @@ private RemoteObjectWrapper[] handleKnownMethods(RemoteObjectWrapper[] remoteObj
Logger.enable();
}

return unknown.toArray(new RemoteObjectWrapper[0]);
return unknown.toArray(new UnicastWrapper[0]);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/de/qtc/rmg/operations/Operation.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public enum Operation {
RMGOption.SSRF_RAW,
RMGOption.SSRF_STREAM_PROTOCOL,
RMGOption.CALL_ARGUMENTS,
RMGOption.FORCE_ACTIVATION,
}),

CODEBASE("dispatchCodebase", "<classname> <url>", "Perform remote class loading attacks", new RMGOption[] {
Expand All @@ -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[] {
Expand All @@ -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[] {
Expand Down Expand Up @@ -140,6 +144,7 @@ public enum Operation {
RMGOption.GUESS_ZERO_ARG,
RMGOption.THREADS,
RMGOption.NO_PROGRESS,
RMGOption.FORCE_ACTIVATION,
}),

KNOWN("dispatchKnown", "<className>", "Display details of known remote objects", new RMGOption[] {
Expand Down Expand Up @@ -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[] {
Expand Down
7 changes: 4 additions & 3 deletions src/de/qtc/rmg/operations/RemoteObjectClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -83,15 +84,15 @@ 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;
this.boundName = remoteObject.boundName;
this.remoteObject = remoteObject;
this.remoteMethods = Collections.synchronizedList(new ArrayList<MethodCandidate>());

remoteRef = remoteObject.remoteRef;
remoteRef = remoteObject.unicastRef;
}

/**
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 20 additions & 0 deletions src/de/qtc/rmg/plugin/PluginSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading

0 comments on commit 21d91d0

Please sign in to comment.