diff --git a/.gitignore b/.gitignore index 85f2342d..87e85b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ buildNumber.properties .project .settings/ .internal/ +*.swp +*.bak diff --git a/CHANGELOG.md b/CHANGELOG.md index e6bd2208..4855130b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ 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] - MMM DD, 2023 + +### Added + +* Add support for dynamically created socket factory classes ([docs](/docs/rmg/dynamic-socket-factories.md)) +* Add support for method guessing on spring-remoting endpoints ([docs](/docs/rmg/spring-remoting.md)) +* Add a *Spring Remoting* example server ([src](docker/spring-remoting/), [package](https://github.com/qtc-de/remote-method-guesser/pkgs/container/remote-method-guesser%2Fspring-remoting-server)) + +### Changed + +* Changed the namespace of the project from `de.qtc` to `eu.tneitzel` +* Fix leak of local ysoserial path (e30f52c) +* The GenericPrint plugin is now included in *rmg* per default (b09e9a5) +* Stream corruption errors during method guessing are only displayed if `--verbose` is used + + ## [4.4.1] - Jun 22, 2023 ### Added diff --git a/README.md b/README.md index e49a6a1e..487d3579 100644 --- a/README.md +++ b/README.md @@ -140,13 +140,13 @@ bind operations. When using the ``bind`` or ``rebind`` action *remote-method-gue [+] RMI registry bound names: [+] [+] - plain-server2 -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711] [+] - legacy-service -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813] [qtc@devbox ~]$ rmg bind 172.17.0.2 9010 127.0.0.1:4444 my-object --localhost-bypass @@ -159,16 +159,16 @@ bind operations. When using the ``bind`` or ``rebind`` action *remote-method-gue [+] RMI registry bound names: [+] [+] - plain-server2 -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711] [+] - my-object [+] --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server) [+] Endpoint: 127.0.0.1:4444 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] [+] - legacy-service -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813] ``` @@ -302,20 +302,20 @@ page](./docs/rmg/actions.md#enum). [+] RMI registry bound names: [+] [+] - plain-server2 -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff7, -3079588349672331489] [+] - legacy-service -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) [+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ffc, -2969569395601583761] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff8, 1319708214331962145] [+] [+] RMI server codebase enumeration: [+] [+] - http://iinsecure.example/well-hidden-development-folder/ -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub -[+] --> de.qtc.rmg.server.interfaces.IPlainServer +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer [+] [+] RMI server String unmarshalling enumeration: [+] @@ -500,13 +500,13 @@ for each *bound name* and *remote-method-guesser* displays them during the ``enu [+] RMI registry bound names: [+] [+] - plain-server2 -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646] [+] - legacy-service -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) [+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ffc, 625759208507801754] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff8, -6355415622579283910] ``` diff --git a/docker/example-server/CHANGELOG.md b/docker/example-server/CHANGELOG.md index 0c4430c1..6825c4f2 100644 --- a/docker/example-server/CHANGELOG.md +++ b/docker/example-server/CHANGELOG.md @@ -6,7 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4.0] - Jan 19, 2023 +## v5.0.0 - Dec 10, 2023 + +### Added + +* Add `custom-socks` bound name to registry on port `9010` which uses a custom socket factory + +### Changed + +* Changed the servers namespace from `de.qtc` to `eu.tneitzel` +* Since Java 9 is no longer available in the alpine default package + repositories, the JDK is now obtained from an older image of the + example-server. + + +## v4.0.0 - Jan 19, 2023 ### Added @@ -19,15 +33,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Change server certificate -## [3.3] - May 08, 2022 +## v3.3.0 - May 08, 2022 ### Changed * Fix timestamp for log messages -## [3.2] - May 06, 2022 +## v3.2.0 - May 06, 2022 ### Changed * Add activation system on port 1098 + + +## v3.1.0 and before + +Changelog entries can be found within the global [CHANGELOG.md](/CHANGELOG.md) file +of remote-method-guesser. diff --git a/docker/example-server/Dockerfile-jdk9 b/docker/example-server/Dockerfile-jdk9 index b77c36d8..954c146a 100644 --- a/docker/example-server/Dockerfile-jdk9 +++ b/docker/example-server/Dockerfile-jdk9 @@ -9,11 +9,9 @@ RUN mvn clean package ########################################### ### Build Stage 2 ### ########################################### -FROM alpine:latest AS jdk-builder +FROM ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk9 AS jdk-builder RUN set -ex \ - && apk add --no-cache openjdk9 \ - && /usr/lib/jvm/java-9-openjdk/bin/jlink --add-modules java.rmi,java.management.rmi,jdk.unsupported --verbose --strip-debug --compress 2 \ - --no-header-files --no-man-pages --module-path /usr/lib/jvm/java-9-openjdk/jmods/ --output /jdk + && mv /usr/lib/jvm/java-9-openjdk /jdk ########################################### ### Container Stage ### diff --git a/docker/example-server/README.md b/docker/example-server/README.md index 994b4b1b..0ee750be 100644 --- a/docker/example-server/README.md +++ b/docker/example-server/README.md @@ -34,21 +34,21 @@ The registry on port `1090` is *SSL* protected and contains three available boun [+] RMI registry bound names: [+] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7fff, 8831379559932805383] [+] - ssl-server -[+] --> de.qtc.rmg.server.interfaces.ISslServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.ISslServer (unknown class) [+] Endpoint: iinsecure.example:42031 TLS: yes ObjID: [-492549a8:1809adab6bf:-7ffe, -8819602238278920745] [+] - secure-server -[+] --> de.qtc.rmg.server.interfaces.ISecureServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.ISecureServer (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ffd, -5037949272481440924] [+] [+] RMI server codebase enumeration: [+] [+] - http://iinsecure.example/well-hidden-development-folder/ -[+] --> de.qtc.rmg.server.interfaces.ISslServer -[+] --> de.qtc.rmg.server.interfaces.IPlainServer -[+] --> de.qtc.rmg.server.interfaces.ISecureServer +[+] --> eu.tneitzel.rmg.server.interfaces.ISslServer +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer +[+] --> eu.tneitzel.rmg.server.interfaces.ISecureServer [+] [+] RMI server String unmarshalling enumeration: [+] @@ -96,13 +96,13 @@ The registry on port `1098` hosts an *Activation System* and has some *activatab [+] RMI registry bound names: [+] [+] - activation-test -[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: -492549a8:1809adab6bf:-7ff1 [+] - activation-test2 -[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: -492549a8:1809adab6bf:-7fee [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7fec, 5541025679742310482] [+] - java.rmi.activation.ActivationSystem [+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activator) @@ -111,10 +111,10 @@ The registry on port `1098` hosts an *Activation System* and has some *activatab [+] RMI server codebase enumeration: [+] [+] - http://iinsecure.example/well-hidden-development-folder/ -[+] --> de.qtc.rmg.server.interfaces.IPlainServer -[+] --> de.qtc.rmg.server.activation.IActivationService +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer +[+] --> eu.tneitzel.rmg.server.activation.IActivationService [+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub -[+] --> de.qtc.rmg.server.activation.IActivationService2 +[+] --> eu.tneitzel.rmg.server.activation.IActivationService2 [+] [+] RMI server String unmarshalling enumeration: [+] @@ -167,20 +167,20 @@ registry port binds an *RMI Activator instance*, but not a full working *Activat [+] RMI registry bound names: [+] [+] - plain-server2 -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ff7, 8893583921173173865] [+] - legacy-service -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ffc, -5452660335673756521] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:40579 TLS: no ObjID: [-492549a8:1809adab6bf:-7ff8, 5860842907020657289] [+] [+] RMI server codebase enumeration: [+] [+] - http://iinsecure.example/well-hidden-development-folder/ -[+] --> de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub -[+] --> de.qtc.rmg.server.interfaces.IPlainServer +[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer [+] [+] RMI server String unmarshalling enumeration: [+] @@ -230,8 +230,8 @@ to enable *codebase logging*: ```yaml environment: [...] - -Djava.rmi.server.RMIClassLoaderSpi=de.qtc.rmg.server.utils.CodebaseLogger - -Dde.qtc.rmg.server.disableColor=true + -Djava.rmi.server.RMIClassLoaderSpi=eu.tneitzel.rmg.server.utils.CodebaseLogger + -Deu.tneitzel.rmg.server.disableColor=true ``` Each successful method call is logged on the server side. The following listing shows the output after the server @@ -265,7 +265,7 @@ Picked up _JAVA_OPTIONS: -Djava.rmi.server.hostname=iinsecure.example -Djava [2022.05.06 - 19:45:12] [2022.05.06 - 19:45:12] Creating LegacyServiceImpl object. [2022.05.06 - 19:45:12] Binding LegacyServiceImpl as legacy-service -[2022.05.06 - 19:45:12] Boundname legacy-service with class de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub is ready. +[2022.05.06 - 19:45:12] Boundname legacy-service with class eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub is ready. [2022.05.06 - 19:45:12] Creating PlainServer object. [2022.05.06 - 19:45:12] Binding Object as plain-server [2022.05.06 - 19:45:12] Boundname plain-server with interface IPlainServer is ready. diff --git a/docker/example-server/docker-compose-jdk11.yml b/docker/example-server/docker-compose-jdk11.yml index 95241cff..8e997604 100644 --- a/docker/example-server/docker-compose-jdk11.yml +++ b/docker/example-server/docker-compose-jdk11.yml @@ -1,8 +1,8 @@ version: '3.7' services: - rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk11 + example-server: + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk11 build: . environment: - > @@ -16,8 +16,8 @@ services: -Djava.rmi.server.codebase=http://iinsecure.example/well-hidden-development-folder/ - > DELETE_THIS_AND_THE_ABOVE_LINE_TO_ENABLE_CODEBASE_LOGGING_OR_TO_DISABLE_COLOR= - -Djava.rmi.server.RMIClassLoaderSpi=de.qtc.rmg.server.utils.CodebaseLogger - -Dde.qtc.rmg.server.disableColor=true + -Djava.rmi.server.RMIClassLoaderSpi=eu.tneitzel.rmg.server.utils.CodebaseLogger + -Deu.tneitzel.rmg.server.disableColor=true volumes: - ./resources/trust/store.p12:/opt/store.p12 - ./resources/conf/policy:/opt/policy diff --git a/docker/example-server/docker-compose-jdk8.yml b/docker/example-server/docker-compose-jdk8.yml index 13af7afb..4b978edf 100644 --- a/docker/example-server/docker-compose-jdk8.yml +++ b/docker/example-server/docker-compose-jdk8.yml @@ -1,8 +1,8 @@ version: '3.7' services: - rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk8 + example-server: + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk8 build: . environment: - > @@ -16,8 +16,8 @@ services: -Djava.rmi.server.codebase=http://iinsecure.example/well-hidden-development-folder/ - > DELETE_THIS_AND_THE_ABOVE_LINE_TO_ENABLE_CODEBASE_LOGGING_OR_TO_DISABLE_COLOR= - -Djava.rmi.server.RMIClassLoaderSpi=de.qtc.rmg.server.utils.CodebaseLogger - -Dde.qtc.rmg.server.disableColor=true + -Djava.rmi.server.RMIClassLoaderSpi=eu.tneitzel.rmg.server.utils.CodebaseLogger + -Deu.tneitzel.rmg.server.disableColor=true volumes: - ./resources/trust/store.p12:/opt/store.p12 - ./resources/conf/policy:/opt/policy diff --git a/docker/example-server/docker-compose-jdk9.yml b/docker/example-server/docker-compose-jdk9.yml index f979b62e..f5124973 100644 --- a/docker/example-server/docker-compose-jdk9.yml +++ b/docker/example-server/docker-compose-jdk9.yml @@ -1,8 +1,8 @@ version: '3.7' services: - rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk9 + example-server: + image: ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk9 build: . environment: - > @@ -16,8 +16,8 @@ services: -Djava.rmi.server.codebase=http://iinsecure.example/well-hidden-development-folder/ - > DELETE_THIS_AND_THE_ABOVE_LINE_TO_ENABLE_CODEBASE_LOGGING_OR_TO_DISABLE_COLOR= - -Djava.rmi.server.RMIClassLoaderSpi=de.qtc.rmg.server.utils.CodebaseLogger - -Dde.qtc.rmg.server.disableColor=true + -Djava.rmi.server.RMIClassLoaderSpi=eu.tneitzel.rmg.server.utils.CodebaseLogger + -Deu.tneitzel.rmg.server.disableColor=true volumes: - ./resources/trust/store.p12:/opt/store.p12 - ./resources/conf/policy:/opt/policy diff --git a/docker/example-server/resources/server/pom.xml b/docker/example-server/resources/server/pom.xml index c3694b52..848cb7f5 100644 --- a/docker/example-server/resources/server/pom.xml +++ b/docker/example-server/resources/server/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - de.qtc.rmg.server.ExampleServer + eu.tneitzel.rmg.server.ExampleServer rmg-example-server - 3.3.0 + 4.1.0 rmg-example-server RMG Example Server @@ -47,8 +47,13 @@ rmg-example-server-${project.version} - de.qtc.rmg.server.ExampleServer + eu.tneitzel.rmg.server.ExampleServer + + + Tobias Neitzel (@qtc_de) + + jar-with-dependencies diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java deleted file mode 100644 index 39dc38fa..00000000 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/ExampleServer.java +++ /dev/null @@ -1,84 +0,0 @@ -package de.qtc.rmg.server; - -import java.rmi.AlreadyBoundException; -import java.rmi.NotBoundException; -import java.rmi.Remote; -import java.rmi.RemoteException; -import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; -import java.rmi.server.UnicastRemoteObject; - -import javax.rmi.ssl.SslRMIClientSocketFactory; -import javax.rmi.ssl.SslRMIServerSocketFactory; - -import de.qtc.rmg.server.activation.ActivationServer; -import de.qtc.rmg.server.interfaces.IPlainServer; -import de.qtc.rmg.server.interfaces.ISecureServer; -import de.qtc.rmg.server.interfaces.ISslServer; -import de.qtc.rmg.server.legacy.LegacyServer; -import de.qtc.rmg.server.operations.PlainServer; -import de.qtc.rmg.server.operations.SecureServer; -import de.qtc.rmg.server.operations.SslServer; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; - -public class ExampleServer { - - private static int registryPort = 1090; - private static Remote remoteObject1 = null; - private static Remote remoteObject2 = null; - private static Remote remoteObject3 = null; - - public static void main(String[] argv) - { - String disableColor = System.getProperty("de.qtc.rmg.server.disableColor"); - if (disableColor != null && disableColor.equalsIgnoreCase("true")) - Logger.disableColor(); - - Logger.println("Initializing Java RMI Server:"); - Logger.println(""); - Logger.increaseIndent(); - - if (System.getSecurityManager() == null) { - System.setSecurityManager(new SecurityManager()); - } - - try { - SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); - SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); - - Logger.printMixedBlue("Creating", "RMI-Registry", "on port "); - Logger.printlnPlainYellow(String.valueOf(registryPort)); - Registry registry = LocateRegistry.createRegistry(registryPort, csf, ssf); - Logger.println(""); - - Logger.printlnMixedBlue("Creating", "PlainServer", "object."); - remoteObject1 = new PlainServer(); - IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject1, 0); - Utils.bindToRegistry(stub, registry, "plain-server"); - - Logger.printlnMixedBlue("Creating", "SSLServer", "object."); - remoteObject2 = new SslServer(); - ISslServer stub2 = (ISslServer)UnicastRemoteObject.exportObject(remoteObject2, 0, csf, ssf); - Utils.bindToRegistry(stub2, registry, "ssl-server"); - - Logger.printlnMixedBlue("Creating", "SecureServer", "object."); - remoteObject3 = new SecureServer(); - ISecureServer stub3 = (ISecureServer)UnicastRemoteObject.exportObject(remoteObject3, 0); - Utils.bindToRegistry(stub3, registry, "secure-server"); - - Logger.decreaseIndent(); - Logger.println(""); - Logger.println("Server setup finished."); - Logger.println("Initializing legacy server."); - Logger.println(""); - - LegacyServer.init(); - ActivationServer.init(); - - } catch (RemoteException | AlreadyBoundException | NotBoundException e) { - Logger.eprintln("Unexpected RMI Error:"); - e.printStackTrace(); - } - } -} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/StringContainer.java b/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/StringContainer.java deleted file mode 100644 index 1bfc48a8..00000000 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/StringContainer.java +++ /dev/null @@ -1,4 +0,0 @@ -package de.qtc.rmg.server.legacy; - -public class StringContainer { -} diff --git a/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/ExampleServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/ExampleServer.java new file mode 100644 index 00000000..cc6ba7e1 --- /dev/null +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/ExampleServer.java @@ -0,0 +1,114 @@ +package eu.tneitzel.rmg.server; + +import java.rmi.AlreadyBoundException; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; + +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import eu.tneitzel.rmg.server.activation.ActivationServer; +import eu.tneitzel.rmg.server.factory.CustomSocketFactoryServer; +import eu.tneitzel.rmg.server.interfaces.IPlainServer; +import eu.tneitzel.rmg.server.interfaces.ISecureServer; +import eu.tneitzel.rmg.server.interfaces.ISslServer; +import eu.tneitzel.rmg.server.legacy.LegacyServer; +import eu.tneitzel.rmg.server.operations.PlainServer; +import eu.tneitzel.rmg.server.operations.SecureServer; +import eu.tneitzel.rmg.server.operations.SslServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; + +public class ExampleServer +{ + private static final int registryPort = 1090; + private static final int activatorPort = 1098; + private static final int plainRegistryPort = 9010; + + private static Remote remoteObjectOne; + private static Remote remoteObjectTwo; + private static Remote remoteObjectThree; + + private static final String boundNameOne = "plain-server"; + private static final String boundNameTwo = "ssl-server"; + private static final String boundNameThree = "secure-server"; + + public static void main(String[] argv) + { + String disableColor = System.getProperty("eu.tneitzel.rmg.server.disableColor"); + + if (disableColor != null && disableColor.equalsIgnoreCase("true")) + { + Logger.disableColor(); + } + + Logger.println("Initializing Java RMI Server:"); + Logger.println(""); + Logger.increaseIndent(); + + if (System.getSecurityManager() == null) + { + System.setSecurityManager(new SecurityManager()); + } + + try + { + SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory(); + SslRMIServerSocketFactory ssf = new SslRMIServerSocketFactory(); + + Logger.printMixedBlue("Creating", "RMI-Registry", "on port "); + Logger.printlnPlainYellow(String.valueOf(registryPort)); + Registry registry = LocateRegistry.createRegistry(registryPort, csf, ssf); + Logger.println(""); + + Logger.printlnMixedBlue("Creating", "PlainServer", "object."); + remoteObjectOne = new PlainServer(); + IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObjectOne, 0); + Utils.bindToRegistry(stub, registry, boundNameOne); + + Logger.printlnMixedBlue("Creating", "SSLServer", "object."); + remoteObjectTwo = new SslServer(); + ISslServer stub2 = (ISslServer)UnicastRemoteObject.exportObject(remoteObjectTwo, 0, csf, ssf); + Utils.bindToRegistry(stub2, registry, boundNameTwo); + + Logger.printlnMixedBlue("Creating", "SecureServer", "object."); + remoteObjectThree = new SecureServer(); + ISecureServer stub3 = (ISecureServer)UnicastRemoteObject.exportObject(remoteObjectThree, 0); + Utils.bindToRegistry(stub3, registry, boundNameThree); + + Logger.decreaseIndent(); + Logger.println(""); + Logger.println("Server setup finished."); + Logger.println("Initializing LegacyServer."); + Logger.println(""); + + LegacyServer.init(plainRegistryPort); + + Logger.println("LegacyServer setup finished."); + Logger.println("Initializing ActivationServer."); + Logger.println(""); + + ActivationServer.init(activatorPort); + + Logger.println("ActivationServer setup finished."); + Logger.println("Initializing CustomSocketFactoryServer."); + Logger.println(""); + + CustomSocketFactoryServer.startServer(plainRegistryPort); + + Logger.println("Setup finished."); + Logger.println("Waiting for incoming connections."); + Logger.println(""); + } + + catch (RemoteException | AlreadyBoundException | NotBoundException e) + { + Logger.eprintln("Unexpected RMI Error:"); + e.printStackTrace(); + } + } +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationServer.java similarity index 79% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationServer.java index ee1abab8..5b7fa3c6 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationServer.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.activation; +package eu.tneitzel.rmg.server.activation; import java.rmi.AccessException; import java.rmi.AlreadyBoundException; @@ -15,10 +15,10 @@ import java.rmi.server.UnicastRemoteObject; import java.util.Properties; -import de.qtc.rmg.server.interfaces.IPlainServer; -import de.qtc.rmg.server.operations.PlainServer; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; +import eu.tneitzel.rmg.server.interfaces.IPlainServer; +import eu.tneitzel.rmg.server.operations.PlainServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; /** * Create an ActivationServer. This class does basically the same as rmid, but skips some configuration @@ -30,10 +30,13 @@ @SuppressWarnings("unused") public class ActivationServer { - private static int activationSystemPort = 1098; - private static Remote remoteObject1 = null; - private static Remote remoteObject2 = null; - private static Remote remoteObject3 = null; + private static Remote remoteObjectOne; + private static Remote remoteObjectTwo; + private static Remote remoteObjectThree; + + private static final String boundNameOne = "activation-test"; + private static final String boundNameTwo = "activation-test2"; + private static final String boundNameThree = "plain-server"; private final static String codebase = "file:///opt/example-server.jar"; @@ -42,11 +45,12 @@ public class ActivationServer * group is created and two activatable RMI services are bound to the registry. Additionally, we bind one * non activatable service. */ - public static void init() + public static void init(int activationSystemPort) { Logger.increaseIndent(); - try { + try + { Logger.printMixedBlue("Creating", "ActivationSystem", "on port "); Logger.printlnPlainYellow(String.valueOf(activationSystemPort)); Utils.startActivation(activationSystemPort, null, "/tmp/activation-log", null); @@ -90,28 +94,25 @@ public static void init() Utils.toogleOutput(); ActivationDesc desc = new ActivationDesc(groupID, ActivationService.class.getName(), codebase, null); - remoteObject1 = Activatable.register(desc); + remoteObjectOne = Activatable.register(desc); ActivationDesc desc2 = new ActivationDesc(groupID, ActivationService2.class.getName(), codebase, null); - remoteObject2 = Activatable.register(desc2); - - remoteObject3 = new PlainServer(); - IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject3, 0); - - Utils.bindToRegistry(remoteObject1, LocateRegistry.getRegistry(activationSystemPort), "activation-test"); - Utils.bindToRegistry(remoteObject2, LocateRegistry.getRegistry(activationSystemPort), "activation-test2"); - Utils.bindToRegistry(stub, LocateRegistry.getRegistry(activationSystemPort), "plain-server"); + remoteObjectTwo = Activatable.register(desc2); - Logger.println(""); - Logger.decreaseIndent(); + remoteObjectThree = new PlainServer(); + IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObjectThree, 0); - Logger.println("Server setup finished."); - Logger.println("Waiting for incoming connections."); - Logger.println(""); + Utils.bindToRegistry(remoteObjectOne, LocateRegistry.getRegistry(activationSystemPort), boundNameOne); + Utils.bindToRegistry(remoteObjectTwo, LocateRegistry.getRegistry(activationSystemPort), boundNameTwo); + Utils.bindToRegistry(stub, LocateRegistry.getRegistry(activationSystemPort), boundNameThree); + } - } catch (Exception e) { + catch (Exception e) + { Logger.eprintln("Unexpected RMI Error:"); e.printStackTrace(); } - } + + Logger.println(""); + Logger.decreaseIndent(); } } diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService.java similarity index 92% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService.java index cb0a6cd4..40065257 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.activation; +package eu.tneitzel.rmg.server.activation; import java.io.IOException; import java.rmi.MarshalledObject; @@ -6,8 +6,8 @@ import java.rmi.activation.Activatable; import java.rmi.activation.ActivationID; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; public class ActivationService extends Activatable implements IActivationService { @@ -54,4 +54,4 @@ public String system(String command, String[] args) return result; } -} \ No newline at end of file +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService2.java similarity index 95% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService2.java index 0db0a0a5..64ae13ed 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/ActivationService2.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/ActivationService2.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.activation; +package eu.tneitzel.rmg.server.activation; import java.rmi.MarshalledObject; import java.rmi.RemoteException; @@ -10,7 +10,7 @@ import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; -import de.qtc.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Logger; public class ActivationService2 extends Activatable implements IActivationService2 { @@ -55,4 +55,4 @@ public void updatePreferences(ArrayList preferences) throws RemoteExcept Logger.printlnMixedBlueYellow("[SecureServer]:", "Processing call for", "void updatePreferences(ArrayList preferences)"); this.preferences = preferences; } -} \ No newline at end of file +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService.java similarity index 83% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService.java index e74260ca..338782dc 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.activation; +package eu.tneitzel.rmg.server.activation; import java.rmi.Remote; import java.rmi.RemoteException; @@ -7,4 +7,4 @@ public interface IActivationService extends Remote { String execute(String cmd) throws RemoteException; String system(String cmd, String[] args) throws RemoteException; -} \ No newline at end of file +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService2.java similarity index 89% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService2.java index adde23e8..84d55dde 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/activation/IActivationService2.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/activation/IActivationService2.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.activation; +package eu.tneitzel.rmg.server.activation; import java.rmi.Remote; import java.rmi.RemoteException; @@ -10,4 +10,4 @@ public interface IActivationService2 extends Remote String login(HashMap credentials) throws RemoteException; void logMessage(int logLevel, Object message) throws RemoteException; void updatePreferences(ArrayList preferences) throws RemoteException; -} \ No newline at end of file +} diff --git a/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactory.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactory.java new file mode 100644 index 00000000..2c80b57b --- /dev/null +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactory.java @@ -0,0 +1,28 @@ +package eu.tneitzel.rmg.server.factory; + +import java.io.IOException; +import java.io.Serializable; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.server.RMISocketFactory; + +public class CustomSocketFactory extends RMISocketFactory implements Serializable +{ + private static final long serialVersionUID = -1168901302380021730L; + private final transient RMISocketFactory defaultFax; + + public CustomSocketFactory() + { + defaultFax = RMISocketFactory.getDefaultSocketFactory(); + } + + public ServerSocket createServerSocket(int arg0) throws IOException + { + return defaultFax.createServerSocket(arg0); + } + + public Socket createSocket(String arg0, int arg1) throws IOException + { + return defaultFax.createSocket(arg0, arg1); + } +} diff --git a/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactoryServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactoryServer.java new file mode 100644 index 00000000..89606522 --- /dev/null +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/factory/CustomSocketFactoryServer.java @@ -0,0 +1,52 @@ +package eu.tneitzel.rmg.server.factory; + +import java.rmi.AlreadyBoundException; +import java.rmi.NotBoundException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.RMISocketFactory; +import java.rmi.server.UnicastRemoteObject; + +import eu.tneitzel.rmg.server.interfaces.IPlainServer; +import eu.tneitzel.rmg.server.operations.PlainServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; + +public class CustomSocketFactoryServer +{ + private static Remote remoteObjectOne;; + private static final String boundName = "custom-socks"; + + public static void startServer(int registryPort) + { + Logger.increaseIndent(); + + try + { + RMISocketFactory csf = new CustomSocketFactory(); + + Logger.printMixedBlue("Locating", "RMI-Registry", "on port "); + Logger.printlnPlainYellow(String.valueOf(registryPort)); + Registry registry = LocateRegistry.getRegistry(registryPort); + Logger.println(""); + + Logger.printlnMixedBlue("Creating", "PlainServer", "object."); + remoteObjectOne = new PlainServer(); + IPlainServer stub = (IPlainServer)UnicastRemoteObject.exportObject(remoteObjectOne, 0, csf, null); + Utils.bindToRegistry(stub, registry, boundName); + + Logger.println("Server setup finished."); + } + + catch (RemoteException | AlreadyBoundException | NotBoundException e) + { + Logger.eprintln("Unexpected RMI Error:"); + e.printStackTrace(); + } + + Logger.println(""); + Logger.decreaseIndent(); + } +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/IPlainServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/IPlainServer.java similarity index 90% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/IPlainServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/IPlainServer.java index d6a0e69f..504e1e72 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/IPlainServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/IPlainServer.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.interfaces; +package eu.tneitzel.rmg.server.interfaces; import java.rmi.Remote; import java.rmi.RemoteException; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISecureServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISecureServer.java similarity index 90% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISecureServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISecureServer.java index 34fb9b20..b2b2e413 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISecureServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISecureServer.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.interfaces; +package eu.tneitzel.rmg.server.interfaces; import java.rmi.Remote; import java.rmi.RemoteException; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISslServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISslServer.java similarity index 89% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISslServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISslServer.java index c4aefdf0..887b5d77 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/interfaces/ISslServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/interfaces/ISslServer.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.interfaces; +package eu.tneitzel.rmg.server.interfaces; import java.rmi.Remote; import java.rmi.RemoteException; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServer.java similarity index 57% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServer.java index 4baa3136..ed5b3638 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServer.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.legacy; +package eu.tneitzel.rmg.server.legacy; import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; @@ -10,77 +10,83 @@ import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; -import de.qtc.rmg.server.interfaces.IPlainServer; -import de.qtc.rmg.server.operations.PlainServer; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; +import eu.tneitzel.rmg.server.interfaces.IPlainServer; +import eu.tneitzel.rmg.server.operations.PlainServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; @SuppressWarnings("unused") -public class LegacyServer { - - private static int registryPort = 9010; - private static Remote remoteObject1 = null; - private static Remote remoteObject2 = null; - private static Remote remoteObject3 = null; - private static Remote remoteObject4 = null; - - - public static void init() +public class LegacyServer +{ + private static Remote remoteObjectOne; + private static Remote remoteObjectTwo; + private static Remote remoteObjectThree; + private static Remote remoteObjectFour; + + private static final String boundNameOne = "legacy-service"; + private static final String boundNameTwo = "plain-server"; + private static final String boundNameThree = "plain-server2"; + + public static void init(int registryPort) { Logger.increaseIndent(); - try { + try + { Logger.printMixedBlue("Creating", "RMI-Registry", "on port "); Logger.printlnPlainYellow(String.valueOf(registryPort)); Registry registry = LocateRegistry.createRegistry(registryPort); Logger.println(""); Logger.printlnMixedBlue("Creating", "LegacyServiceImpl", "object."); - remoteObject1 = new LegacyServiceImpl(); + remoteObjectOne = new LegacyServiceImpl(); Logger.increaseIndent(); Logger.printMixedYellow("Binding", "LegacyServiceImpl"); Logger.printlnPlainMixedBlue(" as", "legacy-service"); - Naming.rebind("//127.0.0.1:" + registryPort + "/legacy-service", remoteObject1); + Naming.rebind("//127.0.0.1:" + registryPort + "/legacy-service", remoteObjectOne); Object o = registry.lookup("legacy-service"); String className = o.getClass().getName(); - Logger.printMixedYellow("Boundname", "legacy-service"); + Logger.printMixedYellow("Boundname", boundNameOne); Logger.printlnPlainMixedBlue(" with class", className, "is ready."); Logger.decreaseIndent(); Logger.printlnMixedBlue("Creating", "PlainServer", "object."); - remoteObject2 = new PlainServer(); - IPlainServer stub1 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject2, 0); - Utils.bindToRegistry(stub1, registry, "plain-server"); + remoteObjectTwo = new PlainServer(); + IPlainServer stub1 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObjectTwo, 0); + Utils.bindToRegistry(stub1, registry, boundNameTwo); Logger.printlnMixedBlue("Creating another", "PlainServer", "object."); - remoteObject3 = new PlainServer(); - IPlainServer stub2 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObject3, 0); - Utils.bindToRegistry(stub2, registry, "plain-server2"); + remoteObjectThree = new PlainServer(); + IPlainServer stub2 = (IPlainServer)UnicastRemoteObject.exportObject(remoteObjectThree, 0); + Utils.bindToRegistry(stub2, registry, boundNameThree); - try { + try + { Logger.printlnMixedBlue("Creating", "ActivatorImp", "object."); Logger.increaseIndent(); - remoteObject4 = Utils.getActivator(registryPort, null); + remoteObjectFour = Utils.getActivator(registryPort, null); Logger.printlnMixedYellowFirst("Activator", "is ready."); + } - } catch( Exception e) { + catch (Exception e) + { Logger.printlnYellow("Activator initialization failed."); + } - } finally { + finally + { Logger.decreaseIndent(); } Logger.println(""); Logger.decreaseIndent(); + } - Logger.println("Server setup finished."); - Logger.println("Initializing activation server."); - Logger.println(""); - - } catch (RemoteException | MalformedURLException | AlreadyBoundException | NotBoundException e) { + catch (RemoteException | MalformedURLException | AlreadyBoundException | NotBoundException e) + { Logger.eprintln("Unexpected RMI Error:"); e.printStackTrace(); } diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyService.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyService.java similarity index 93% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyService.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyService.java index eb3698eb..b0cf542b 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyService.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyService.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.legacy; +package eu.tneitzel.rmg.server.legacy; import java.rmi.Remote; import java.rmi.RemoteException; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServiceImpl.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServiceImpl.java similarity index 95% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServiceImpl.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServiceImpl.java index dd2011c0..8fdea7b7 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/legacy/LegacyServiceImpl.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/LegacyServiceImpl.java @@ -1,10 +1,10 @@ -package de.qtc.rmg.server.legacy; +package eu.tneitzel.rmg.server.legacy; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; -import de.qtc.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Logger; @SuppressWarnings("serial") public class LegacyServiceImpl extends UnicastRemoteObject implements LegacyService diff --git a/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/StringContainer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/StringContainer.java new file mode 100644 index 00000000..f4a69740 --- /dev/null +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/legacy/StringContainer.java @@ -0,0 +1,4 @@ +package eu.tneitzel.rmg.server.legacy; + +public class StringContainer { +} diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/PlainServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/PlainServer.java similarity index 91% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/operations/PlainServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/PlainServer.java index ac24dba6..5b4df8cf 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/PlainServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/PlainServer.java @@ -1,10 +1,10 @@ -package de.qtc.rmg.server.operations; +package eu.tneitzel.rmg.server.operations; import java.io.IOException; -import de.qtc.rmg.server.interfaces.IPlainServer; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; +import eu.tneitzel.rmg.server.interfaces.IPlainServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; public class PlainServer implements IPlainServer { diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SecureServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SecureServer.java similarity index 91% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SecureServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SecureServer.java index e6990bf9..4a700ed7 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SecureServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SecureServer.java @@ -1,11 +1,11 @@ -package de.qtc.rmg.server.operations; +package eu.tneitzel.rmg.server.operations; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.HashMap; -import de.qtc.rmg.server.interfaces.ISecureServer; -import de.qtc.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.interfaces.ISecureServer; +import eu.tneitzel.rmg.server.utils.Logger; public class SecureServer implements ISecureServer { diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SslServer.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SslServer.java similarity index 88% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SslServer.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SslServer.java index 0ad47ba6..aaa96f44 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/operations/SslServer.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/operations/SslServer.java @@ -1,10 +1,10 @@ -package de.qtc.rmg.server.operations; +package eu.tneitzel.rmg.server.operations; import java.io.IOException; -import de.qtc.rmg.server.interfaces.ISslServer; -import de.qtc.rmg.server.utils.Logger; -import de.qtc.rmg.server.utils.Utils; +import eu.tneitzel.rmg.server.interfaces.ISslServer; +import eu.tneitzel.rmg.server.utils.Logger; +import eu.tneitzel.rmg.server.utils.Utils; public class SslServer implements ISslServer { diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/CodebaseLogger.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/CodebaseLogger.java similarity index 97% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/utils/CodebaseLogger.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/CodebaseLogger.java index 2fef2217..d35104ec 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/CodebaseLogger.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/CodebaseLogger.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.utils; +package eu.tneitzel.rmg.server.utils; import java.net.MalformedURLException; import java.rmi.server.RMIClassLoader; diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Logger.java similarity index 99% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Logger.java index 80ce9c98..239b3e31 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/utils/Logger.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Logger.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.utils; +package eu.tneitzel.rmg.server.utils; import java.text.SimpleDateFormat; import java.util.Date; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Utils.java similarity index 99% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java rename to docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Utils.java index df3d8004..36c31794 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Utils.java +++ b/docker/example-server/resources/server/src/eu/tneitzel/rmg/server/utils/Utils.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.utils; +package eu.tneitzel.rmg.server.utils; import java.io.BufferedReader; import java.io.IOException; @@ -114,4 +114,4 @@ public static void bindToRegistry(Remote object, Registry registry, String bound Logger.printlnPlainMixedBlue(" with interface", className, "is ready."); Logger.decreaseIndent(); } -} \ No newline at end of file +} diff --git a/docker/spring-remoting/CHANGELOG.md b/docker/spring-remoting/CHANGELOG.md new file mode 100644 index 00000000..d8dba1bb --- /dev/null +++ b/docker/spring-remoting/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 - Dec 10, 2023 + +Initial Release diff --git a/docker/spring-remoting/Dockerfile b/docker/spring-remoting/Dockerfile new file mode 100644 index 00000000..9b8d9bfd --- /dev/null +++ b/docker/spring-remoting/Dockerfile @@ -0,0 +1,35 @@ +########################################### +### 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-spring-remoting-server-*-jar-with-dependencies.jar /opt/remoting-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 + +EXPOSE 1099/tcp + +CMD ["/opt/start.sh"] diff --git a/docker/spring-remoting/README.md b/docker/spring-remoting/README.md new file mode 100644 index 00000000..9a58128e --- /dev/null +++ b/docker/spring-remoting/README.md @@ -0,0 +1,92 @@ +### Spring Remoting Example Server + +---- + +The *Spring Remoting* example server provided by this repository can be used to test the +*Spring Remoting* related features of *remote-method-guesser*. You can either build the +container from source or pull it from [GitHub Packages](https://github.com/qtc-de/remote-method-guesser/pkgs/container/remote-method-guesser%2Fspring-remoting-server). + +* To build from source, just clone the repository, switch to the [docker directory](/docker/spring-remoting) and run `docker build .`. +* To load the container from the *GitHub Container Registry* just use the corresponding pull command: + ```console + [user@host ~]$ docker pull ghcr.io/qtc-de/remote-method-guesser/spring-remoting-server:1.0 + ``` + + +### Configuration Details + +---- + +The *Spring Remoting* example server exposes an *RMI* registry port on `tcp/1099`. Scanning this +port with *remote-method-guesser* version *v5.0.0* or higher should provide the following results: + +```console +[user@host ~]$ rmg enum 172.17.0.2 1099 +[+] RMI registry bound names: +[+] +[+] - spring-remoting +[+] --> org.springframework.remoting.rmi.RmiInvocationHandler (known class: Spring RmiInvocationHandler) +[+] Spring Remoting Interface: eu.tneitzel.rmg.springremoting.ServerOperations (unknown class) +[+] Endpoint: iinsecure.example:33779 CSF: RMISocketFactory ObjID: [43861eb7:18c69d6d219:-7fff, 8901450872606600476] +[+] +[+] 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 +``` + +As one can see in the output above, the actual exposed remote object implements `org.springframework.remoting.rmi.RmiInvocationHandler`, +as it is always the case for *Spring Remoting*. The underlying interface type however is `eu.tneitzel.rmg.springremoting.ServerOperations`. +This interface supports the following methods: + +```java +package eu.tneitzel.rmg.springremoting; + +public interface ServerOperations +{ + String notRelevant(); + String execute(String cmd); + String system(String cmd, String[] args); + String upload(int size, int id, byte[] content); + int math(int num1, int num2); +} +``` + +If you want to learn more about *Spring Remoting* and the associated features of *remote-method-guesser*, +it is recommended to read the corresponding [documentation article](/docs/rmg/spring-remoting.md). diff --git a/docker/spring-remoting/docker-compose.yml b/docker/spring-remoting/docker-compose.yml new file mode 100644 index 00000000..97a6fb1b --- /dev/null +++ b/docker/spring-remoting/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3.7' + +services: + spring-remoting: + image: ghcr.io/qtc-de/remote-method-guesser/spring-remoting-server:1.0 + build: . diff --git a/docker/spring-remoting/resources/scripts/start.sh b/docker/spring-remoting/resources/scripts/start.sh new file mode 100755 index 00000000..705846f2 --- /dev/null +++ b/docker/spring-remoting/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/remoting-server.jar diff --git a/docker/spring-remoting/resources/server/pom.xml b/docker/spring-remoting/resources/server/pom.xml new file mode 100644 index 00000000..45952278 --- /dev/null +++ b/docker/spring-remoting/resources/server/pom.xml @@ -0,0 +1,60 @@ + + 4.0.0 + eu.tneitzel.rmg + spring-remoting + 1.0.0 + + + UTF-8 + 1.8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + 2.7.16 + + + + + + + maven-assembly-plugin + + + package + + single + + + + + rmg-spring-remoting-server-${project.version} + + + eu.tneitzel.rmg.springremoting.Starter + true + + + + Tobias Neitzel (@qtc_de) + + + + + jar-with-dependencies + + + + + + + diff --git a/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/ServerOperations.java b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/ServerOperations.java new file mode 100644 index 00000000..bb75a606 --- /dev/null +++ b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/ServerOperations.java @@ -0,0 +1,10 @@ +package eu.tneitzel.rmg.springremoting; + +public interface ServerOperations +{ + String notRelevant(); + String execute(String cmd); + String system(String cmd, String[] args); + String upload(int size, int id, byte[] content); + int math(int num1, int num2); +} diff --git a/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/SpringRemotingService.java b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/SpringRemotingService.java new file mode 100644 index 00000000..0a6a7891 --- /dev/null +++ b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/SpringRemotingService.java @@ -0,0 +1,122 @@ +package eu.tneitzel.rmg.springremoting; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.remoting.rmi.RmiServiceExporter; + +@SuppressWarnings("deprecation") +public class SpringRemotingService implements ServerOperations +{ + Logger logger = LoggerFactory.getLogger(SpringRemotingService.class); + + SpringRemotingService springRemotingService() + { + return new SpringRemotingService(); + } + + @Bean + RmiServiceExporter exporter(SpringRemotingService impl) + { + Class serverInterface = ServerOperations.class; + RmiServiceExporter exporter = new RmiServiceExporter(); + + exporter.setServiceInterface(serverInterface); + exporter.setService(impl); + exporter.setServiceName("spring-remoting"); + exporter.setRegistryPort(1099); + + return exporter; + } + + public String notRelevant() + { + logger.info("Processing call for: notRelevant()"); + return "Hello World :D"; + } + + public String execute(String command) + { + logger.info("Processing call for: String execute(String command)"); + String result = ""; + + try + { + Process p = java.lang.Runtime.getRuntime().exec(command); + p.waitFor(); + result = readFromProcess(p); + } + + catch (IOException | InterruptedException e) + { + result = "Exception: " + e.getMessage(); + } + + return result; + } + + public String system(String command, String[] args) + { + logger.info("Processing call for: String system(String command, String[] args)"); + String result = ""; + + String[] commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy(args, 0, commandArray, 1, args.length); + + try + { + Process p = java.lang.Runtime.getRuntime().exec(commandArray); + p.waitFor(); + result = readFromProcess(p); + } + + catch (IOException | InterruptedException e) + { + result = "Exception: " + e.getMessage(); + } + + return result; + } + + public String upload(int size, int id, byte[] content) + { + logger.info("Processing call for: String upload(int size, int id, byte[] content)"); + return "Upload of size " + size + " was saved as user_uploads_" + id + "."; + } + + public int math(int num1, int num2) + { + logger.info("Processing call for: int math(int num1, int num2)"); + return num1 / num2; + } + + private static String readFromProcess(Process p) throws IOException + { + StringBuilder result = new StringBuilder(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = reader.readLine(); + + while (line != null) + { + result.append(line); + line = reader.readLine(); + } + + reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); + line = reader.readLine(); + + while (line != null) + { + result.append(line); + line = reader.readLine(); + } + + return result.toString(); + } +} diff --git a/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/Starter.java b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/Starter.java new file mode 100644 index 00000000..4dcb1ceb --- /dev/null +++ b/docker/spring-remoting/resources/server/src/main/java/eu/tneitzel/rmg/springremoting/Starter.java @@ -0,0 +1,11 @@ +package eu.tneitzel.rmg.springremoting; + +import org.springframework.boot.SpringApplication; + +public class Starter +{ + public static void main(String[] args) + { + SpringApplication.run(SpringRemotingService.class, args); + } +} diff --git a/docker/ssrf-server/CHANGELOG.md b/docker/ssrf-server/CHANGELOG.md index 9dbec46e..3207724f 100644 --- a/docker/ssrf-server/CHANGELOG.md +++ b/docker/ssrf-server/CHANGELOG.md @@ -6,14 +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). -## [1.4] - Jan 19, 2023 +## v2.0.0 - Dec 10, 2023 + +### Changed + +* Changed the servers namespace from `de.qtc` to `eu.tneitzel` +* Since Java 9 is no longer available in the alpine default package + repositories, the JDK is now obtained from an older image of the + rmg-ssrf-server. + + +## v1.4.0 - Jan 19, 2023 ### Changed * Remove unused hostname from startup script -## [1.3] - May 08, 2022 +## v1.3.0 - May 08, 2022 ### Changed diff --git a/docker/ssrf-server/Dockerfile b/docker/ssrf-server/Dockerfile index bb99f5b8..14d7620e 100644 --- a/docker/ssrf-server/Dockerfile +++ b/docker/ssrf-server/Dockerfile @@ -9,11 +9,9 @@ RUN mvn clean package ########################################### ### Build Stage 2 ### ########################################### -FROM alpine:latest AS jdk-builder +FROM ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.4 AS jdk-builder RUN set -ex \ - && apk add --no-cache openjdk9 \ - && /usr/lib/jvm/java-9-openjdk/bin/jlink --add-modules java.rmi,java.management.rmi,jdk.management.agent,jdk.naming.rmi,jdk.httpserver \ - --verbose --strip-debug --compress 2 --no-header-files --no-man-pages --module-path /usr/lib/jvm/java-9-openjdk/jmods/ --output /jdk + && mv /usr/lib/jvm/java-9-openjdk /jdk ########################################### ### Container Stage ### diff --git a/docker/ssrf-server/README.md b/docker/ssrf-server/README.md index 307a1c1c..5437df1d 100644 --- a/docker/ssrf-server/README.md +++ b/docker/ssrf-server/README.md @@ -76,13 +76,13 @@ In this example we use *remote-method-guesser's* enum action to enumerate availa [+] RMI registry bound names: [+] [+] - FileManager -[+] --> de.qtc.rmg.server.ssrf.rmi.IFileManager (unknown class) +[+] --> eu.tneitzel.rmg.server.ssrf.rmi.IFileManager (unknown class) [+] Endpoint: localhost:34289 ObjID: [2fd1b5a8:17d842f762e:-7fff, 8844200108184892689] [+] [+] RMI server codebase enumeration: [+] [+] - http://localhost:8000/rmi-class-definitions.jar -[+] --> de.qtc.rmg.server.ssrf.rmi.IFileManager +[+] --> eu.tneitzel.rmg.server.ssrf.rmi.IFileManager ``` diff --git a/docker/ssrf-server/docker-compose.yml b/docker/ssrf-server/docker-compose.yml index 50893e1e..bc15dd6d 100644 --- a/docker/ssrf-server/docker-compose.yml +++ b/docker/ssrf-server/docker-compose.yml @@ -1,6 +1,6 @@ version: '3.7' services: - rmg: - image: ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.4 + rmg-ssrf-server: + image: ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:2.0 build: . diff --git a/docker/ssrf-server/resources/server/pom.xml b/docker/ssrf-server/resources/server/pom.xml index 710ab088..48d4ef59 100644 --- a/docker/ssrf-server/resources/server/pom.xml +++ b/docker/ssrf-server/resources/server/pom.xml @@ -3,9 +3,9 @@ 4.0.0 - de.qtc.rmg.server.ssrf + eu.tneitzel.rmg.server.ssrf rmg-ssrf-server - 1.3.0 + 2.0.0 rmg-ssrf-server RMG SSRF Server @@ -50,9 +50,14 @@ rmg-ssrf-server-${project.version} - de.qtc.rmg.server.ssrf.Starter + eu.tneitzel.rmg.server.ssrf.Starter true + + + Tobias Neitzel (@qtc_de) + + jar-with-dependencies diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/Starter.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/Starter.java similarity index 84% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/Starter.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/Starter.java index cbed8993..3bcf82f6 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/Starter.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/Starter.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf; +package eu.tneitzel.rmg.server.ssrf; import java.io.IOException; import java.rmi.AlreadyBoundException; @@ -6,12 +6,12 @@ import java.rmi.registry.Registry; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.server.ssrf.http.SSRFServer; -import de.qtc.rmg.server.ssrf.rmi.FileManager; -import de.qtc.rmg.server.ssrf.rmi.IFileManager; -import de.qtc.rmg.server.ssrf.rmi.LocalhostJmxConnector; -import de.qtc.rmg.server.ssrf.rmi.LocalhostSocketFactory; -import de.qtc.rmg.server.ssrf.utils.Logger; +import eu.tneitzel.rmg.server.ssrf.http.SSRFServer; +import eu.tneitzel.rmg.server.ssrf.rmi.FileManager; +import eu.tneitzel.rmg.server.ssrf.rmi.IFileManager; +import eu.tneitzel.rmg.server.ssrf.rmi.LocalhostJmxConnector; +import eu.tneitzel.rmg.server.ssrf.rmi.LocalhostSocketFactory; +import eu.tneitzel.rmg.server.ssrf.utils.Logger; /** * The Starter class is responsible for creating an HTTP server that is vulnerable diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/JarHandler.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/JarHandler.java similarity index 94% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/JarHandler.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/JarHandler.java index 256e332b..72db2439 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/JarHandler.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/JarHandler.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.http; +package eu.tneitzel.rmg.server.ssrf.http; import java.io.File; import java.io.IOException; @@ -8,7 +8,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import de.qtc.rmg.server.ssrf.utils.Logger; +import eu.tneitzel.rmg.server.ssrf.utils.Logger; /** * The JarHandler handles requests to the codebase endpoint specified within the RMI server. diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFHandler.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFHandler.java similarity index 98% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFHandler.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFHandler.java index 24b7ead3..89809c37 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFHandler.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFHandler.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.http; +package eu.tneitzel.rmg.server.ssrf.http; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -8,7 +8,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; -import de.qtc.rmg.server.ssrf.utils.Logger; +import eu.tneitzel.rmg.server.ssrf.utils.Logger; import org.apache.commons.io.HexDump; import org.apache.commons.io.IOUtils; diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFServer.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFServer.java similarity index 93% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFServer.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFServer.java index 64e467d9..69398b2f 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/http/SSRFServer.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/http/SSRFServer.java @@ -1,11 +1,11 @@ -package de.qtc.rmg.server.ssrf.http; +package eu.tneitzel.rmg.server.ssrf.http; import java.io.IOException; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpServer; -import de.qtc.rmg.server.ssrf.utils.Logger; +import eu.tneitzel.rmg.server.ssrf.utils.Logger; /** * The SSRFServer class creates an HTTP server that is vulnerable to SSRF attacks. diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/FileManager.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/FileManager.java similarity index 98% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/FileManager.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/FileManager.java index e93fab95..c9547da7 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/FileManager.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/FileManager.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.rmi; +package eu.tneitzel.rmg.server.ssrf.rmi; import java.io.File; import java.io.IOException; diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/IFileManager.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/IFileManager.java similarity index 94% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/IFileManager.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/IFileManager.java index 4dbf0edc..02debfce 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/IFileManager.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/IFileManager.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.rmi; +package eu.tneitzel.rmg.server.ssrf.rmi; import java.io.File; import java.io.IOException; diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostJmxConnector.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostJmxConnector.java similarity index 97% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostJmxConnector.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostJmxConnector.java index 769cfe1b..9bf1263c 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostJmxConnector.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostJmxConnector.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.rmi; +package eu.tneitzel.rmg.server.ssrf.rmi; import java.io.IOException; import java.lang.management.ManagementFactory; diff --git a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostSocketFactory.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostSocketFactory.java similarity index 95% rename from docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostSocketFactory.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostSocketFactory.java index 81cd2a59..753cc917 100644 --- a/docker/ssrf-server/resources/server/src/de/qtc/rmg/server/ssrf/rmi/LocalhostSocketFactory.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/rmi/LocalhostSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.ssrf.rmi; +package eu.tneitzel.rmg.server.ssrf.rmi; import java.io.IOException; import java.net.InetSocketAddress; diff --git a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/utils/Logger.java similarity index 99% rename from docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java rename to docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/utils/Logger.java index e9747bb8..56ea1267 100644 --- a/docker/example-server/resources/server/src/de/qtc/rmg/server/utils/Logger.java +++ b/docker/ssrf-server/resources/server/src/eu/tneitzel/rmg/server/ssrf/utils/Logger.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.server.utils; +package eu.tneitzel.rmg.server.ssrf.utils; import java.text.SimpleDateFormat; import java.util.Date; diff --git a/docs/rmg/README.md b/docs/rmg/README.md index 6e14b093..87b0a015 100644 --- a/docs/rmg/README.md +++ b/docs/rmg/README.md @@ -6,6 +6,7 @@ In this folder you can find more detailed documentation on *remote-method-guesse * [Actions](./actions.md) * [Activation System](./activation-system.md) +* [Dynamic Socket Factory Creation](./dynamic-socket-factories.md) * [Media](./media.md) * [Method-Guessing](./method-guessing.md) * [Plugin System](./plugin-system.md) diff --git a/docs/rmg/actions.md b/docs/rmg/actions.md index 49160a3e..a8a11359 100644 --- a/docs/rmg/actions.md +++ b/docs/rmg/actions.md @@ -295,13 +295,13 @@ following listing shows an example for this situation: [+] RMI registry bound names: [+] [+] - activation-test -[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: 6fd4e3c:180ac45a068:-7ff1 [+] - activation-test2 -[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: 6fd4e3c:180ac45a068:-7fee [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:41867 TLS: no ObjID: [6fd4e3c:180ac45a068:-7fec, 969949632761859811] [+] - java.rmi.activation.ActivationSystem [+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System) @@ -327,15 +327,15 @@ the obtained `UnicastRef` is then displayed as usual below the activation relate [+] RMI registry bound names: [+] [+] - activation-test -[+] --> de.qtc.rmg.server.activation.IActivationService (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: 6fd4e3c:180ac45a068:-7ff1 [+] Endpoint: iinsecure.example:37597 TLS: no ObjID: [1c74dc89:180ac521427:-7ffb, 3078273701606404425] [+] - activation-test2 -[+] --> de.qtc.rmg.server.activation.IActivationService2 (unknown class) +[+] --> eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class) [+] Activator: iinsecure.example:1098 ActivationID: 6fd4e3c:180ac45a068:-7fee [+] Endpoint: iinsecure.example:35721 TLS: yes ObjID: [1c74dc89:180ac521427:-7ff8, 6235870260204364974] [+] - plain-server -[+] --> de.qtc.rmg.server.interfaces.IPlainServer (unknown class) +[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class) [+] Endpoint: iinsecure.example:41867 TLS: no ObjID: [6fd4e3c:180ac45a068:-7fec, 969949632761859811] [+] - java.rmi.activation.ActivationSystem [+] --> sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System) @@ -733,13 +733,13 @@ the ``--bound-name`` during the action, more detailed information can be obtaine [+] RMI registry bound names: [+] [+] - FileManager -[+] --> de.qtc.rmg.server.ssrf.rmi.IFileManager (unknown class) +[+] --> eu.tneitzel.rmg.server.ssrf.rmi.IFileManager (unknown class) [+] Endpoint: localhost:36983 ObjID: [-36af9747:17d8947959b:-7fff, 292657548115654006] [+] [+] RMI server codebase enumeration: [+] [+] - http://localhost:8000/rmi-class-definitions.jar -[+] --> de.qtc.rmg.server.ssrf.rmi.IFileManager +[+] --> eu.tneitzel.rmg.server.ssrf.rmi.IFileManager ``` Apart from ``list`` the following scan actions are available: diff --git a/docs/rmg/dynamic-socket-factories.md b/docs/rmg/dynamic-socket-factories.md new file mode 100644 index 00000000..143828e5 --- /dev/null +++ b/docs/rmg/dynamic-socket-factories.md @@ -0,0 +1,60 @@ +### Dynamic Socket Factories + +---- + +With version *v5.0.0* support for dynamically created socket factories +were added to *remote-method-guesser*. This basically means the following: + +*RMI* servers can register a custom socket factory that has to be used +to connect to their exposed remote objects. If this is done by an *RMI* +server, you usually see an error message like the following when you +attempt to connect with *remote-method-guesser*: + +> The class custom.socket.Factory could not be resolved within your class path + +With this error message, *Java* simply complains that it can't find the +implementation of `custom.socket.Factory`, which is required to establish +a connection to the *RMI* server. + +In some cases, this means that you cannot further enumerate the *RMI* +service, but often times it is possible anyways. Custom socket factory +classes often still rely on the default socket implementations. This means +that at the end of the day, the socket factory still outputs a regular +socket, as it can also be obtained from *Java's* default socket *APIs*. + +From version *v5.0.0* on, *remote-method-guesser* attempts to detect +errors caused by missing socket factory classes. In these cases, *rmg* +attempts to create the class dynamically. Since the actual implementation +is unknown, the dynamically created socket factory is simply the default +socket factory used by *remote-method-guesser*. Surprisingly often, this +is sufficient to connect. + +But why people implement custom socket factories when you can also connect +to them with the default one? Well, most of the time developers only add some +small behavior changes for their sockets, like e.g. a specific socket timeout +or trust for a self signed certificate. As these changes do not matter for an +successful connection (as *remote-method-guesser* trusts all certificates anyway) +dynamic socket factory creation works in these cases. + +However, some developers like it special. You could for example create a socket +factory that applies *XOR* encoding to all bytes before they are transmitted. +In such a case, dynamic socket factory creation will not work, as the default +sockets created by *remote-method-guesser* will not apply the *XOR* encoding +and the server side socket will not understand our data. + + +### User Options + +---- + +By default, *remote-method-guesser* attempts to create a socket factory +for classes that contain the string `SocketFactory` or end with `Factory` +or `SF`. If you want to dynamically create a socket factory for a different +class, you can specify the class name using the `--socket-factory` option. +Each class that cannot be found locally will then be checked whether it contains +the specified pattern and a socket factory will be created if this is the case. + +You can also use the `--socket-factory` option to prevent the dynamic creation +of socket factories. When a pattern was specified, the default patterns are not +checked. Therefore, using something like `--socket-factory nobody.uses.a.class.name.like.this` +will prevent socket factory creation. diff --git a/docs/rmg/inline-gadgets.md b/docs/rmg/inline-gadgets.md new file mode 100644 index 00000000..0cbb3af5 --- /dev/null +++ b/docs/rmg/inline-gadgets.md @@ -0,0 +1,134 @@ +### Inline Gadgets + +---- + +Since it's early days, *remote-method-guesser* provides a *ysoserial* integration, +that allows to create *ysoserial* gadgets by using command line parameters passed +within the *rmg* command line. For most situations, this approach is sufficient, +but sometimes support for *inline gadgets* would be useful. With *inline gadgets* +we mean gadgets that are passed e.g. as base64 encoded command line parameter. +This would e.g. allow to use serialization based rmg actions without having the +*ysoserial* even present. + +For version *v5.0.0* of remote-method-guesser, *inline gadgets* were part of the +roadmap. However, as it turned out, our considerations on how to implement them +were too naive and at the end, this feature was not implemented at all. In this +document we want to write down some of the problems we encountered, how they could +have been resolved and why we did not follow this path. + + +### ObjectOutputStream + +---- + +Our first concern was to write the user supplied object to the `ObjectOutputStream` +that one can obtain by calling the `getOutputStream` method on a `StreamRemoteCall` +object. Luckily, remote-method-guesser [already implemented](https://github.com/qtc-de/remote-method-guesser/blob/master/src/eu.tneitzel.rmg/networking/RMIEndpoint.java#L157) +*RMI* calls on such a low level that all this stuff can be accessed quite easily. +Writing the user supplied object is however not that straight forward. We do not +want to deserialize the object, as this would require all gadget libraries to be +present and would lead to payload execution on the executing system. Just writing +the object as bytes to the output stream would also not work, as Java would mark +these bytes as a *byte array* within the output stream and they would not be +interpreted as object on the server side. Tampering with the `ObjectOutputStream` +implementation is therefore required. + +One of our first approaches was to subclass `ObjectOutputStream` and to overwrite +the `writeObject` method. We implemented a class `GadgetHolder` that contained +the user supplied gadget as byte array. The custom implementation of `writeObject` +checked whether the current object to be written is a `GadgetHolder` and wrote the +contained byte array directly to the `BlockDataOutputStream` that is part of each +`ObjectOutputStream`. + +Since the stream you get from `StreamRemoteCall` is already an `ObjectOutputStream`, +the above mentioned subclass was wrapped essentially an `ObjectOutputStream` wrapped +around an `ObjectOutputStream`. For this to work, it is important to overwrite the +`writeStreamHeader` method, to prevent any stream headers from being written during +the wrapping. Moreover, when writing to `BlockDataOutputStream`, one needs to make +sure that the correct `BlockDataMode` is configured on this stream. + +At the end, everything seemed to work well, but remote-method-guessers *gadget canaries* +made us aware, that this implementation is not working for nested objects. When +remote-method-guesser sends a gadget to the application server, it wraps this gadget +into an `Object` array. The first item within this array is the gadget, whereas the second +one is a dynamically created class. The application server will only attempt to deserialize +the second object, if the deserialization of the first one was successful. By checking for +a `ClassNotFound` exception containing the name of the dynamically created class, successful +gadget deserialization can be verified. + +When our subclassed `ObjectOutputStream` attempted to serialize the `Object` array, it used +the custom `writeObject` method. Since `Object[]` is not `GadgetHolder`, it called the superclass +implementation of `writeObject`. For an array however, `writeObject` does not use `writeObject` +to write the contained items to the stream, but uses `writeObject0` instead. This is a private +method and will therefore always use the implementation of the superclass. Nesting `GadgetHolder` +in any kind of other class is therefore not working. + +At this point we thought about removing *gadget canaries* when *inline gadgets* are used. But +this seemed not to be the *ideal* solution. After some other failed attempts, we thought that +operating on the underlying stream level would be the best. We associated a unique `SerialVersionUid` +to `GadgetHolder` and padded the user specified gadget with some static prefix and suffix bytes. +Within the `ObjectOutputStream` obtained from `StreamRemoteCall` we replaced the `BlockDataOutputStream` +with a custom stream class. This class checked for the `SerialVersionUid` of `GadgetHolder` and replaced +the object on the stream with the contents of the byte array contained in it's property. Sounds crazy, +but worked with great success - until we discovered the real problems. + + +### Reference Counting + +---- + +When serializing or deserializing objects, Java tracks certain locations (*handles*) within the stream +that might be referenced at a later point. E.g. if one object is referenced by several fields within a +class, the object only needs to be serialized once and each field can simply point to the already serialized +object. The handles themselves are not contained within the stream, but internally tracked during serialization +or deserialization. References to a handle are part of the stream. Handles are of type `int` and the internal +tracker starts at a value of `0x7e0000`. Each new handle increments this tracker by one. + +When creating a payload object using *ysoserial*, it usually contains a lot of these references. This again +causes problems with remote-method-guessers *gadget canaries*. When the actual payload object is wrapped inside +an `Object` array, the `Object` array type gets written first to the stream. Since this type could be referenced +later on, a handle is created for it. The handle count within the stream is no longer in sync with the handle count +that was used by *ysoserial* during the serialization. Therefore, the payload object will not be deserialized +correctly. + +This problem could be fixed by shifting the handle references within a payload object with the required value. +Sounds easy, but such an approach would require to reliably identify handle references within a payload object. +A simply match and replace for `0x7e....` would obviously not work, as such a value could appear in other locations +of the stream too. Therefore, one would need to implement a serialization parser like e.g. [SerializationDumper](https://github.com/NickstaDB/SerializationDumper) +and include it into remote-method-guesser. A lot of implementation effort - and at the end, it is still quite hacky. + +At this point we decided to drop *gadget canaries* when using *inline gadgets*, but the real problem is yet to come. + + +### MarshalOutputStream + +---- + +After implementing the above mentioned changes, the deserialization still did not work as expected. Comparing +the network traffic when using the ysoserial integration with the traffic created by an inline gadget revealed +that the former contained additional `TC_NULL` bytes that were missing within the inline gadget. This made us +remember something we already knew, but totally had forgotten. + +As it turned out, the assumption that `getOutputStream` on `StreamRemoteCall` returns an `ObjectOutputStream` was +false. What you get is an `ObjectOutput`, but the underlying implementation is a `ConncetionOutputStream` - a class +that extends `MarshalOutputStream`. `MarshalOutputStream` is well known to remote-method-guesser, as this class is +responsible for adding annotations to classes, a feature that allows e.g. remote codebases to work in RMI. + +The problem is now that `MarshalOutputStream` **always** writes an annotation to a class. Even if a codebase is not +used and there is noting to annotate, a `null` gets still written. On the other hand, `MarshalInputStream` expects +these annotations also to be present. A gadget created by *ysoserial* however uses a classical `ObjectOutputStream`. +Therefore, these annotation bytes are missing and the `MarshalInputStream` fails to deserialize the object. + +This problem could again be overcome in different ways. The easiest approach would probably be to add a `--stream` option +to *ysoserial* that allows to select `MarshalOutputStream` for payload creation. However, this would still require users +to know about this problematic and to use the appropriate option during payload creation. Another option would be to add the +missing `TC_NULL` bytes on the already serialized object, but this would require again a serialization parser. + + +### Conclusion + +---- + +At the end, we decided against the *inline gadget* feature and will not implement it. If remote-method-guessers +*ysoserial* integration does not work for you, we recommend using the prebuild [docker image](https://github.com/qtc-de/remote-method-guesser/pkgs/container/remote-method-guesser%2Frmg) +that contains *ysoserial* in the *non-slim* versions and should work out of the box. diff --git a/docs/rmg/method-guessing.md b/docs/rmg/method-guessing.md index f65e32fe..f2778bfd 100644 --- a/docs/rmg/method-guessing.md +++ b/docs/rmg/method-guessing.md @@ -379,7 +379,7 @@ although the speedup was just marginal. Summarized: *rmg v3.3.0* implements the hybrid approach. Pure primitive methods are guessed by using the *Ping polyglot* technique, whereas methods with at least one non primitive argument type are guessed by extending the ``TC_BLOCKDATA`` structure over it's expected length. The corresponding -implementation can be found in the [MethodCandidate](https://github.com/qtc-de/remote-method-guesser/blob/develop/src/de/qtc/rmg/internal/MethodCandidate.java) +implementation can be found in the [MethodCandidate](https://github.com/qtc-de/remote-method-guesser/blob/develop/src/eu.tneitzel.rmg/internal/MethodCandidate.java) class within the ``sendArguments`` method. diff --git a/docs/rmg/plugin-system.md b/docs/rmg/plugin-system.md index b173eee5..8ec76f1b 100644 --- a/docs/rmg/plugin-system.md +++ b/docs/rmg/plugin-system.md @@ -161,25 +161,25 @@ that is applied by RMI when the RMI server location was set to *localhost*. The following listing contains the default implementation that is used by *remote-method-guesser* internally: ```java -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; import java.lang.reflect.Method; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.DGCClientSocketFactory; -import de.qtc.rmg.networking.LoopbackSocketFactory; -import de.qtc.rmg.networking.LoopbackSslSocketFactory; -import de.qtc.rmg.networking.SSRFResponseSocketFactory; -import de.qtc.rmg.networking.SSRFSocketFactory; -import de.qtc.rmg.networking.TrustAllSocketFactory; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.operations.RegistryClient; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.YsoIntegration; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.DGCClientSocketFactory; +import eu.tneitzel.rmg.networking.LoopbackSocketFactory; +import eu.tneitzel.rmg.networking.LoopbackSslSocketFactory; +import eu.tneitzel.rmg.networking.SSRFResponseSocketFactory; +import eu.tneitzel.rmg.networking.SSRFSocketFactory; +import eu.tneitzel.rmg.networking.TrustAllSocketFactory; +import eu.tneitzel.rmg.operations.Operation; +import eu.tneitzel.rmg.operations.RegistryClient; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.YsoIntegration; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; @@ -263,7 +263,7 @@ public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISo ClassPool pool = ClassPool.getDefault(); try { - CtClass evaluator = pool.makeClass("de.qtc.rmg.plugin.DefaultProviderEval"); + CtClass evaluator = pool.makeClass("eu.tneitzel.rmg.plugin.DefaultProviderEval"); String evalFunction = "public static Object[] eval() {" + " return new Object[] { " + argumentString + "};" + "}"; @@ -357,7 +357,7 @@ public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISo public String getDefaultSSLSocketFactory(String host, int port) { if( RMGOption.SSRFRESPONSE.notNull() ) - return "de.qtc.rmg.networking.DGCClientSslSocketFactory"; + return "eu.tneitzel.rmg.networking.DGCClientSslSocketFactory"; TrustAllSocketFactory trustAllFax = new TrustAllSocketFactory(); @@ -365,7 +365,7 @@ public class DefaultProvider implements IArgumentProvider, IPayloadProvider, ISo LoopbackSslSocketFactory.fac = trustAllFax.getSSLSocketFactory(); LoopbackSslSocketFactory.followRedirect = RMGOption.CONN_FOLLOW.getBool(); - return "de.qtc.rmg.networking.LoopbackSslSocketFactory"; + return "eu.tneitzel.rmg.networking.LoopbackSslSocketFactory"; } } ``` diff --git a/docs/rmg/spring-remoting.md b/docs/rmg/spring-remoting.md new file mode 100644 index 00000000..44b9e8b6 --- /dev/null +++ b/docs/rmg/spring-remoting.md @@ -0,0 +1,51 @@ +### Spring Remoting + +---- + +The *Spring Framework* offers several methods of remoting and also provides a wrapper +around *RMI*. Despite support for serialization based remoting was removed in most recent +Spring versions (v6), it can still be encountered quite often. + +RMI via Spring Remoting is a little bit different than plain Java RMI. Instead of creating +a `RemoteObject` and registering it within an *RMI Registry*, Spring uses a wrapper `RemoteObject` +that gets registered within the registry instead. This wrapper contains a reference to the actual +object where calls should be performed on and supports the following methods: + +```java +java.lang.String getTargetInterfaceName() +java.lang.Object invoke(org.springframework.remoting.support.RemoteInvocation invo) +``` + +As you probably already guessed `getTargetInterfaceName` returns the name of the interface that +is implemented by the underlying object. The `invoke` method on the other hand is used to forward +method calls to the underlying object. The `RemoteInvocation` type contains all information required +for the method call, including the method name, the method argument types and the respective +argument values. The `invoke` method looks up the requested method via reflection and calls it +using the specified argument types. + + +### remote-method-guesser and Spring Remoting + +---- + +When *remote-method-guesser* encounters a Spring Remoting `RemoteObject` it highlights this already +during the enum action. For Spring Remoting based RemoteObjects, the underlying interface type is +displayed along with the usual information. + +Method calls dispatched via the [call](https://github.com/qtc-de/remote-method-guesser#call) action +are always wrapped into `RemoteInvocation` calls instead. This means that remote-method-guesser always +performs calls on the underlying object that is enclosed in the Spring Remoting wrapper. Exceptions +are made for the two known methods mentioned above. These will be dispatched on the wrapper object +itself. If you want to enforce forwarding via `RemoteInvocation`, you can use the `--spring-remoting` +option. + +Method guessing is always performed on the underlying object contained within the Spring Remoting +wrapper. All method candidates are wrapped into `RemoteInvocations` and send to the underlying object +via the `invoke` method. Since valid RMI calls have to be used, the different protocol based performance +boost techniques described in the [guessing docs](https://github.com/qtc-de/remote-method-guesser/blob/master/docs/rmg/method-guessing.md) +do not apply to Spring Remoting. However, as method lookups are done via reflection and the methods +argument types and argument values are passed separately, it is still possible to use argument-confusion +to prevent accidental calls of RMI methods. Another advantage of Spring Remotings reflection based lookup +is that return values are not taken into account. Therefore, methods can be guessed by name and argument +types alone. remote-method-guesser automatically filters return value based duplicates out of method +wordlists. diff --git a/docs/rmi/known-endpoints.md b/docs/rmi/known-endpoints.md index 77773cb7..a06fcca6 100644 --- a/docs/rmi/known-endpoints.md +++ b/docs/rmi/known-endpoints.md @@ -34,7 +34,7 @@ > attacks. With JEP290, deserialization filters were introduced. The deserialization filters of DGC > endpoints are more restrictive than for the RMI registry and there a no known bypasses. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) ### JMX Connection @@ -93,7 +93,7 @@ > other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean > is therefore most of the time equivalent to remote code execution. * References: - * [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter) + * [https://github.com/qtc-de/beanshooter#generic-deploy](https://github.com/qtc-de/beanshooter#generic-deploy) * Deserialization * Description: @@ -101,7 +101,7 @@ > All communication to JMX that is dispatched over this remote object is not filtered for deserialization > attacks. Therefore, each suitable method can be used to pass a deserialization payload to the server. * References: - * [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter) + * [https://github.com/qtc-de/beanshooter#serial](https://github.com/qtc-de/beanshooter#serial) ### JMX Server @@ -138,7 +138,7 @@ > other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean > is therefore most of the time equivalent to remote code execution. * References: - * [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter) + * [https://github.com/qtc-de/beanshooter#generic-deploy](https://github.com/qtc-de/beanshooter#generic-deploy) * Deserialization * Description: @@ -148,7 +148,7 @@ > actual JMX communication using the RMIConnection object is not filtered. Therefore, if you can > establish a working JMX connection, you can also perform deserialization attacks. * References: - * [https://github.com/qtc-de/beanshooter](https://github.com/qtc-de/beanshooter) + * [https://github.com/qtc-de/beanshooter#serial](https://github.com/qtc-de/beanshooter#serial) ### RMI Activation Group @@ -180,7 +180,7 @@ > ActivationGroup remote objects do not use a deserialization filter. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) ### RMI Activation System @@ -224,7 +224,7 @@ > When accessed from localhost, the ActivationSystem is vulnerable to deserialization attacks. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) ### RMI Activator @@ -255,7 +255,7 @@ > Deserialization filters were never applied to the activation system and the Activator can be used > for deserialization attacks. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) ### RMI Registry @@ -292,7 +292,7 @@ > JEP290, deserialization filters were introduced. Depending on the patch level of the corresponding > Java instance, the filters may be bypassed. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) * Localhost Bypass * Description: @@ -301,7 +301,7 @@ > RMI registry instance. In 2019, a bypass for this localhost restriction was identified, that may allows > an attacker to bind, rebind or unbind names from remote. * References: - * [https://github.com/qtc-de/remote-method-guesser](https://github.com/qtc-de/remote-method-guesser) + * [https://github.com/qtc-de/remote-method-guesser#bind-rebind-and-unbind](https://github.com/qtc-de/remote-method-guesser#bind-rebind-and-unbind) * UnicastRemoteObject * Description: @@ -311,3 +311,44 @@ > existing remote objects that are listening on an user specified TCP port. * References: * [https://github.com/qtc-de/remote-method-guesser/blob/master/docs/rmi/unicast-remote-object.md](https://github.com/qtc-de/remote-method-guesser/blob/master/docs/rmi/unicast-remote-object.md) + + +### Spring RmiInvocationHandler + +--- + +* Name: `Spring RmiInvocationHandler` +* Class Names: + * `org.springframework.remoting.rmi.RmiInvocationHandler` +* Description: + + > RmiInvocationHandler is basically a wrapper around the actual RMI services that are implemented by the server. The + > interface supports an invoke method that forwards the call to the actual RMI service. Moreover, the getTargetInterfaceName + > method can be used to retrieve the interface that is implemented by the actual RMI service. + +* Remote Methods: + + ```java + java.lang.String getTargetInterfaceName() + java.lang.Object invoke(org.springframework.remoting.support.RemoteInvocation invo) + ``` +* References: + * [https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/rmi/RmiInvocationHandler.html](https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/rmi/RmiInvocationHandler.html) + * [https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/support/RemoteInvocation.html](https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/support/RemoteInvocation.html) +* Known Vulnerabilities: + + * Deserialization + * Description: + + > The invoke method uses a method parameter with non trivial type and is therefore vulnerable to arbitrary deserialization. + * References: + * [https://github.com/qtc-de/remote-method-guesser#serial](https://github.com/qtc-de/remote-method-guesser#serial) + + * Vulnerable Methods + * Description: + + > The actual RMI services behind the wrapper may expose other methods that contain vulnerabilities. Obtaining the interface name + > by calling getTargetInterfaceName may allow to obtain the interface definition using online resources. If this is not possible, + > remote-method-guesser can be used to guess remote methods. + * References: + * [https://github.com/qtc-de/remote-method-guesser#guess](https://github.com/qtc-de/remote-method-guesser#guess) diff --git a/docs/rmi/unicast-remote-object.md b/docs/rmi/unicast-remote-object.md index a5ce777e..0acde084 100644 --- a/docs/rmi/unicast-remote-object.md +++ b/docs/rmi/unicast-remote-object.md @@ -307,7 +307,7 @@ to provide a permissive ``SecurityManager`` yourself in advance to prevent this. it is not made part of it's default codebase yet: ```java -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.lang.reflect.Constructor; import java.rmi.Remote; @@ -320,7 +320,7 @@ import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject; import java.util.Properties; -import de.qtc.rmg.networking.RMIWhisperer; +import eu.tneitzel.rmg.networking.RMIWhisperer; import sun.rmi.server.Activation; import sun.rmi.server.ActivationGroupImpl; diff --git a/plugins/GenericPrint.java b/plugins/GenericPrint.java index 468d614f..0670f808 100644 --- a/plugins/GenericPrint.java +++ b/plugins/GenericPrint.java @@ -5,12 +5,12 @@ import java.util.Map; import java.util.Map.Entry; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.plugin.IResponseHandler; -import de.qtc.rmg.utils.ActivatableWrapper; -import de.qtc.rmg.utils.RemoteObjectWrapper; -import de.qtc.rmg.utils.UnicastWrapper; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.plugin.IResponseHandler; +import eu.tneitzel.rmg.utils.ActivatableWrapper; +import eu.tneitzel.rmg.utils.RemoteObjectWrapper; +import eu.tneitzel.rmg.utils.UnicastWrapper; /** * GenericPrint is an rmg ResponseHandler plugin that attempts to print all incoming diff --git a/pom.xml b/pom.xml index 709569f9..5232c957 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,37 @@ - - + 4.0.0 - de.qtc.rmg + eu.tneitzel remote-method-guesser - remote-method-guesser + 5.0.0 jar - 4.4.1 - Identify common misconfigurations on Java RMI endpoints + + ${project.artifactId} + Java RMI Vulnerability Scanner + https://github.com/qtc-de/remote-method-guesser + + + + GPL-v3.0 + http://www.gnu.org/licenses/gpl-3.0.txt + + + + + + qtc-de + Tobias Neitzel + secure@tneitzel.eu + https://tneitzel.eu/ + + + + + scm:git:git://github.com/qtc-de/remote-method-guesser.git + scm:git:ssh://github.com:qtc-de/remote-method-guesser.git + https://github.com/qtc-de/remote-method-guesser/tree/master + UTF-8 @@ -18,7 +40,6 @@ - net.sourceforge.argparse4j argparse4j @@ -28,7 +49,7 @@ commons-io commons-io - 2.11.0 + 2.15.1 @@ -40,13 +61,17 @@ org.yaml snakeyaml - 1.33 + 2.2 + + org.springframework + spring-remoting + 2.0.8 + - src @@ -81,10 +106,13 @@ rmg-${project.version} - de.qtc.rmg.Starter + eu.tneitzel.rmg.Starter true + + Tobias Neitzel (@qtc_de) + java.base/java.io java.base/java.lang diff --git a/resources/known-endpoints/known-endpoints.yml b/resources/known-endpoints/known-endpoints.yml index eac952cb..6ba83a91 100644 --- a/resources/known-endpoints/known-endpoints.yml +++ b/resources/known-endpoints/known-endpoints.yml @@ -27,7 +27,7 @@ knownEndpoints: other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean is therefore most of the time equivalent to remote code execution. references: - - https://github.com/qtc-de/beanshooter + - https://github.com/qtc-de/beanshooter#generic-deploy - name: Deserialization description: | @@ -36,7 +36,7 @@ knownEndpoints: actual JMX communication using the RMIConnection object is not filtered. Therefore, if you can establish a working JMX connection, you can also perform deserialization attacks. references: - - https://github.com/qtc-de/beanshooter + - https://github.com/qtc-de/beanshooter#serial - name: JMX Connection @@ -88,14 +88,14 @@ knownEndpoints: other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean is therefore most of the time equivalent to remote code execution. references: - - https://github.com/qtc-de/beanshooter + - https://github.com/qtc-de/beanshooter#generic-deploy - name: Deserialization description: | All communication to JMX that is dispatched over this remote object is not filtered for deserialization attacks. Therefore, each suitable method can be used to pass a deserialization payload to the server. references: - - https://github.com/qtc-de/beanshooter + - https://github.com/qtc-de/beanshooter#serial - name: RMI Registry @@ -125,7 +125,7 @@ knownEndpoints: JEP290, deserialization filters were introduced. Depending on the patch level of the corresponding Java instance, the filters may be bypassed. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#serial - name: Localhost Bypass description: | @@ -133,7 +133,7 @@ knownEndpoints: RMI registry instance. In 2019, a bypass for this localhost restriction was identified, that may allows an attacker to bind, rebind or unbind names from remote. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#bind-rebind-and-unbind - name: UnicastRemoteObject description: | @@ -173,7 +173,7 @@ knownEndpoints: attacks. With JEP290, deserialization filters were introduced. The deserialization filters of DGC endpoints are more restrictive than for the RMI registry and there a no known bypasses. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#serial - name: RMI Activation System @@ -210,7 +210,7 @@ knownEndpoints: description: | When accessed from localhost, the ActivationSystem is vulnerable to deserialization attacks. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#serial - name: RMI Activation Group @@ -235,7 +235,7 @@ knownEndpoints: description: | ActivationGroup remote objects do not use a deserialization filter. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#serial - name: RMI Activator @@ -259,4 +259,37 @@ knownEndpoints: Deserialization filters were never applied to the activation system and the Activator can be used for deserialization attacks. references: - - https://github.com/qtc-de/remote-method-guesser + - https://github.com/qtc-de/remote-method-guesser#serial + + +- name: Spring RmiInvocationHandler + className: + - org.springframework.remoting.rmi.RmiInvocationHandler + + description: | + RmiInvocationHandler is basically a wrapper around the actual RMI services that are implemented by the server. The + interface supports an invoke method that forwards the call to the actual RMI service. Moreover, the getTargetInterfaceName + method can be used to retrieve the interface that is implemented by the actual RMI service. + + remoteMethods: + - java.lang.String getTargetInterfaceName() + - java.lang.Object invoke(org.springframework.remoting.support.RemoteInvocation invo) + + references: + - https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/rmi/RmiInvocationHandler.html + - https://docs.spring.io/spring-framework/docs/5.3.0/javadoc-api/org/springframework/remoting/support/RemoteInvocation.html + + vulnerabilities: + - name: Deserialization + description: | + The invoke method uses a method parameter with non trivial type and is therefore vulnerable to arbitrary deserialization. + references: + - https://github.com/qtc-de/remote-method-guesser#serial + + - name: Vulnerable Methods + description: | + The actual RMI services behind the wrapper may expose other methods that contain vulnerabilities. Obtaining the interface name + by calling getTargetInterfaceName may allow to obtain the interface definition using online resources. If this is not possible, + remote-method-guesser can be used to guess remote methods. + references: + - https://github.com/qtc-de/remote-method-guesser#guess diff --git a/src/de/qtc/rmg/operations/MethodGuesser.java b/src/de/qtc/rmg/operations/MethodGuesser.java deleted file mode 100644 index 37ea4939..00000000 --- a/src/de/qtc/rmg/operations/MethodGuesser.java +++ /dev/null @@ -1,373 +0,0 @@ -package de.qtc.rmg.operations; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.utils.ProgressBar; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.UnicastWrapper; - -/** - * The MethodGuesser class is used to brute force available remote methods on Java RMI endpoints. It uses - * low level Java RMI functions to invoke methods parsed from a wordlist with incorrect argument types. The - * server-side exception can be used as an indicator whether the invoked method exists on the server. - * - * When a RMI client calls a remote method, it establishes a TCP connection to the remote endpoint - * and sends (among others) the following information: - * - * - The ObjID of the RemoteObject that should receive the call - * - A method hash, that identifies the remote method to be called - * - A collection of method arguments to be used for the call - * - * During method guessing, remote-method-guesser uses a wordlist of Java methods and computes their hash - * values. The corresponding hashes are then sent to the server, together with invalid method arguments. - * If the remote method does not exist, the server throws an exception complaining about an unknown method - * hash. On the other hand, if the remote method exists, the server will complain about the invalid method - * arguments. - * - * @author Tobias Neitzel (@qtc_de) - */ -public class MethodGuesser { - - private int padding = 0; - private final ProgressBar progressBar; - - private Set candidates; - private List clientList; - private List knownClientList; - private List> candidateSets; - - /** - * To create a MethodGuesser you need to pass the references for remote objects you want to guess on. - * These are usually obtained from the RMI registry and can be passed as an array of UnicastWrapper. - * Furthermore, you need to specify a Set of MethodCandidates that represents the methods you want - * to guess. - * - * @param remoteObjects Array of looked up remote objects from the RMI registry - * @param candidates MethodCandidates that should be guessed - */ - public MethodGuesser(UnicastWrapper[] remoteObjects, Set candidates) - { - this.candidates = candidates; - - this.knownClientList = new ArrayList(); - this.candidateSets = RMGUtils.splitSet(candidates, RMGOption.THREADS.getValue()); - - if( !RMGOption.GUESS_FORCE_GUESSING.getBool() ) - remoteObjects = handleKnownMethods(remoteObjects); - - this.clientList = initClientList(remoteObjects); - this.progressBar = new ProgressBar(candidates.size() * clientList.size(), 37); - } - - /** - * This function is basically used to prevent guessing on duplicate classes. Some RMI endpoints bind - * multiple instances of the same remote class to the registry. Guessing each of them is usually not - * what you want, as they use the same implementation. This function checks the bound class names for - * duplicates and handles them according to the RMGOption.GUESS_DUPLICATES value. - * - * If guessing duplicates was requested, the function creates a new RemoteObjectClient for each bound name. - * If guessing duplicates was not requested, only the first boundName is used to create a RemoteObjectClient - * and all other bound names that are based on the same class are registered as duplicates. - * - * @param remoteObjects Array of looked up remote objects from the RMI registry - */ - private List initClientList(UnicastWrapper[] remoteObjects) - { - List remoteObjectClients = new ArrayList(); - setPadding(remoteObjects); - - if( !RMGOption.GUESS_DUPLICATES.getBool() ) - remoteObjects = UnicastWrapper.handleDuplicates(remoteObjects); - - for( UnicastWrapper o : remoteObjects ) { - - RemoteObjectClient client = new RemoteObjectClient(o); - remoteObjectClients.add(client); - } - - if( UnicastWrapper.hasDuplicates(remoteObjects) ) - printDuplicates(remoteObjects); - - return remoteObjectClients; - } - - /** - * This function is just used for displaying the result. It is called when iterating over the boundNames - * and saves the length of the longest boundName. This is used as a padding value for the other boundNames. - * - * @param remoteObjects List containing all UnicastWrapper used during the guess operation - */ - private void setPadding(UnicastWrapper[] remoteObjects) - { - for(UnicastWrapper o : remoteObjects) { - - if( padding < o.boundName.length() ) - padding = o.boundName.length(); - } - } - - /** - * This function prints a short info text that multiple bound names on the RMI server implement - * the same class / interface and that only one of them is used during method guessing. The - * output is disabled by default and only enabled if --verbose was used. - * - * @param remoteObjects List containing all UnicastWrapper used during the guess operation - */ - private void printDuplicates(UnicastWrapper[] remoteObjects) - { - Logger.disableIfNotVerbose(); - Logger.printInfoBox(); - - Logger.println("The following bound names use the same remote class:"); - Logger.lineBreak(); - Logger.increaseIndent(); - - for( UnicastWrapper remoteObject : remoteObjects ) { - - String[] duplicates = remoteObject.getDuplicateBoundNames(); - - if( duplicates.length == 0 ) - continue; - - Logger.printlnMixedBlue("-", remoteObject.boundName); - Logger.increaseIndent(); - - for(String dup : duplicates ) { - Logger.printlnMixedYellow("-->", dup); - } - - Logger.decreaseIndent(); - } - - Logger.decreaseIndent(); - Logger.lineBreak(); - Logger.printlnMixedBlue("Method guessing", "is skipped", "for duplicate remote classes."); - Logger.printlnMixedYellow("You can use", "--guess-duplicates", "to guess them anyway."); - Logger.decreaseIndent(); - Logger.enable(); - } - - /** - * When known remote objects are encountered and --force-guessing was not used, the corresponding remote methods - * are added automatically to the list of guessed methods. This function performs this task and also prints according - * information for the user. - * - * @param remoteObjects Array of looked up remote objects from the RMI registry - * @return Array of unknown remote objects - */ - private UnicastWrapper[] handleKnownMethods(UnicastWrapper[] remoteObjects) - { - ArrayList unknown = new ArrayList(); - - for(UnicastWrapper o : remoteObjects) { - - if(!o.isKnown()) - unknown.add(o); - - else { - RemoteObjectClient knownClient = new RemoteObjectClient(o); - knownClient.addRemoteMethods(RMGUtils.getKnownMethods(o.className)); - - knownClientList.add(knownClient); - } - } - - if(knownClientList.size() != 0) { - - Logger.disableIfNotVerbose(); - Logger.printInfoBox(); - - Logger.println("The following bound names use a known remote object class:"); - Logger.lineBreak(); - Logger.increaseIndent(); - - for(RemoteObjectClient o : knownClientList) - Logger.printlnMixedBlue("-", o.getBoundName() + " (" + o.getBoundName() + ")"); - - Logger.decreaseIndent(); - Logger.lineBreak(); - Logger.printlnMixedBlue("Method guessing", "is skipped", "and known methods are listed instead."); - Logger.printlnMixedYellow("You can use", "--force-guessing", "to guess methods anyway."); - Logger.decreaseIndent(); - Logger.lineBreak(); - Logger.enable(); - } - - return unknown.toArray(new UnicastWrapper[0]); - } - - /** - * Helper function that prints some visual text when the guesser is started. Just contains information - * on the number of methods that are guessed or the concrete method signature (if specified). - */ - public void printGuessingIntro() - { - int count = candidates.size(); - - if(count == 0) { - Logger.eprintlnMixedYellow("List of candidate methods contains", "0", "elements."); - Logger.eprintln("Please use a valid and non empty wordlist file."); - RMGUtils.exit(); - - } else if( clientList.size() == 0 ) { - return; - } - - Logger.lineBreak(); - Logger.printlnMixedYellow("Starting Method Guessing on", String.valueOf(count), "method signature(s)."); - - if( count == 1 ) { - Logger.printlnMixedBlue("Method signature:", ((MethodCandidate)candidates.toArray()[0]).getSignature() + "."); - } - } - - /** - * This method starts the actual guessing process. It creates a GuessingWorker for each remoteClient in the clientList - * and for each Set of MethodCandidates in the candidateSets. - * - * @return List of RemoteObjectClient containing the successfully guessed methods. Only clients containing - * guessed methods are returned. Clients without guessed methods are filtered. - */ - public List guessMethods() - { - Logger.lineBreak(); - - if( clientList.size() == 0 ) { - clientList.addAll(knownClientList); - return clientList; - } - - Logger.increaseIndent(); - Logger.printlnYellow("MethodGuesser is running:"); - Logger.increaseIndent(); - Logger.printlnBlue("--------------------------------"); - - ExecutorService pool = Executors.newFixedThreadPool(RMGOption.THREADS.getValue()); - - for( RemoteObjectClient client : clientList ) { - for( Set candidates : candidateSets ) { - Runnable r = new GuessingWorker(client, candidates); - pool.execute(r); - } - } - - try { - pool.shutdown(); - pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - - } catch (InterruptedException e) { - Logger.eprintln("Interrupted!"); - } - - Logger.decreaseIndent(); - Logger.lineBreak(); - Logger.printlnYellow("done."); - Logger.lineBreak(); - - clientList = RemoteObjectClient.filterEmpty(clientList); - clientList.addAll(knownClientList); - - return clientList; - } - - /** - * The GuessingWorker class performs the actual method guessing in terms of RMI calls. It implements Runnable and - * is intended to be run within a thread pool. Each GuessingWorker gets assigned a Set of MethodCandidates and iterates - * over the corresponding set. It uses the obtained RemoteObjectClient object to dispatch a call to the candidates and - * inspects the server-side exception to determine whether the method exists on the remote object. - * - * @author Tobias Neitzel (@qtc_de) - */ - private class GuessingWorker implements Runnable { - - private String boundName; - private Set candidates; - private RemoteObjectClient client; - - /** - * Initialize the guessing worker with all the required information. - * - * @param client RemoteObjectClient to the targeted remote object - * @param candidates MethodCandidates to guess - */ - public GuessingWorker(RemoteObjectClient client, Set candidates) - { - this.client = client; - this.boundName = client.getBoundName(); - this.candidates = candidates; - } - - /** - * This function is called when a guessed MethodCandidate exists. It creates a corresponding log entry and - * saves the candidate within the results map. - * - * @param candidate MethodCandidate that was successfully guessed - */ - private void logHit(MethodCandidate candidate) - { - String prefix = Logger.blue("[ " + Logger.padRight(boundName, padding) + " ] "); - Logger.printlnMixedYellow(prefix + "HIT! Method with signature", candidate.getSignature(), "exists!"); - client.addRemoteMethod(candidate); - } - - /** - * Invokes the assigned MethodCandidates. Methods are invoked by using a specially crafting argument array. - * The array is crafted in a way that method calls will never be fully dispatched on the server side while - * simultaneously preventing corruption of the underlying TCP stream. This allows to reuse the TCP connection - * during method guessing which makes the process much faster, especially on TLS protected connections. - */ - public void run() { - - for( MethodCandidate candidate : candidates ) { - - try { - client.guessingCall(candidate); - logHit(candidate); // If there was no exception, the method exists (zero arg / valid call) - - } catch(java.rmi.ServerException e) { - - Throwable cause = ExceptionHandler.getCause(e); - - /* - * In case of an existing method, the specially crafted argument array that is used during guessing calls - * will always lead to one of the following exceptions. These are caught and indicate an existing method. - * One could also attempt to catch the 'unrecognized method hash' exception from the server to match non - * existing methods, but this requires an additional string compare that might be slower. - */ - if( cause instanceof java.io.OptionalDataException || cause instanceof java.io.StreamCorruptedException) { - logHit(candidate); - } - - } catch(Exception e) { - - /* - * If we end up here, an unexpected exception was raised that indicates a general error. - */ - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - - String info = "Caught unexpected " + e.getClass().getName() + " during method guessing.\n" - +"Please report this to improve rmg :)\n" - +"Stack-Trace:\n" - +writer.toString(); - - Logger.println(info); - - } finally { - progressBar.taskDone(); - } - } - } - } -} diff --git a/src/de/qtc/rmg/Starter.java b/src/eu/tneitzel/rmg/Starter.java similarity index 66% rename from src/de/qtc/rmg/Starter.java rename to src/eu/tneitzel/rmg/Starter.java index 47e40a5d..aedaac5d 100644 --- a/src/de/qtc/rmg/Starter.java +++ b/src/eu/tneitzel/rmg/Starter.java @@ -1,9 +1,9 @@ -package de.qtc.rmg; +package eu.tneitzel.rmg; -import de.qtc.rmg.internal.ArgumentHandler; -import de.qtc.rmg.operations.Dispatcher; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.internal.ArgumentHandler; +import eu.tneitzel.rmg.operations.Dispatcher; +import eu.tneitzel.rmg.operations.Operation; +import eu.tneitzel.rmg.utils.RMGUtils; /** * The Starter class contains the entrypoint of remote-method-guesser. It is responsible @@ -11,10 +11,10 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class Starter { - - public static void main(String[] argv) { - +public class Starter +{ + public static void main(String[] argv) + { ArgumentHandler handler = new ArgumentHandler(argv); Operation operation = handler.getAction(); diff --git a/src/de/qtc/rmg/endpoints/KnownEndpoint.java b/src/eu/tneitzel/rmg/endpoints/KnownEndpoint.java similarity index 94% rename from src/de/qtc/rmg/endpoints/KnownEndpoint.java rename to src/eu/tneitzel/rmg/endpoints/KnownEndpoint.java index 6972bc7c..698bd7b4 100644 --- a/src/de/qtc/rmg/endpoints/KnownEndpoint.java +++ b/src/eu/tneitzel/rmg/endpoints/KnownEndpoint.java @@ -1,8 +1,8 @@ -package de.qtc.rmg.endpoints; +package eu.tneitzel.rmg.endpoints; import java.util.List; -import de.qtc.rmg.io.Logger; +import eu.tneitzel.rmg.io.Logger; /** * The KnownEndpoint class represents a well known RMI endpoint. By the term 'well known' we mean @@ -16,8 +16,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class KnownEndpoint { - +public class KnownEndpoint +{ private String name; private String description; @@ -96,9 +96,13 @@ public void printEnum() String format = String.format("(known class: %s)", name); if( vulnerabilities.size() == 0 ) + { Logger.printlnPlainGreen(format); + } else + { Logger.printlnPlainYellow(format); + } } } diff --git a/src/de/qtc/rmg/endpoints/KnownEndpointHolder.java b/src/eu/tneitzel/rmg/endpoints/KnownEndpointHolder.java similarity index 84% rename from src/de/qtc/rmg/endpoints/KnownEndpointHolder.java rename to src/eu/tneitzel/rmg/endpoints/KnownEndpointHolder.java index 6d6aca97..8aab1428 100644 --- a/src/de/qtc/rmg/endpoints/KnownEndpointHolder.java +++ b/src/eu/tneitzel/rmg/endpoints/KnownEndpointHolder.java @@ -1,8 +1,9 @@ -package de.qtc.rmg.endpoints; +package eu.tneitzel.rmg.endpoints; import java.io.InputStream; import java.util.List; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; @@ -15,8 +16,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class KnownEndpointHolder { - +public class KnownEndpointHolder +{ private List knownEndpoints = null; private static KnownEndpointHolder instance = null; @@ -53,13 +54,18 @@ public void setKnownEndpoints(List knownEndpoints) */ public KnownEndpoint lookup(String className) { - if( knownEndpoints == null ) + if (knownEndpoints == null) + { return null; + } - for( KnownEndpoint endpoint : knownEndpoints ) - - if( endpoint.getClassName().contains(className) ) + for (KnownEndpoint endpoint : knownEndpoints) + { + if (endpoint.getClassName().contains(className)) + { return endpoint; + } + } return null; } @@ -73,8 +79,10 @@ public KnownEndpoint lookup(String className) */ public boolean isKnown(String className) { - if( lookup(className) == null ) + if (lookup(className) == null) + { return false; + } return true; } @@ -92,8 +100,14 @@ public boolean isKnown(String className) */ public static KnownEndpointHolder getHolder() { - if( instance == null ) { - Yaml yaml = new Yaml(new Constructor(KnownEndpointHolder.class)); + if (instance == null) + { + LoaderOptions options = new LoaderOptions(); + + options.setAllowDuplicateKeys(false); + options.setAllowRecursiveKeys(false); + + Yaml yaml = new Yaml(new Constructor(KnownEndpointHolder.class, options)); InputStream stream = KnownEndpoint.class.getResourceAsStream(resource); instance = yaml.load(stream); diff --git a/src/de/qtc/rmg/endpoints/Vulnerability.java b/src/eu/tneitzel/rmg/endpoints/Vulnerability.java similarity index 93% rename from src/de/qtc/rmg/endpoints/Vulnerability.java rename to src/eu/tneitzel/rmg/endpoints/Vulnerability.java index 0b34a23f..076f8295 100644 --- a/src/de/qtc/rmg/endpoints/Vulnerability.java +++ b/src/eu/tneitzel/rmg/endpoints/Vulnerability.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.endpoints; +package eu.tneitzel.rmg.endpoints; import java.util.List; @@ -10,8 +10,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class Vulnerability { - +public class Vulnerability +{ private String name; private String description; diff --git a/src/de/qtc/rmg/exceptions/MalformedPluginException.java b/src/eu/tneitzel/rmg/exceptions/MalformedPluginException.java similarity index 74% rename from src/de/qtc/rmg/exceptions/MalformedPluginException.java rename to src/eu/tneitzel/rmg/exceptions/MalformedPluginException.java index 8e978e69..28b03712 100644 --- a/src/de/qtc/rmg/exceptions/MalformedPluginException.java +++ b/src/eu/tneitzel/rmg/exceptions/MalformedPluginException.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.exceptions; +package eu.tneitzel.rmg.exceptions; /** * MalformedPluginExceptions are thrown then an rmg plugin was specified on the command @@ -8,13 +8,14 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class MalformedPluginException extends Exception { - +public class MalformedPluginException extends Exception +{ private static final long serialVersionUID = 1L; public MalformedPluginException() {} - public MalformedPluginException(String message) { + public MalformedPluginException(String message) + { super(message); } } diff --git a/src/de/qtc/rmg/exceptions/SSRFException.java b/src/eu/tneitzel/rmg/exceptions/SSRFException.java similarity index 77% rename from src/de/qtc/rmg/exceptions/SSRFException.java rename to src/eu/tneitzel/rmg/exceptions/SSRFException.java index 3ed7b264..30f18f9f 100644 --- a/src/de/qtc/rmg/exceptions/SSRFException.java +++ b/src/eu/tneitzel/rmg/exceptions/SSRFException.java @@ -1,8 +1,8 @@ -package de.qtc.rmg.exceptions; +package eu.tneitzel.rmg.exceptions; import java.io.Serializable; -public class SSRFException extends Exception implements Serializable { - +public class SSRFException extends Exception implements Serializable +{ private static final long serialVersionUID = 1L; } diff --git a/src/de/qtc/rmg/exceptions/UnexpectedCharacterException.java b/src/eu/tneitzel/rmg/exceptions/UnexpectedCharacterException.java similarity index 82% rename from src/de/qtc/rmg/exceptions/UnexpectedCharacterException.java rename to src/eu/tneitzel/rmg/exceptions/UnexpectedCharacterException.java index c573ac8f..b271fe18 100644 --- a/src/de/qtc/rmg/exceptions/UnexpectedCharacterException.java +++ b/src/eu/tneitzel/rmg/exceptions/UnexpectedCharacterException.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.exceptions; +package eu.tneitzel.rmg.exceptions; /** * UnexpectedCharacterException may be thrown during the dynamic creation @@ -14,10 +14,12 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("serial") -public class UnexpectedCharacterException extends Exception { +public class UnexpectedCharacterException extends Exception +{ public UnexpectedCharacterException() {} - public UnexpectedCharacterException(String message) { + public UnexpectedCharacterException(String message) + { super(message); } } diff --git a/src/de/qtc/rmg/internal/ArgumentHandler.java b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java similarity index 85% rename from src/de/qtc/rmg/internal/ArgumentHandler.java rename to src/eu/tneitzel/rmg/internal/ArgumentHandler.java index 75922788..aede6a98 100644 --- a/src/de/qtc/rmg/internal/ArgumentHandler.java +++ b/src/eu/tneitzel/rmg/internal/ArgumentHandler.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.io.FileInputStream; import java.io.IOException; @@ -10,12 +10,12 @@ import java.util.Properties; import java.util.Set; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.operations.PortScanner; -import de.qtc.rmg.operations.ScanAction; -import de.qtc.rmg.plugin.PluginSystem; -import de.qtc.rmg.utils.RMGUtils; +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; @@ -56,10 +56,13 @@ public ArgumentHandler(String[] argv) Subparsers subparsers = parser.addSubparsers().help(" ").metavar("action").dest("action"); Operation.addSubparsers(subparsers); - try { + try + { args = parser.parseArgs(argv); + } - } catch (ArgumentParserException e) { + catch (ArgumentParserException e) + { parser.handleError(e); System.exit(1); } @@ -78,27 +81,30 @@ private Properties loadConfig(String filename) { Properties config = new Properties(); - try { + try + { InputStream configStream = null; configStream = ArgumentParser.class.getResourceAsStream(defaultConfiguration); config.load(configStream); configStream.close(); - if( filename != null ) { + if (filename != null) + { configStream = new FileInputStream(filename); config.load(configStream); configStream.close(); } + } - } catch( IOException e ) { + catch (IOException e) + { ExceptionHandler.unexpectedException(e, "loading", ".properties file", true); } return config; } - /** * Initializes the RMGOption enum and sets some static variables depending on the specified options. */ @@ -107,14 +113,17 @@ private void initialize() config = loadConfig(args.get(RMGOption.GLOBAL_CONFIG.name)); RMGOption.prepareOptions(args, config); - if( RMGOption.GLOBAL_NO_COLOR.getBool() ) + if (RMGOption.GLOBAL_NO_COLOR.getBool()) + { Logger.disableColor(); + } - if( RMGOption.SSRF_RAW.getBool() ) + if (RMGOption.SSRF_RAW.getBool()) + { Logger.disableStdout(); + } checkPortRange(); - PluginSystem.init(RMGOption.GLOBAL_PLUGIN.getValue()); } @@ -124,12 +133,15 @@ private void initialize() */ private void checkPortRange() { - if( RMGOption.TARGET_PORT.isNull() ) + if (RMGOption.TARGET_PORT.isNull()) + { return; + } int port = RMGOption.TARGET_PORT.getValue(); - if( port < 1 || port > 65535 ) { + if (port < 1 || port > 65535) + { Logger.eprintlnMixedYellow("The specified port number", String.valueOf(port), "is out of range."); RMGUtils.exit(); } @@ -144,8 +156,10 @@ public Operation getAction() { this.action = Operation.getByName(args.getString("action")); - if( action == null ) + if (action == null) + { ExceptionHandler.internalError("ArgumentHandler.getAction", "Invalid action was specified"); + } return action; } @@ -161,20 +175,24 @@ public Operation getAction() */ public String getRegMethod() { - if( regMethod != null ) + if (regMethod != null) + { return regMethod; + } String signature = RMGOption.REG_METHOD.getValue(); String[] supported = new String[]{"lookup", "unbind", "rebind", "bind"}; - if( signature == null ) { + if (signature == null) + { regMethod = "lookup"; return regMethod; } - for(String methodName : supported ) { - - if( signature.contains(methodName) ) { + for (String methodName : supported) + { + if (signature.contains(methodName)) + { regMethod = methodName; return methodName; } @@ -198,19 +216,23 @@ public String getRegMethod() */ public String getDgcMethod() { - if( dgcMethod != null ) + if (dgcMethod != null) + { return dgcMethod; + } String signature = RMGOption.DGC_METHOD.getValue(); - if( signature == null ) { + if (signature == null) + { dgcMethod = "clean"; return dgcMethod; } - for(String methodName : new String[]{"clean", "dirty"} ) { - - if( signature.contains(methodName) ) { + for (String methodName : new String[]{"clean", "dirty"}) + { + if (signature.contains(methodName)) + { dgcMethod = methodName; return methodName; } @@ -232,16 +254,20 @@ public String getDgcMethod() */ public RMIComponent getComponent() { - if( component != null ) + if (component != null) + { return component; + } RMIComponent targetComponent = RMIComponent.getByShortName(RMGOption.TARGET_COMPONENT.getValue()); - if( targetComponent == null ) + if (targetComponent == null) + { return null; + } - switch( targetComponent ) { - + switch (targetComponent) + { case REGISTRY: case DGC: case ACTIVATOR: @@ -255,7 +281,6 @@ public RMIComponent getComponent() } component = targetComponent; - return targetComponent; } @@ -270,15 +295,17 @@ public Object getGadget() String gadget = null; String command = null; - if( this.getAction() == Operation.BIND || this.getAction() == Operation.REBIND ) { - + 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.require(RMGOption.BIND_ADDRESS); + } - } else { + else + { gadget = (String) RMGOption.require(RMGOption.GADGET_NAME); command = RMGOption.require(RMGOption.GADGET_CMD); } @@ -310,8 +337,10 @@ public EnumSet getScanActions() { List scanActions = (List) RMGOption.ENUM_ACTION.value; - if( scanActions == null ) + if (scanActions == null) + { return EnumSet.allOf(ScanAction.class); + } return ScanAction.parseScanActions(scanActions); } @@ -333,18 +362,23 @@ public int[] getRmiPorts() String defaultPorts = config.getProperty("rmi_ports"); List portStrings = (List)RMGOption.SCAN_PORTS.value; - if( portStrings == null ) { + if (portStrings == null) + { portStrings = new ArrayList(); portStrings.add("-"); } - for(String portString : portStrings) { - - if( portString.equals("-") ) + for (String portString : portStrings) + { + if (portString.equals("-")) + { addPorts(defaultPorts, rmiPorts); + } else + { addPorts(portString, rmiPorts); + } } return rmiPorts.stream().mapToInt(i->i).toArray(); @@ -360,7 +394,8 @@ public void addPorts(String portString, Set portList) { String[] ports = portString.split(","); - for(String port: ports) { + for (String port: ports) + { addRange(port, portList); } } @@ -373,9 +408,10 @@ public void addPorts(String portString, Set portList) */ public void addRange(String portRange, Set portList) { - try { - - if(!portRange.contains("-")) { + try + { + if (!portRange.contains("-")) + { portList.add(Integer.valueOf(portRange)); return; } @@ -384,10 +420,14 @@ public void addRange(String portRange, Set portList) int start = Integer.valueOf(split[0]); int end = Integer.valueOf(split[1]); - for(int ctr = start; ctr <= end; ctr++) + for (int ctr = start; ctr <= end; ctr++) + { portList.add(ctr); + } + } - } catch( java.lang.NumberFormatException | java.lang.ArrayIndexOutOfBoundsException e ) { + catch (java.lang.NumberFormatException | java.lang.ArrayIndexOutOfBoundsException e) + { Logger.eprintlnMixedYellow("Caught unexpected", e.getClass().getSimpleName(), "while parsing RMI ports."); Logger.eprintlnMixedBlue("The specified value", portRange, "is invalid."); RMGUtils.exit(); diff --git a/src/de/qtc/rmg/internal/CodebaseCollector.java b/src/eu/tneitzel/rmg/internal/CodebaseCollector.java similarity index 81% rename from src/de/qtc/rmg/internal/CodebaseCollector.java rename to src/eu/tneitzel/rmg/internal/CodebaseCollector.java index e3806a01..1c3c22bd 100644 --- a/src/de/qtc/rmg/internal/CodebaseCollector.java +++ b/src/eu/tneitzel/rmg/internal/CodebaseCollector.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.net.MalformedURLException; import java.rmi.server.RMIClassLoader; @@ -7,7 +7,7 @@ import java.util.HashSet; import java.util.Set; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.RMGUtils; import javassist.CannotCompileException; import javassist.NotFoundException; @@ -49,6 +49,16 @@ * serialVersionUID. Since changing the serialVersionUID of an already existing class is not possible, we instead * create a new class where the full qualified class name is prefixed with an underscore. * + * From remote-method-guesser v5.0.0, this class has another purpose of handling custom socket factories. When the + * server exposes RMI objects with custom socket factory classes, this usually causes a ClassNotFound error, as + * we do not have the associated implementations on the client side. In this case, remote-method-guesser now attempts + * to create the socket factory class dynamically. Since the implementation is still unknown, it simply clones the + * default socket factory class LoopbackSslSocketFactory or LoopbackSocketFactory depending on the values for the + * --ssl, --socket-factory-ssl and --socket-factory-plain options. This works surprisingly often, as most custom socket + * factory classes use simple socket implementations under the hood. This dynamic class creation is done for all classes + * that are unknown and contain "SocketFactory" within their class name or end with "Factory" or "SF". The user can also + * specify other patterns using the --socket-factory option. + * * Summarized: * * 1. Extract server specified codebases and store them within a HashMap for later use @@ -58,8 +68,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class CodebaseCollector extends RMIClassLoaderSpi { - +public class CodebaseCollector extends RMIClassLoaderSpi +{ private static HashMap serialVersionUIDMap = new HashMap(); private static HashMap> codebases = new HashMap>(); private static RMIClassLoaderSpi originalLoader = RMIClassLoader.getDefaultProviderInstance(); @@ -78,31 +88,48 @@ public class CodebaseCollector extends RMIClassLoaderSpi { public Class loadClass(String codebase, String name, ClassLoader defaultLoader) throws MalformedURLException, ClassNotFoundException { Class resolvedClass = null; + long serialVersionUID = RMGOption.SERIAL_VERSION_UID.getValue(); addCodebase(codebase, name); codebase = null; - try { + if (serialVersionUIDMap.containsKey(name)) + { + serialVersionUID = serialVersionUIDMap.get(name); + name = "_" + name; + } + try + { if (name.endsWith("_Stub")) { - long serialVersionUID = RMGOption.SERIAL_VERSION_UID.getValue(); + RMGUtils.makeLegacyStub(name, serialVersionUID); + } + + else if (name.equals("sun.rmi.server.ActivatableRef")) + { + RMGUtils.makeActivatableRef(); + } - if (serialVersionUIDMap.containsKey(name)) + else if (!RMGOption.SOCKET_FACTORY.isNull()) + { + if (name.contains(RMGOption.SOCKET_FACTORY.getValue())) { - serialVersionUID = serialVersionUIDMap.get(name); - name = "_" + name; + RMGUtils.makeSocketFactory(name, serialVersionUID); } - - RMGUtils.makeLegacyStub(name, serialVersionUID); } - if (name.equals("sun.rmi.server.ActivatableRef")) - RMGUtils.makeActivatableRef(); + else if (name.contains("SocketFactory") || name.endsWith("Factory") || name.endsWith("SF")) + { + RMGUtils.makeSocketFactory(name, serialVersionUID); + } resolvedClass = originalLoader.loadClass(codebase, name, defaultLoader); - } catch (CannotCompileException | NotFoundException e) { + } + + catch (CannotCompileException | NotFoundException e) + { ExceptionHandler.internalError("loadClass", "Unable to compile unknown stub class."); } @@ -163,10 +190,14 @@ public ClassLoader getClassLoader(String codebase) throws MalformedURLException * function to return the codebase only supports String types. The currently selected approach * of rmg (which is the MaliciousOutputStream class) allows arbitrary objects and is therefore * more flexible. + * + * Since version v5.0.0, this function always returns null instead of invoking the original loaders + * getClassAnnotation method. This was changed, as the original loaders getClassAnnotation method + * leaked the local ysoserial path when using remote-method-guessers ysoserial integration. */ public String getClassAnnotation(Class cl) { - return originalLoader.getClassAnnotation(cl); + return null; } /** diff --git a/src/de/qtc/rmg/internal/ExceptionHandler.java b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java similarity index 98% rename from src/de/qtc/rmg/internal/ExceptionHandler.java rename to src/eu/tneitzel/rmg/internal/ExceptionHandler.java index 64871301..e0755493 100644 --- a/src/de/qtc/rmg/internal/ExceptionHandler.java +++ b/src/eu/tneitzel/rmg/internal/ExceptionHandler.java @@ -1,9 +1,9 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.rmi.server.ObjID; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.RMGUtils; /** * During the different RMI operations you have always a roughly same set of exceptions @@ -517,17 +517,31 @@ public static void connectException(Exception e, String callName) { Throwable t = ExceptionHandler.getCause(e); - if( t instanceof java.net.ConnectException ) { - + if (t instanceof java.net.ConnectException) + { String message = t.getMessage(); - if( message.contains("Connection refused") ) + if (message.contains("Connection refused")) + { ExceptionHandler.connectionRefused(e, callName, "call"); + } - if( message.contains("Network is unreachable") ) + else if (message.contains("Network is unreachable")) + { ExceptionHandler.networkUnreachable(e, callName, "call"); + } - } else { + else + { + Logger.eprintlnMixedYellow("Caught", "ConnectException", "during " + callName + " call."); + Logger.eprintlnMixedBlue("Exception message:", message); + showStackTrace(e); + RMGUtils.exit(); + } + } + + else + { ExceptionHandler.unexpectedException(e, callName, "call", true); } } diff --git a/src/de/qtc/rmg/internal/MethodArguments.java b/src/eu/tneitzel/rmg/internal/MethodArguments.java similarity index 81% rename from src/de/qtc/rmg/internal/MethodArguments.java rename to src/eu/tneitzel/rmg/internal/MethodArguments.java index d4d86de0..772b603e 100644 --- a/src/de/qtc/rmg/internal/MethodArguments.java +++ b/src/eu/tneitzel/rmg/internal/MethodArguments.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.util.Iterator; @@ -28,8 +28,8 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("rawtypes") -public class MethodArguments implements Iterable>, Iterator> { - +public class MethodArguments implements Iterable>, Iterator> +{ private int size = 0; private int capacity = 0; private int currentIndex = 0; @@ -43,24 +43,38 @@ public MethodArguments(int capacity) this.methodArguments = new Pair[capacity]; } + @SuppressWarnings("unchecked") + public MethodArguments(Object argumentObject, Class argumentClass) + { + this.capacity = 1; + this.methodArguments = new Pair[capacity]; + + methodArguments[size++] = new Pair(argumentObject, argumentClass); + } + @Override - public Iterator> iterator() { + public Iterator> iterator() + { return this; } @Override - public boolean hasNext() { + public boolean hasNext() + { return currentIndex < size; } @Override - public Pair next() { + public Pair next() + { return methodArguments[currentIndex++]; } public void add(Object argumentObject, Class argumentClass) { - if(size < capacity) + if (size < capacity) + { methodArguments[size++] = new Pair(argumentObject, argumentClass); + } } } diff --git a/src/de/qtc/rmg/internal/MethodCandidate.java b/src/eu/tneitzel/rmg/internal/MethodCandidate.java similarity index 90% rename from src/de/qtc/rmg/internal/MethodCandidate.java rename to src/eu/tneitzel/rmg/internal/MethodCandidate.java index f3a76861..db4f60a4 100644 --- a/src/de/qtc/rmg/internal/MethodCandidate.java +++ b/src/eu/tneitzel/rmg/internal/MethodCandidate.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -8,9 +8,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.RawObjectOutputStream; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.RawObjectOutputStream; +import eu.tneitzel.rmg.utils.RMGUtils; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtMethod; @@ -24,8 +24,8 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class MethodCandidate { - +public class MethodCandidate +{ private long hash; private CtMethod method; @@ -100,13 +100,14 @@ private void initialize(CtMethod method) throws NotFoundException this.argumentCount = types.length; this.hash = getCtMethodHash(method); - if( argumentCount == 0 ) { - + if (argumentCount == 0) + { this.isVoid = true; this.primitiveSize = -99; + } - } else { - + else + { this.isVoid = false; this.primitiveSize = RMGUtils.getPrimitiveSize(types); } @@ -132,10 +133,13 @@ private static long getCtMethodHash(CtMethod method) * @param methodSignature signature to compute the hash on * @return RMI method hash */ - private static long computeMethodHash(String methodSignature) { + private static long computeMethodHash(String methodSignature) + { long hash = 0; ByteArrayOutputStream sink = new ByteArrayOutputStream(127); - try { + + try + { MessageDigest md = MessageDigest.getInstance("SHA"); DataOutputStream out = new DataOutputStream(new DigestOutputStream(sink, md)); @@ -144,15 +148,24 @@ private static long computeMethodHash(String methodSignature) { // use only the first 64 bits of the digest for the hash out.flush(); byte hasharray[] = md.digest(); - for (int i = 0; i < Math.min(8, hasharray.length); i++) { + + for (int i = 0; i < Math.min(8, hasharray.length); i++) + { hash += ((long) (hasharray[i] & 0xFF)) << (i * 8); } - } catch (IOException ignore) { + } + + catch (IOException ignore) + { /* can't happen, but be deterministic anyway. */ hash = -1; - } catch (NoSuchAlgorithmException complain) { + } + + catch (NoSuchAlgorithmException complain) + { throw new SecurityException(complain.getMessage()); } + return hash; } @@ -170,17 +183,20 @@ private static long computeMethodHash(String methodSignature) { @SuppressWarnings("restriction") public void sendArguments(ObjectOutputStream oo) throws IOException { - if( this.primitiveSize == -99 ) { - + if (this.primitiveSize == -99) + { oo.flush(); + } - } else if( this.primitiveSize == -1 ) { - + else if (this.primitiveSize == -1) + { oo.flush(); RawObjectOutputStream rout = new RawObjectOutputStream(oo); rout.writeRaw(sun.rmi.transport.TransportConstants.Ping); + } - } else { + else + { oo.write(new byte[this.primitiveSize]); oo.writeByte(1); } @@ -208,11 +224,15 @@ public CtClass[] getParameterTypes() throws CannotCompileException, NotFoundExce */ public String getName() throws CannotCompileException, NotFoundException { - if(this.method != null) + if (this.method != null) + { return this.getMethod().getName(); + } else + { return "method"; + } } /** @@ -244,14 +264,16 @@ public int getPrimitive(int selected) throws NotFoundException, CannotCompileExc { CtClass[] types = this.getParameterTypes(); - if(selected != -1) { - - if( selected >= types.length ) { + if (selected != -1) + { + if (selected >= types.length) + { Logger.eprintlnMixedYellow("Specified argument position", String.valueOf(selected), "is out of bounds."); return -1; } - if( types[selected].isPrimitive() ) { + if (types[selected].isPrimitive()) + { Logger.eprintlnMixedYellow("Specified argument position", String.valueOf(selected), "is a primitive type."); return -1; } @@ -260,17 +282,22 @@ public int getPrimitive(int selected) throws NotFoundException, CannotCompileExc } int result = -1; - for(int ctr = 0; ctr < types.length; ctr++) { - - if(!types[ctr].isPrimitive()) { - - if( types[ctr].getName().equals("java.lang.String") ) + for (int ctr = 0; ctr < types.length; ctr++) + { + if (!types[ctr].isPrimitive()) + { + if (types[ctr].getName().equals("java.lang.String")) + { result = ctr; + } else + { return ctr; + } } } + return result; } @@ -323,7 +350,8 @@ public boolean isVoid() */ public CtMethod getMethod() throws CannotCompileException, NotFoundException { - if( this.method == null ) { + if (this.method == null) + { MethodCandidate tmp = new MethodCandidate(this.getSignature()); this.method = tmp.getMethod(); } @@ -349,10 +377,13 @@ public String getArgumentTypeName(int position) { String typeName = "None"; - try { + try + { typeName = this.method.getParameterTypes()[position].getName(); + } - } catch( Exception e ) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "parameter type", "determination", true); } @@ -365,8 +396,10 @@ public String getArgumentTypeName(int position) @Override public boolean equals(Object o) { - if(!(o instanceof MethodCandidate)) + if (!(o instanceof MethodCandidate)) + { return false; + } MethodCandidate other = (MethodCandidate)o; return this.hash == other.getHash(); @@ -376,7 +409,8 @@ public boolean equals(Object o) * MethodCandidates are hashed according to their method hash. */ @Override - public int hashCode(){ + public int hashCode() + { return Long.hashCode(this.hash); } } diff --git a/src/de/qtc/rmg/internal/Pair.java b/src/eu/tneitzel/rmg/internal/Pair.java similarity index 94% rename from src/de/qtc/rmg/internal/Pair.java rename to src/eu/tneitzel/rmg/internal/Pair.java index 31e9a54e..232a652e 100644 --- a/src/de/qtc/rmg/internal/Pair.java +++ b/src/eu/tneitzel/rmg/internal/Pair.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; /** * For the MethodArguments class, a Pair type is required. Unfortunately, Java 8 does not support such a diff --git a/src/de/qtc/rmg/internal/RMGOption.java b/src/eu/tneitzel/rmg/internal/RMGOption.java similarity index 95% rename from src/de/qtc/rmg/internal/RMGOption.java rename to src/eu/tneitzel/rmg/internal/RMGOption.java index 811032ea..9210622e 100644 --- a/src/de/qtc/rmg/internal/RMGOption.java +++ b/src/eu/tneitzel/rmg/internal/RMGOption.java @@ -1,11 +1,11 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.util.EnumSet; import java.util.Properties; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.utils.RMGUtils; +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; @@ -104,8 +104,14 @@ public enum RMGOption { DGC_METHOD("--dgc-method", "method to use for dgc operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), REG_METHOD("--registry-method", "method to use for registry operations", Arguments.store(), RMGOptionGroup.ACTION, "method"), SERIAL_VERSION_UID("--serial-version-uid", "serialVersionUID to use for RMI stubs", Arguments.store(), RMGOptionGroup.ACTION, "uid"), - PAYLOAD_SERIAL_VERSION_UID("--payload-serial-version-uid", "serialVersionUID to use for payload classes", Arguments.store(), RMGOptionGroup.ACTION, "uid"); + PAYLOAD_SERIAL_VERSION_UID("--payload-serial-version-uid", "serialVersionUID to use for payload classes", Arguments.store(), RMGOptionGroup.ACTION, "uid"), + SOCKET_FACTORY_PLAIN("--socket-factory-plain", "enforce plaintext 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), + SOCKET_FACTORY("--socket-factory", "dynamically create a socket factory class with the specified name", Arguments.store(), RMGOptionGroup.CONNECTION, "classname"), + + SPRING_REMOTING("--spring-remoting", "enforce method calls to be dispatched via spring remoting", Arguments.storeTrue(), RMGOptionGroup.CONNECTION), + GENERIC_PRINT("--return-value", "attempt to output the return value using GenericPrint", Arguments.storeTrue(), RMGOptionGroup.ACTION); public final String name; public final String description; diff --git a/src/de/qtc/rmg/internal/RMGOptionGroup.java b/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java similarity index 96% rename from src/de/qtc/rmg/internal/RMGOptionGroup.java rename to src/eu/tneitzel/rmg/internal/RMGOptionGroup.java index 7d517d41..6d36494a 100644 --- a/src/de/qtc/rmg/internal/RMGOptionGroup.java +++ b/src/eu/tneitzel/rmg/internal/RMGOptionGroup.java @@ -1,8 +1,8 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; import java.util.HashMap; -import de.qtc.rmg.operations.Operation; +import eu.tneitzel.rmg.operations.Operation; import net.sourceforge.argparse4j.inf.ArgumentGroup; import net.sourceforge.argparse4j.inf.ArgumentParser; @@ -62,4 +62,4 @@ public ArgumentGroup addArgumentGroup(ArgumentParser argParser, Operation operat return group; } -} \ No newline at end of file +} diff --git a/src/de/qtc/rmg/internal/RMIComponent.java b/src/eu/tneitzel/rmg/internal/RMIComponent.java similarity index 97% rename from src/de/qtc/rmg/internal/RMIComponent.java rename to src/eu/tneitzel/rmg/internal/RMIComponent.java index 248cdb83..36045ea3 100644 --- a/src/de/qtc/rmg/internal/RMIComponent.java +++ b/src/eu/tneitzel/rmg/internal/RMIComponent.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.internal; +package eu.tneitzel.rmg.internal; /** * The RMIComponent enum represents the different RMI components that may be targeted @@ -47,4 +47,4 @@ public static RMIComponent getByShortName(String shortName) return null; } -} \ No newline at end of file +} diff --git a/src/de/qtc/rmg/io/DevNullOutputStream.java b/src/eu/tneitzel/rmg/io/DevNullOutputStream.java similarity index 93% rename from src/de/qtc/rmg/io/DevNullOutputStream.java rename to src/eu/tneitzel/rmg/io/DevNullOutputStream.java index 3ad664c4..35e3cb16 100644 --- a/src/de/qtc/rmg/io/DevNullOutputStream.java +++ b/src/eu/tneitzel/rmg/io/DevNullOutputStream.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.IOException; import java.io.OutputStream; diff --git a/src/de/qtc/rmg/io/Formatter.java b/src/eu/tneitzel/rmg/io/Formatter.java similarity index 62% rename from src/de/qtc/rmg/io/Formatter.java rename to src/eu/tneitzel/rmg/io/Formatter.java index b690111b..75b6d224 100644 --- a/src/de/qtc/rmg/io/Formatter.java +++ b/src/eu/tneitzel/rmg/io/Formatter.java @@ -1,27 +1,33 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMISocketFactory; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Set; -import de.qtc.rmg.endpoints.KnownEndpoint; -import de.qtc.rmg.endpoints.Vulnerability; -import de.qtc.rmg.internal.CodebaseCollector; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.operations.RemoteObjectClient; -import de.qtc.rmg.utils.ActivatableWrapper; -import de.qtc.rmg.utils.RemoteObjectWrapper; -import de.qtc.rmg.utils.UnicastWrapper; +import javax.rmi.ssl.SslRMIClientSocketFactory; + +import eu.tneitzel.rmg.endpoints.KnownEndpoint; +import eu.tneitzel.rmg.endpoints.KnownEndpointHolder; +import eu.tneitzel.rmg.endpoints.Vulnerability; +import eu.tneitzel.rmg.internal.CodebaseCollector; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.operations.RemoteObjectClient; +import eu.tneitzel.rmg.utils.ActivatableWrapper; +import eu.tneitzel.rmg.utils.RemoteObjectWrapper; +import eu.tneitzel.rmg.utils.SpringRemotingWrapper; +import eu.tneitzel.rmg.utils.UnicastWrapper; /** * The formatter class is used to print formatted output for the enum and guess operations. * * @author Tobias Neitzel (@qtc_de) */ -public class Formatter { - +public class Formatter +{ /** * Creates a formatted list of available bound names and their corresponding classes. Classes * are divided in known classes (classes that are available on the current class path) and @@ -36,26 +42,38 @@ public void listBoundNames(RemoteObjectWrapper[] remoteObjects) Logger.lineBreak(); Logger.increaseIndent(); - if( remoteObjects == null || remoteObjects.length == 0 ) { + if (remoteObjects == null || remoteObjects.length == 0) + { Logger.println("- No objects are bound to the registry."); return; } - for(RemoteObjectWrapper remoteObject : remoteObjects) { - + for (RemoteObjectWrapper remoteObject : remoteObjects) + { Logger.printlnMixedYellow("-", remoteObject.boundName); - if( remoteObject.remoteObject == null) + if (remoteObject.remoteObject == null) + { continue; + } Logger.increaseIndent(); - if( remoteObject.isKnown() ) { - Logger.printMixedBlue("-->", remoteObject.className, ""); + if (remoteObject instanceof SpringRemotingWrapper) + { + Logger.printMixedBlue("-->", SpringRemotingWrapper.invocationHandlerClass, ""); + KnownEndpointHolder.getHolder().lookup(SpringRemotingWrapper.invocationHandlerClass).printEnum(); + } + + else if (remoteObject.isKnown()) + { + Logger.printMixedBlue("-->", remoteObject.getInterfaceName(), ""); remoteObject.knownEndpoint.printEnum(); + } - } else { - Logger.printMixedBlue("-->", remoteObject.className); + else + { + Logger.printMixedBlue("-->", remoteObject.getInterfaceName()); Logger.printlnPlainMixedPurple("", "(unknown class)"); } @@ -73,7 +91,8 @@ public void listBoundNames(RemoteObjectWrapper[] remoteObjects) */ public void listGuessedMethods(List results) { - if( results.isEmpty() ) { + if (results.isEmpty()) + { Logger.printlnBlue("No remote methods identified :("); return; } @@ -82,15 +101,24 @@ public void listGuessedMethods(List results) Logger.lineBreak(); Logger.increaseIndent(); - for(RemoteObjectClient client : results ) { - + for (RemoteObjectClient client : results) + { List methods = client.remoteMethods; Logger.printlnMixedBlue("-", String.join(" == ", client.getBoundNames())); Logger.increaseIndent(); - for( MethodCandidate m : methods ) { - Logger.printlnMixedYellow("-->", m.getSignature()); + for (MethodCandidate m : methods) + { + if (client.remoteObject instanceof SpringRemotingWrapper) + { + Logger.printlnMixedYellow("-->",SpringRemotingWrapper.getSignature(m)); + } + + else + { + Logger.printlnMixedYellow("-->", m.getSignature()); + } } Logger.decreaseIndent(); @@ -112,19 +140,21 @@ public void listCodebases() Logger.increaseIndent(); HashMap> codebases = CodebaseCollector.getCodebases(); - if(codebases.isEmpty()) { + if (codebases.isEmpty()) + { Logger.printlnMixedYellow("- The remote server", "does not", "expose any codebases."); Logger.decreaseIndent(); return; } - for( Entry> item : codebases.entrySet() ) { - + for (Entry> item : codebases.entrySet()) + { Logger.printlnMixedYellow("-", item.getKey()); Logger.increaseIndent(); Iterator iterator = item.getValue().iterator(); - while( iterator.hasNext() ) { + while (iterator.hasNext()) + { Logger.printlnMixedBlue("-->", iterator.next()); } @@ -152,8 +182,10 @@ public void listKnownEndpoint(KnownEndpoint knownEndpoint) Logger.printlnBlue("Class Name:"); Logger.increaseIndent(); - for(String className : knownEndpoint.getClassName()) + for (String className : knownEndpoint.getClassName()) + { Logger.printlnMixedYellow("-", className); + } Logger.decreaseIndent(); Logger.lineBreak(); @@ -163,8 +195,10 @@ public void listKnownEndpoint(KnownEndpoint knownEndpoint) String[] lines = knownEndpoint.getDescription().split("\n"); - for( String line : lines) + for (String line : lines) + { Logger.printlnYellow(line); + } Logger.decreaseIndent(); Logger.lineBreak(); @@ -172,8 +206,10 @@ public void listKnownEndpoint(KnownEndpoint knownEndpoint) Logger.printlnBlue("Remote Methods:"); Logger.increaseIndent(); - for(String remoteMethod : knownEndpoint.getRemoteMethods()) + for (String remoteMethod : knownEndpoint.getRemoteMethods()) + { Logger.printlnMixedYellow("-", remoteMethod); + } Logger.decreaseIndent(); Logger.lineBreak(); @@ -181,8 +217,10 @@ public void listKnownEndpoint(KnownEndpoint knownEndpoint) Logger.printlnBlue("References:"); Logger.increaseIndent(); - for(String reference : knownEndpoint.getReferences()) + for (String reference : knownEndpoint.getReferences()) + { Logger.printlnMixedYellow("-", reference); + } Logger.decreaseIndent(); listVulnerabilities(knownEndpoint.getVulnerabilities()); @@ -197,15 +235,17 @@ public void listKnownEndpoint(KnownEndpoint knownEndpoint) */ private void listVulnerabilities(List vulns) { - if( vulns == null || vulns.size() == 0 ) + if (vulns == null || vulns.size() == 0) + { return; + } Logger.lineBreak(); Logger.printlnBlue("Vulnerabilities:"); Logger.increaseIndent(); - for( Vulnerability vuln : vulns ) { - + for (Vulnerability vuln : vulns) + { Logger.lineBreak(); Logger.printlnBlue("-----------------------------------"); @@ -221,8 +261,10 @@ private void listVulnerabilities(List vulns) String[] lines = vuln.getDescription().split("\n"); - for( String line : lines) + for (String line : lines) + { Logger.printlnYellow(line); + } Logger.decreaseIndent(); Logger.lineBreak(); @@ -230,8 +272,10 @@ private void listVulnerabilities(List vulns) Logger.printlnBlue("References:"); Logger.increaseIndent(); - for(String reference : vuln.getReferences()) + for (String reference : vuln.getReferences()) + { Logger.printlnMixedYellow("-", reference); + } Logger.decreaseIndent(); } @@ -245,11 +289,51 @@ private void listVulnerabilities(List vulns) */ private void printRemoteRef(RemoteObjectWrapper wrapper) { - if (wrapper instanceof UnicastWrapper) + if (wrapper instanceof SpringRemotingWrapper) + { + printSpringRemoting((SpringRemotingWrapper)wrapper); + } + + else if (wrapper instanceof UnicastWrapper) + { printUnicastRef((UnicastWrapper)wrapper); + } else + { printActivatableRef((ActivatableWrapper)wrapper); + } + } + + /** + * Print information on a SpringRemotingWrapper. This information includes the real + * interface name that can be accessed via the spring remoting wrapper and all information + * that is printed by the printUnicastRef method. + * + * @param ref SpringRemotingWrapper containing the wrapper + */ + private void printSpringRemoting(SpringRemotingWrapper ref) + { + String interfaceName = ref.getInterfaceName(); + + if (interfaceName != null) + { + Logger.print(" "); + + if (ref.isKnown()) + { + Logger.printPlainMixedPurple("Spring Remoting Interface:", interfaceName); + ref.knownEndpoint.printEnum(); + } + + else + { + Logger.printPlainMixedPurple("Spring Remoting Interface:", interfaceName); + Logger.printlnPlainMixedBlue("", "(unknown class)"); + } + } + + printUnicastRef((UnicastWrapper)ref); } /** @@ -260,24 +344,29 @@ private void printRemoteRef(RemoteObjectWrapper wrapper) */ private void printUnicastRef(UnicastWrapper ref) { - if(ref == null || ref.remoteObject == null) + if (ref == null || ref.remoteObject == null) + { return; + } Logger.print(" "); Logger.printPlainMixedBlue("Endpoint:", ref.getTarget()); - switch( ref.isTLSProtected() ) { + RMIClientSocketFactory csf = ref.csf; - case 1: - Logger.printPlainMixedGreen(" TLS:", "yes"); - break; + if (csf == null || csf.getClass() == RMISocketFactory.class) + { + Logger.printPlainMixedRed(" CSF:", RMISocketFactory.class.getSimpleName()); + } - case -1: - Logger.printPlainMixedRed(" TLS:", "no"); - break; + else if (csf.getClass() == SslRMIClientSocketFactory.class) + { + Logger.printPlainMixedGreen(" CSF:", SslRMIClientSocketFactory.class.getSimpleName()); + } - default: - Logger.printPlainMixedPurple(" TLS:", "unknown"); + else + { + Logger.printPlainMixedPurple(" CSF:", csf.getClass().getName()); } Logger.printlnPlainMixedBlue(" ObjID:", ref.objID.toString()); @@ -293,8 +382,10 @@ private void printUnicastRef(UnicastWrapper ref) */ private void printActivatableRef(ActivatableWrapper ref) { - if(ref == null || ref.remoteObject == null) + if (ref == null || ref.remoteObject == null) + { return; + } Logger.print(" "); Logger.printPlainMixedBlue("Activator:", ref.getActivatorEndpoint()); @@ -302,6 +393,8 @@ private void printActivatableRef(ActivatableWrapper ref) UnicastWrapper unicastRef = ref.getActivated(); if (unicastRef != null) + { printUnicastRef(unicastRef); + } } } diff --git a/src/de/qtc/rmg/io/Logger.java b/src/eu/tneitzel/rmg/io/Logger.java similarity index 99% rename from src/de/qtc/rmg/io/Logger.java rename to src/eu/tneitzel/rmg/io/Logger.java index 958e8755..7469b50b 100644 --- a/src/de/qtc/rmg/io/Logger.java +++ b/src/eu/tneitzel/rmg/io/Logger.java @@ -1,6 +1,6 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; -import de.qtc.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.RMGOption; /** * The Logger class exposes static methods that can be used to create colored output. diff --git a/src/de/qtc/rmg/io/MaliciousOutputStream.java b/src/eu/tneitzel/rmg/io/MaliciousOutputStream.java similarity index 93% rename from src/de/qtc/rmg/io/MaliciousOutputStream.java rename to src/eu/tneitzel/rmg/io/MaliciousOutputStream.java index f6791840..b7179966 100644 --- a/src/de/qtc/rmg/io/MaliciousOutputStream.java +++ b/src/eu/tneitzel/rmg/io/MaliciousOutputStream.java @@ -1,13 +1,13 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.lang.reflect.Field; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.utils.DefinitelyNonExistingClass; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.utils.DefinitelyNonExistingClass; +import eu.tneitzel.rmg.utils.RMGUtils; import sun.rmi.server.MarshalOutputStream; /** @@ -51,7 +51,7 @@ public MaliciousOutputStream(OutputStream out) throws IOException super(out); if( !MarshalOutputStream.class.isAssignableFrom(out.getClass()) ) { - Logger.eprintlnMixedYellow("Internal error:", "de.qtc.rmg.io.MaliciousOutputStream", "requires MaliciousOutputStream."); + Logger.eprintlnMixedYellow("Internal error:", "eu.tneitzel.rmg.io.MaliciousOutputStream", "requires MaliciousOutputStream."); RMGUtils.exit(); } diff --git a/src/de/qtc/rmg/io/RawObjectInputStream.java b/src/eu/tneitzel/rmg/io/RawObjectInputStream.java similarity index 95% rename from src/de/qtc/rmg/io/RawObjectInputStream.java rename to src/eu/tneitzel/rmg/io/RawObjectInputStream.java index 86ca3fe0..1089a6cf 100644 --- a/src/de/qtc/rmg/io/RawObjectInputStream.java +++ b/src/eu/tneitzel/rmg/io/RawObjectInputStream.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.DataInput; import java.io.IOException; @@ -6,7 +6,7 @@ import java.io.ObjectInputStream; import java.lang.reflect.Field; -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; /** * Wrapper class for an ObjectInputStream. Allows to perform raw byte operations on the underlying diff --git a/src/de/qtc/rmg/io/RawObjectOutputStream.java b/src/eu/tneitzel/rmg/io/RawObjectOutputStream.java similarity index 95% rename from src/de/qtc/rmg/io/RawObjectOutputStream.java rename to src/eu/tneitzel/rmg/io/RawObjectOutputStream.java index 4ad82d6c..987f6412 100644 --- a/src/de/qtc/rmg/io/RawObjectOutputStream.java +++ b/src/eu/tneitzel/rmg/io/RawObjectOutputStream.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.DataOutput; import java.io.IOException; @@ -6,7 +6,7 @@ import java.io.OutputStream; import java.lang.reflect.Field; -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; /** * Wrapper class for an ObjectOutputStream. Allows to perform raw byte operations on the underlying diff --git a/src/de/qtc/rmg/io/SampleWriter.java b/src/eu/tneitzel/rmg/io/SampleWriter.java similarity index 98% rename from src/de/qtc/rmg/io/SampleWriter.java rename to src/eu/tneitzel/rmg/io/SampleWriter.java index 12dcde49..e932d558 100644 --- a/src/de/qtc/rmg/io/SampleWriter.java +++ b/src/eu/tneitzel/rmg/io/SampleWriter.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.File; import java.io.IOException; @@ -11,11 +11,11 @@ import org.apache.commons.io.IOUtils; -import de.qtc.rmg.exceptions.UnexpectedCharacterException; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.Security; +import eu.tneitzel.rmg.exceptions.UnexpectedCharacterException; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.Security; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtMethod; diff --git a/src/de/qtc/rmg/io/SingleOpOutputStream.java b/src/eu/tneitzel/rmg/io/SingleOpOutputStream.java similarity index 96% rename from src/de/qtc/rmg/io/SingleOpOutputStream.java rename to src/eu/tneitzel/rmg/io/SingleOpOutputStream.java index adb8d951..3e9ea6cf 100644 --- a/src/de/qtc/rmg/io/SingleOpOutputStream.java +++ b/src/eu/tneitzel/rmg/io/SingleOpOutputStream.java @@ -1,8 +1,8 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.ByteArrayOutputStream; -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; /** * The SingleOpOutputStream class is used during SSRF operations. When the SSRF option is used, diff --git a/src/de/qtc/rmg/io/WordlistHandler.java b/src/eu/tneitzel/rmg/io/WordlistHandler.java similarity index 99% rename from src/de/qtc/rmg/io/WordlistHandler.java rename to src/eu/tneitzel/rmg/io/WordlistHandler.java index 3298fbb1..89715322 100644 --- a/src/de/qtc/rmg/io/WordlistHandler.java +++ b/src/eu/tneitzel/rmg/io/WordlistHandler.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.io; +package eu.tneitzel.rmg.io; import java.io.File; import java.io.IOException; @@ -13,7 +13,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import de.qtc.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.internal.MethodCandidate; import javassist.CannotCompileException; import javassist.NotFoundException; diff --git a/src/de/qtc/rmg/networking/DGCClientSocket.java b/src/eu/tneitzel/rmg/networking/DGCClientSocket.java similarity index 97% rename from src/de/qtc/rmg/networking/DGCClientSocket.java rename to src/eu/tneitzel/rmg/networking/DGCClientSocket.java index b8608605..2bf2845b 100644 --- a/src/de/qtc/rmg/networking/DGCClientSocket.java +++ b/src/eu/tneitzel/rmg/networking/DGCClientSocket.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -9,7 +9,7 @@ import java.net.Socket; import java.rmi.server.UID; -import de.qtc.rmg.io.DevNullOutputStream; +import eu.tneitzel.rmg.io.DevNullOutputStream; import sun.rmi.server.MarshalOutputStream; import sun.rmi.transport.TransportConstants; diff --git a/src/de/qtc/rmg/networking/DGCClientSocketFactory.java b/src/eu/tneitzel/rmg/networking/DGCClientSocketFactory.java similarity index 96% rename from src/de/qtc/rmg/networking/DGCClientSocketFactory.java rename to src/eu/tneitzel/rmg/networking/DGCClientSocketFactory.java index 71f68ab5..2940efce 100644 --- a/src/de/qtc/rmg/networking/DGCClientSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/DGCClientSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.ServerSocket; diff --git a/src/de/qtc/rmg/networking/DGCClientSslSocketFactory.java b/src/eu/tneitzel/rmg/networking/DGCClientSslSocketFactory.java similarity index 97% rename from src/de/qtc/rmg/networking/DGCClientSslSocketFactory.java rename to src/eu/tneitzel/rmg/networking/DGCClientSslSocketFactory.java index 621345d1..638ae51f 100644 --- a/src/de/qtc/rmg/networking/DGCClientSslSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/DGCClientSslSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.InetAddress; diff --git a/src/de/qtc/rmg/networking/DummySocketFactory.java b/src/eu/tneitzel/rmg/networking/DummySocketFactory.java similarity index 95% rename from src/de/qtc/rmg/networking/DummySocketFactory.java rename to src/eu/tneitzel/rmg/networking/DummySocketFactory.java index b04aa241..db632b09 100644 --- a/src/de/qtc/rmg/networking/DummySocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/DummySocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.io.Serializable; @@ -6,7 +6,7 @@ import java.net.Socket; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; /** * During the creation of the An Trinh registry whitelist bypass gadget, the creation of a diff --git a/src/de/qtc/rmg/networking/DummyTrustManager.java b/src/eu/tneitzel/rmg/networking/DummyTrustManager.java similarity index 95% rename from src/de/qtc/rmg/networking/DummyTrustManager.java rename to src/eu/tneitzel/rmg/networking/DummyTrustManager.java index f7ce6f8c..78deac35 100644 --- a/src/de/qtc/rmg/networking/DummyTrustManager.java +++ b/src/eu/tneitzel/rmg/networking/DummyTrustManager.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; diff --git a/src/de/qtc/rmg/networking/LimitedSocketFactory.java b/src/eu/tneitzel/rmg/networking/LimitedSocketFactory.java similarity index 93% rename from src/de/qtc/rmg/networking/LimitedSocketFactory.java rename to src/eu/tneitzel/rmg/networking/LimitedSocketFactory.java index c44fc6a3..3b717ad8 100644 --- a/src/de/qtc/rmg/networking/LimitedSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/LimitedSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.InetAddress; @@ -9,8 +9,8 @@ import java.net.UnknownHostException; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.RMGUtils; /** * The LimitedSocketFactoryClass is used when creating a rogue JMX server. It is required diff --git a/src/de/qtc/rmg/networking/LoopbackSocketFactory.java b/src/eu/tneitzel/rmg/networking/LoopbackSocketFactory.java similarity index 64% rename from src/de/qtc/rmg/networking/LoopbackSocketFactory.java rename to src/eu/tneitzel/rmg/networking/LoopbackSocketFactory.java index 7c6c8f19..34adfbce 100644 --- a/src/de/qtc/rmg/networking/LoopbackSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/LoopbackSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.net.Socket; import java.net.UnknownHostException; @@ -6,9 +6,9 @@ import java.net.ServerSocket; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; /** * Remote objects bound to an RMI registry are usually pointing to remote endpoints @@ -19,7 +19,7 @@ * objects are open, it is still possible to communicate with them. * * The LoopbackSocketFactory class extends the default RMISocketFactory and can be set - * as a replacement. Within its constructor, it requires to specify a host that is the + * as a replacement. The class uses remote-method-guessers global option access to obtain * actual target of the RMI communication (usually the registry host). All other RMI * connections are then expected to target the same host. This is implemented by overwriting * the createSocket function. If the specified host value does not match the expected value, @@ -32,30 +32,15 @@ * * @author Tobias Neitzel (@qtc_de) */ -public class LoopbackSocketFactory extends RMISocketFactory { - - private String host; - private RMISocketFactory fac; - private boolean printInfo = true; - private boolean followRedirect = false; - - /** - * Creates a new LoopbackSocketFactory. - * - * @param host remote host that is expected to get all further RMI connections - * @param fac original socket factory to create sockets from - * @param followRedirect if true, connections are not redirected to the expected host - */ - public LoopbackSocketFactory(String host, RMISocketFactory fac, boolean followRedirect) - { - this.host = host; - this.fac = fac; - this.followRedirect= followRedirect; - } +public class LoopbackSocketFactory extends RMISocketFactory +{ + private transient RMISocketFactory fax; + private transient boolean printInfo = true; + @Override public ServerSocket createServerSocket(int port) throws IOException { - return fac.createServerSocket(port); + return getFax().createServerSocket(port); } /** @@ -67,41 +52,64 @@ public Socket createSocket(String host, int port) throws IOException { Socket sock = null; - if(!this.host.equals(host)) { - - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { + if (!RMGOption.TARGET_HOST.getValue().equals(host)) + { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.printInfoBox(); Logger.printlnMixedBlue("RMI object tries to connect to different remote host:", host); } - if( this.followRedirect ) { - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) + if (RMGOption.CONN_FOLLOW.getBool()) + { + if ( printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.println("Following redirect to new target..."); + } + } - } else { - - host = this.host; + else + { + host = RMGOption.TARGET_HOST.getValue(); - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.printlnMixedBlue("Redirecting the connection back to", host); Logger.printlnMixedYellow("You can use", "--follow", "to prevent this."); } } - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.decreaseIndent(); } this.printInfo = false; } - try { - sock = fac.createSocket(host, port); + try + { + sock = getFax().createSocket(host, port); + } - } catch( UnknownHostException e ) { + catch( UnknownHostException e ) + { ExceptionHandler.unknownHost(e, host, true); } return sock; } + + /** + * Obtain the RMISocketFactory to create sockets from. This is always the default RMISocketFactory. + */ + private RMISocketFactory getFax() + { + if (fax == null) + { + fax = RMISocketFactory.getDefaultSocketFactory(); + } + + return fax; + } } diff --git a/src/de/qtc/rmg/networking/LoopbackSslSocketFactory.java b/src/eu/tneitzel/rmg/networking/LoopbackSslSocketFactory.java similarity index 57% rename from src/de/qtc/rmg/networking/LoopbackSslSocketFactory.java rename to src/eu/tneitzel/rmg/networking/LoopbackSslSocketFactory.java index 94fb397c..1b64a653 100644 --- a/src/de/qtc/rmg/networking/LoopbackSslSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/LoopbackSslSocketFactory.java @@ -1,15 +1,16 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.rmi.server.RMIClientSocketFactory; import javax.net.ssl.SSLSocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; /** * Remote objects bound to an RMI registry are usually pointing to remote endpoints @@ -20,7 +21,7 @@ * objects are open, it is still possible to communicate with them. * * The LoopbackSslSocketFactory class extends the default SSLSocketFactory and can be set - * as a replacement. The class uses static variables to define configuration parameters and + * as a replacement. The class uses remote-method-guessers global option access to obtain * the actual target of the RMI communication (usually the registry host). All other RMI * connections are then expected to target the same host. This is implemented by overwriting * the createSocket function. If the specified host value does not match the expected value, @@ -28,17 +29,15 @@ * * During a redirect, the class prints a warning to the user to inform about the * redirection. If redirection is a desired behavior, the user can use the --follow option - * with rmg, which sets the followRedirect attribute to true. In these cases, a warning - * is still printed, but the connection goes to the specified target. + * with remote-method-guesser, which sets the followRedirect attribute to true. In these + * cases, a warning is still printed, but the connection goes to the specified target. * * @author Tobias Neitzel (@qtc_de) */ -public class LoopbackSslSocketFactory extends SSLSocketFactory { - - public static String host = ""; - public static SSLSocketFactory fac = null; - public static boolean printInfo = true; - public static boolean followRedirect = false; +public class LoopbackSslSocketFactory extends SSLSocketFactory implements RMIClientSocketFactory +{ + public transient SSLSocketFactory fax; + public transient boolean printInfo = true; /** * Overwrites the default implementation of createSocket. Checks whether host matches the expected @@ -50,39 +49,49 @@ public Socket createSocket(String target, int port) throws IOException { Socket sock = null; - if(!host.equals(target)) { + if(!RMGOption.TARGET_HOST.getValue().equals(target)) { - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.printInfoBox(); Logger.printlnMixedBlue("RMI object tries to connect to different remote host:", target); } - if( followRedirect ) { - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) + if (RMGOption.CONN_FOLLOW.getBool()) + { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.println("Following SSL redirect to new target..."); + } + } - } else { - - target = host; + else + { + target = RMGOption.TARGET_HOST.getValue(); - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { - Logger.printlnMixedBlue("Redirecting the SSL connection back to", host); + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { + Logger.printlnMixedBlue("Redirecting the SSL connection back to", target); Logger.printlnMixedYellow("You can use", "--follow", "to prevent this."); } } - if( printInfo && RMGOption.GLOBAL_VERBOSE.getBool() ) { + if (printInfo && RMGOption.GLOBAL_VERBOSE.getBool()) + { Logger.decreaseIndent(); } printInfo = false; } - try { - sock = fac.createSocket(host, port); + try + { + sock = getFax().createSocket(target, port); + } - } catch( UnknownHostException e ) { - ExceptionHandler.unknownHost(e, host, true); + catch (UnknownHostException e) + { + ExceptionHandler.unknownHost(e, target, true); } return sock; @@ -91,36 +100,50 @@ public Socket createSocket(String target, int port) throws IOException @Override public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3) throws IOException { - return fac.createSocket(arg0, arg1, arg2, arg3); + return getFax().createSocket(arg0, arg1, arg2, arg3); } @Override public String[] getDefaultCipherSuites() { - return fac.getDefaultCipherSuites(); + return getFax().getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { - return fac.getSupportedCipherSuites(); + return getFax().getSupportedCipherSuites(); } @Override public Socket createSocket(InetAddress arg0, int arg1) throws IOException { - return fac.createSocket(arg0, arg1); + return getFax().createSocket(arg0, arg1); } @Override public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3) throws IOException, UnknownHostException { - return fac.createSocket(arg0, arg1, arg2, arg3); + return getFax().createSocket(arg0, arg1, arg2, arg3); } @Override public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2, int arg3) throws IOException { - return fac.createSocket(arg0, arg1, arg2, arg3); + return getFax().createSocket(arg0, arg1, arg2, arg3); + } + + /** + * Obtain the SSLSocketFactory to create sockets from. This is always the underlying SSLSocketFactory + * from the TrustAllSocketFactory. + */ + private SSLSocketFactory getFax() + { + if (fax == null) + { + fax = new TrustAllSocketFactory().getSSLSocketFactory(); + } + + return fax; } } diff --git a/src/de/qtc/rmg/networking/RMIEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIEndpoint.java similarity index 97% rename from src/de/qtc/rmg/networking/RMIEndpoint.java rename to src/eu/tneitzel/rmg/networking/RMIEndpoint.java index 3ce8a19e..045af615 100644 --- a/src/de/qtc/rmg/networking/RMIEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIEndpoint.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.io.ObjectInput; @@ -9,14 +9,14 @@ import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RemoteRef; -import de.qtc.rmg.exceptions.SSRFException; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.internal.Pair; -import de.qtc.rmg.io.MaliciousOutputStream; -import de.qtc.rmg.io.RawObjectInputStream; -import de.qtc.rmg.plugin.PluginSystem; +import eu.tneitzel.rmg.exceptions.SSRFException; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.internal.Pair; +import eu.tneitzel.rmg.io.MaliciousOutputStream; +import eu.tneitzel.rmg.io.RawObjectInputStream; +import eu.tneitzel.rmg.plugin.PluginSystem; import javassist.CtClass; import javassist.CtPrimitiveType; import sun.rmi.server.UnicastRef; diff --git a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java similarity index 77% rename from src/de/qtc/rmg/networking/RMIRegistryEndpoint.java rename to src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java index 96dbd59d..240874f1 100644 --- a/src/de/qtc/rmg/networking/RMIRegistryEndpoint.java +++ b/src/eu/tneitzel/rmg/networking/RMIRegistryEndpoint.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.io.InvalidClassException; @@ -11,14 +11,14 @@ import java.util.HashMap; import java.util.Map; -import de.qtc.rmg.exceptions.SSRFException; -import de.qtc.rmg.internal.CodebaseCollector; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.plugin.PluginSystem; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.RemoteObjectWrapper; +import eu.tneitzel.rmg.exceptions.SSRFException; +import eu.tneitzel.rmg.internal.CodebaseCollector; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.plugin.PluginSystem; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.RemoteObjectWrapper; import javassist.tools.reflect.Reflection; /** @@ -33,7 +33,8 @@ public class RMIRegistryEndpoint extends RMIEndpoint private Registry rmiRegistry; private Map remoteObjectCache; - private static boolean stopLookupLoop = false; + private static int lookupCount = 0; + private static final int maxLookupCount = 5; /** * The main purpose of this constructor function is to setup the different socket factories. @@ -55,10 +56,13 @@ public RMIRegistryEndpoint(String host, int port) this.remoteObjectCache = new HashMap(); - try { + try + { RMISocketFactory.setSocketFactory(PluginSystem.getDefaultSocketFactory(host, port)); - - } catch (IOException e) { + } + + catch (IOException e) + { Logger.eprintlnMixedBlue("Unable to set custom", "RMISocketFactory.", "Host redirection will probably not work."); ExceptionHandler.showStackTrace(e); Logger.eprintln(""); @@ -66,10 +70,13 @@ public RMIRegistryEndpoint(String host, int port) java.security.Security.setProperty("ssl.SocketFactory.provider", PluginSystem.getDefaultSSLSocketFactory(host, port)); - try { + try + { this.rmiRegistry = LocateRegistry.getRegistry(host, port, csf); - - } catch( RemoteException e ) { + } + + catch (RemoteException e) + { ExceptionHandler.internalError("RMIRegistryEndpoint.locateRegistry", "Caught unexpected RemoteException."); ExceptionHandler.stackTrace(e); RMGUtils.exit(); @@ -95,34 +102,52 @@ public RMIRegistryEndpoint(RMIEndpoint rmi) */ public String[] getBoundNames() throws java.rmi.NoSuchObjectException { - if( RMGOption.TARGET_BOUND_NAME.notNull() ) + if (RMGOption.TARGET_BOUND_NAME.notNull()) + { return new String[] { RMGOption.TARGET_BOUND_NAME.getValue() }; + } String[] boundNames = null; - try { + try + { boundNames = rmiRegistry.list(); - - } catch( java.rmi.ConnectIOException e ) { + } + + catch (java.rmi.ConnectIOException e) + { ExceptionHandler.connectIOException(e, "list"); - - } catch( java.rmi.ConnectException e ) { + } + + catch (java.rmi.ConnectException e) + { ExceptionHandler.connectException(e, "list"); - - } catch( java.rmi.UnknownHostException e ) { + } + + catch (java.rmi.UnknownHostException e) + { ExceptionHandler.unknownHost(e, host, true); - - } catch( java.rmi.NoSuchObjectException e ) { + } + + catch (java.rmi.NoSuchObjectException e) + { throw e; - - } catch( Exception e ) { - + } + + + catch (Exception e) + { Throwable cause = ExceptionHandler.getCause(e); - if( cause instanceof SSRFException ) + if (cause instanceof SSRFException) + { SSRFSocket.printContent(host, port); + } + else + { ExceptionHandler.unexpectedException(e, "list", "call", true); + } } return boundNames; @@ -141,8 +166,10 @@ public RemoteObjectWrapper[] lookup(String[] boundNames) throws IllegalArgumentE { RemoteObjectWrapper[] remoteObjects = new RemoteObjectWrapper[boundNames.length]; - for(int ctr = 0; ctr < boundNames.length; ctr++) + for (int ctr = 0; ctr < boundNames.length; ctr++) + { remoteObjects[ctr] = this.lookup(boundNames[ctr]); + } return remoteObjects; } @@ -159,44 +186,60 @@ public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentExcept { Remote remoteObject = remoteObjectCache.get(boundName); - if( remoteObject == null ) { - - try { + if (remoteObject == null) + { + try + { remoteObject = rmiRegistry.lookup(boundName); remoteObjectCache.put(boundName, remoteObject); + lookupCount = 0; + } - } catch( java.rmi.ConnectIOException e ) { + catch (java.rmi.ConnectIOException e) + { ExceptionHandler.connectIOException(e, "lookup"); + } - } catch( java.rmi.ConnectException e ) { + catch (java.rmi.ConnectException e) + { ExceptionHandler.connectException(e, "lookup"); + } - } catch( java.rmi.UnknownHostException e ) { + catch (java.rmi.UnknownHostException e) + { ExceptionHandler.unknownHost(e, host, true); + } - } catch( java.rmi.NoSuchObjectException e ) { + catch (java.rmi.NoSuchObjectException e) + { ExceptionHandler.noSuchObjectException(e, "registry", true); + } - } catch( java.rmi.NotBoundException e ) { + catch (java.rmi.NotBoundException e) + { ExceptionHandler.notBoundException(e, boundName); + } - } catch( Exception e ) { - + catch( Exception e ) + { Throwable cause = ExceptionHandler.getCause(e); if (e instanceof UnmarshalException && cause instanceof InvalidClassException) { InvalidClassException invalidClassException = (InvalidClassException)cause; - if (stopLookupLoop || ! cause.getMessage().contains("serialVersionUID")) + if (lookupCount > maxLookupCount || !cause.getMessage().contains("serialVersionUID")) + { ExceptionHandler.invalidClassException(invalidClassException); + } - try { + try + { String className = RMGUtils.getClass(invalidClassException); long serialVersionUID = RMGUtils.getSerialVersionUID(invalidClassException); CodebaseCollector.addSerialVersionUID(className, serialVersionUID); - stopLookupLoop = true; + lookupCount += 1; } catch (Exception e1) @@ -208,16 +251,24 @@ public RemoteObjectWrapper lookup(String boundName) throws IllegalArgumentExcept } else if (e instanceof UnmarshalException && e.getMessage().contains("Transport return code invalid")) + { throw (UnmarshalException)e; + } if( cause instanceof ClassNotFoundException ) + { ExceptionHandler.lookupClassNotFoundException(e, cause.getMessage()); + } else if( cause instanceof SSRFException ) + { SSRFSocket.printContent(host, port); + } else + { ExceptionHandler.unexpectedException(e, "lookup", "call", true); + } } } diff --git a/src/de/qtc/rmg/networking/SSRFResponseSocket.java b/src/eu/tneitzel/rmg/networking/SSRFResponseSocket.java similarity index 97% rename from src/de/qtc/rmg/networking/SSRFResponseSocket.java rename to src/eu/tneitzel/rmg/networking/SSRFResponseSocket.java index 882d8829..49f69500 100644 --- a/src/de/qtc/rmg/networking/SSRFResponseSocket.java +++ b/src/eu/tneitzel/rmg/networking/SSRFResponseSocket.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -8,7 +8,7 @@ import java.io.OutputStream; import java.net.Socket; -import de.qtc.rmg.io.DevNullOutputStream; +import eu.tneitzel.rmg.io.DevNullOutputStream; import sun.rmi.transport.TransportConstants; /** diff --git a/src/de/qtc/rmg/networking/SSRFResponseSocketFactory.java b/src/eu/tneitzel/rmg/networking/SSRFResponseSocketFactory.java similarity index 96% rename from src/de/qtc/rmg/networking/SSRFResponseSocketFactory.java rename to src/eu/tneitzel/rmg/networking/SSRFResponseSocketFactory.java index 8c2d33fa..1ad11e6f 100644 --- a/src/de/qtc/rmg/networking/SSRFResponseSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/SSRFResponseSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.ServerSocket; diff --git a/src/de/qtc/rmg/networking/SSRFSocket.java b/src/eu/tneitzel/rmg/networking/SSRFSocket.java similarity index 94% rename from src/de/qtc/rmg/networking/SSRFSocket.java rename to src/eu/tneitzel/rmg/networking/SSRFSocket.java index 4fa49ad3..68ebcc22 100644 --- a/src/de/qtc/rmg/networking/SSRFSocket.java +++ b/src/eu/tneitzel/rmg/networking/SSRFSocket.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -12,12 +12,12 @@ import java.nio.charset.StandardCharsets; import java.rmi.server.UID; -import de.qtc.rmg.exceptions.SSRFException; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.SingleOpOutputStream; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.exceptions.SSRFException; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.SingleOpOutputStream; +import eu.tneitzel.rmg.utils.RMGUtils; import sun.rmi.server.MarshalOutputStream; import sun.rmi.transport.TransportConstants; diff --git a/src/de/qtc/rmg/networking/SSRFSocketFactory.java b/src/eu/tneitzel/rmg/networking/SSRFSocketFactory.java similarity index 95% rename from src/de/qtc/rmg/networking/SSRFSocketFactory.java rename to src/eu/tneitzel/rmg/networking/SSRFSocketFactory.java index 121f161d..d3f3552b 100644 --- a/src/de/qtc/rmg/networking/SSRFSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/SSRFSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.ServerSocket; diff --git a/src/de/qtc/rmg/networking/TimeoutSocketFactory.java b/src/eu/tneitzel/rmg/networking/TimeoutSocketFactory.java similarity index 98% rename from src/de/qtc/rmg/networking/TimeoutSocketFactory.java rename to src/eu/tneitzel/rmg/networking/TimeoutSocketFactory.java index b11577fb..1229ba86 100644 --- a/src/de/qtc/rmg/networking/TimeoutSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/TimeoutSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.InetSocketAddress; diff --git a/src/de/qtc/rmg/networking/TrustAllSocketFactory.java b/src/eu/tneitzel/rmg/networking/TrustAllSocketFactory.java similarity index 95% rename from src/de/qtc/rmg/networking/TrustAllSocketFactory.java rename to src/eu/tneitzel/rmg/networking/TrustAllSocketFactory.java index 98a87082..88f498ee 100644 --- a/src/de/qtc/rmg/networking/TrustAllSocketFactory.java +++ b/src/eu/tneitzel/rmg/networking/TrustAllSocketFactory.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.networking; +package eu.tneitzel.rmg.networking; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,9 +12,9 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.RMGUtils; /** * Wrapper around an SSLSocketFactory that trusts all certificates. This is used for TLS protected diff --git a/src/de/qtc/rmg/operations/ActivationClient.java b/src/eu/tneitzel/rmg/operations/ActivationClient.java similarity index 97% rename from src/de/qtc/rmg/operations/ActivationClient.java rename to src/eu/tneitzel/rmg/operations/ActivationClient.java index 8e013fc1..38f5f192 100644 --- a/src/de/qtc/rmg/operations/ActivationClient.java +++ b/src/eu/tneitzel/rmg/operations/ActivationClient.java @@ -1,14 +1,14 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.rmi.server.ObjID; import java.rmi.server.RemoteRef; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.MaliciousOutputStream; -import de.qtc.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.MaliciousOutputStream; +import eu.tneitzel.rmg.networking.RMIEndpoint; import javassist.ClassPool; import javassist.CtClass; diff --git a/src/de/qtc/rmg/operations/DGCClient.java b/src/eu/tneitzel/rmg/operations/DGCClient.java similarity index 96% rename from src/de/qtc/rmg/operations/DGCClient.java rename to src/eu/tneitzel/rmg/operations/DGCClient.java index bdeebf4e..11901c83 100644 --- a/src/de/qtc/rmg/operations/DGCClient.java +++ b/src/eu/tneitzel/rmg/operations/DGCClient.java @@ -1,15 +1,15 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.rmi.server.ObjID; import java.util.HashMap; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.MaliciousOutputStream; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.utils.DefinitelyNonExistingClass; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.MaliciousOutputStream; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.utils.DefinitelyNonExistingClass; /** * The distributed garbage collector (DGC) is a well known RMI object with publicly known method definitions. @@ -94,7 +94,7 @@ public void enumSecurityManager(String callName) Logger.statusDefault(); ExceptionHandler.showStackTrace(e); - } else if( c.getMessage().equals("de.qtc.rmg.utils.DefinitelyNonExistingClass")) { + } else if( c.getMessage().equals("eu.tneitzel.rmg.utils.DefinitelyNonExistingClass")) { Logger.printlnMixedYellow("- RMI server", "did not", "attempt to parse the supplied codebase."); Logger.printlnMixedBlue(" --> The server", "does use", "a Security Manager."); Logger.statusDefault(); diff --git a/src/de/qtc/rmg/operations/Dispatcher.java b/src/eu/tneitzel/rmg/operations/Dispatcher.java similarity index 81% rename from src/de/qtc/rmg/operations/Dispatcher.java rename to src/eu/tneitzel/rmg/operations/Dispatcher.java index 4ed4ca45..e136b6bd 100644 --- a/src/de/qtc/rmg/operations/Dispatcher.java +++ b/src/eu/tneitzel/rmg/operations/Dispatcher.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.io.IOException; import java.rmi.NoSuchObjectException; @@ -10,25 +10,25 @@ import javax.management.remote.rmi.RMIServer; -import de.qtc.rmg.endpoints.KnownEndpoint; -import de.qtc.rmg.endpoints.KnownEndpointHolder; -import de.qtc.rmg.exceptions.UnexpectedCharacterException; -import de.qtc.rmg.internal.ArgumentHandler; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Formatter; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.SampleWriter; -import de.qtc.rmg.io.WordlistHandler; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.networking.RMIRegistryEndpoint; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.RemoteObjectWrapper; -import de.qtc.rmg.utils.RogueJMX; -import de.qtc.rmg.utils.UnicastWrapper; -import de.qtc.rmg.utils.YsoIntegration; +import eu.tneitzel.rmg.endpoints.KnownEndpoint; +import eu.tneitzel.rmg.endpoints.KnownEndpointHolder; +import eu.tneitzel.rmg.exceptions.UnexpectedCharacterException; +import eu.tneitzel.rmg.internal.ArgumentHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Formatter; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.SampleWriter; +import eu.tneitzel.rmg.io.WordlistHandler; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.RemoteObjectWrapper; +import eu.tneitzel.rmg.utils.RogueJMX; +import eu.tneitzel.rmg.utils.UnicastWrapper; +import eu.tneitzel.rmg.utils.YsoIntegration; import javassist.CannotCompileException; import javassist.NotFoundException; @@ -37,15 +37,15 @@ * to the ArgumentParser object and extracts all required arguments parameters for the corresponding method calls. * * Methods within the Dispatcher class can be annotated with the Parameters annotation to specify additional requirements - * on their expected arguments. Refer to the de.qtc.rmg.annotations.Parameters class for more details. + * on their expected arguments. Refer to the eu.tneitzel.rmg.annotations.Parameters class for more details. * - * To add a new operation to rmg, the operation must first be registered within the de.qtc.rmg.operations.Operation class. + * To add a new operation to rmg, the operation must first be registered within the eu.tneitzel.rmg.operations.Operation class. * A new Operation needs to be created there that references the corresponding method within this class. * * @author Tobias Neitzel (@qtc_de) */ -public class Dispatcher { - +public class Dispatcher +{ private ArgumentHandler p; private String[] boundNames = null; @@ -71,7 +71,9 @@ public Dispatcher(ArgumentHandler p) private void obtainBoundNames() throws NoSuchObjectException { if(boundNames != null) + { return; + } boundNames = getRegistry().getBoundNames(); } @@ -92,19 +94,26 @@ private void obtainBoundObjects() throws NoSuchObjectException { int retryCount = 0; - if( boundNames == null ) + if (boundNames == null) + { obtainBoundNames(); + } while (retryCount < 5) { - try { + try + { remoteObjects = getRegistry().lookup(boundNames); return; + } - } catch (java.rmi.UnmarshalException e) { + catch (java.rmi.UnmarshalException e) + { retryCount += 1; + } - } catch( Exception e ) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "lookup", "operation", true); } } @@ -115,15 +124,20 @@ private void obtainBoundObjects() throws NoSuchObjectException */ private void createMethodCandidate() { - if( !RMGOption.TARGET_SIGNATURE.notNull() ) + if (!RMGOption.TARGET_SIGNATURE.notNull()) + { return; + } String signature = RMGOption.TARGET_SIGNATURE.getValue(); - try { + try + { candidate = new MethodCandidate(signature); + } - } catch (CannotCompileException | NotFoundException e) { + catch (CannotCompileException | NotFoundException e) + { ExceptionHandler.invalidSignature(signature); } } @@ -162,8 +176,10 @@ private RMIRegistryEndpoint getRegistry() */ private RMIRegistryEndpoint getRegistry(RMIEndpoint rmi) { - if(rmiReg == null) + if (rmiReg == null) + { rmiReg = new RMIRegistryEndpoint(rmi); + } return rmiReg; } @@ -178,7 +194,8 @@ private RemoteObjectClient getRemoteObjectClient(RMIEndpoint rmi) { RMGOption.requireOneOf(RMGOption.TARGET_OBJID, RMGOption.TARGET_BOUND_NAME, RMGOption.TARGET_COMPONENT); - if(RMGOption.TARGET_BOUND_NAME.isNull() && RMGOption.TARGET_OBJID.isNull()) { + if (RMGOption.TARGET_BOUND_NAME.isNull() && RMGOption.TARGET_OBJID.isNull()) + { RMIComponent component = p.getComponent(); RMGOption.TARGET_OBJID.setValue( RMGUtils.getObjIDByComponent(component).toString() ); } @@ -196,14 +213,19 @@ private RemoteObjectClient getRemoteObjectClient(RMIEndpoint rmi) */ private RemoteObjectClient getRemoteObjectClient(String objIDString, String boundName, RMIEndpoint rmi) { - if( objIDString != null ) { + if (objIDString != null) + { ObjID objID = RMGUtils.parseObjID(objIDString); return new RemoteObjectClient(rmi, objID); + } - } else if( boundName != null ) { + else if (boundName != null) + { return new RemoteObjectClient(getRegistry(rmi), boundName); + } - } else { + else + { ExceptionHandler.missingTarget(p.getAction().name()); return null; } @@ -218,8 +240,10 @@ private RemoteObjectClient getRemoteObjectClient(String objIDString, String boun */ private void writeSamples(List results) { - if( results.size() == 0 ) + if (results.size() == 0) + { return; + } String templateFolder = RMGOption.GUESS_TEMPLATE_FOLDER.getValue(); String sampleFolder = RMGOption.GUESS_SAMPLE_FOLDER.getValue(); @@ -231,31 +255,38 @@ private void writeSamples(List results) Logger.lineBreak(); Logger.increaseIndent(); - try { + try + { SampleWriter writer = new SampleWriter(templateFolder, sampleFolder, sslValue, followRedirect); - for(RemoteObjectClient client: results) { - + for (RemoteObjectClient client: results) + { RemoteObjectWrapper remoteObject = client.remoteObject; - for(String boundName : client.getBoundNames()) { - + for (String boundName : client.getBoundNames()) + { Logger.printlnMixedYellow("Creating samples for bound name", boundName + "."); Logger.increaseIndent(); - if(!remoteObject.isKnown()) - writer.createInterface(boundName, remoteObject.className, client.remoteMethods); + if (!remoteObject.isKnown()) + { + writer.createInterface(boundName, remoteObject.getInterfaceName(), client.remoteMethods); + } - writer.createSamples(boundName, remoteObject.className, !remoteObject.isKnown(), client.remoteMethods, getRMIEndpoint()); + writer.createSamples(boundName, remoteObject.getInterfaceName(), !remoteObject.isKnown(), client.remoteMethods, getRMIEndpoint()); Logger.decreaseIndent(); } } + } - } catch (IOException | CannotCompileException | NotFoundException e) { + catch (IOException | CannotCompileException | NotFoundException e) + { ExceptionHandler.unexpectedException(e, "sample", "creation", true); + } - } catch (UnexpectedCharacterException e) { + catch (UnexpectedCharacterException e) + { Logger.eprintlnMixedYellow("Caught", "UnexpectedCharacterException", "during sample creation."); Logger.eprintln("This is caused by special characters within bound- or classes names."); Logger.eprintlnMixedYellow("You can enforce sample creation with the", "--trusted", "switch."); @@ -279,15 +310,21 @@ private Set getCandidates() boolean zeroArg = RMGOption.GUESS_ZERO_ARG.getBool(); boolean updateWordlist = RMGOption.GUESS_UPDATE.getBool(); - if( candidate != null ) { + if (candidate != null) + { candidates.add(candidate); + } - } else { - - try { + else + { + try + { WordlistHandler wlHandler = new WordlistHandler(wordlistFile, wordlistFolder, updateWordlist, zeroArg); candidates = wlHandler.getWordlistMethods(); - } catch( IOException e ) { + } + + catch (IOException e) + { Logger.eprintlnMixedYellow("Caught", "IOException", "while reading wordlist file(s)."); ExceptionHandler.stackTrace(e); RMGUtils.exit(); @@ -319,20 +356,23 @@ public void dispatchSerial() RMIEndpoint rmi = getRMIEndpoint(); RMIComponent component = p.getComponent(); - if( component == null ) { - - if( candidate == null ) + if (component == null) + { + if (candidate == null) + { ExceptionHandler.missingSignature(); + } int argumentPosition = RMGOption.ARGUMENT_POS.getValue(); RemoteObjectClient client = getRemoteObjectClient(rmi); client.gadgetCall(candidate, p.getGadget(), argumentPosition); + } - } else { - - switch( component ) { - + else + { + switch (component) + { case ACTIVATOR: ActivationClient act = new ActivationClient(rmi); act.gadgetCall(p.getGadget()); @@ -368,11 +408,15 @@ public void dispatchCall() RMIEndpoint rmi = getRMIEndpoint(); Object[] argumentArray = p.getCallArguments(); - if( candidate == null ) + if (candidate == null) + { ExceptionHandler.missingSignature(); + } - if( argumentArray.length != candidate.getArgumentCount() ) + if (argumentArray.length != candidate.getArgumentCount()) + { ExceptionHandler.wrongArgumentCount(candidate.getArgumentCount(), argumentArray.length); + } RemoteObjectClient client = getRemoteObjectClient(rmi); client.genericCall(candidate, argumentArray); @@ -396,18 +440,23 @@ public void dispatchCodebase() RMIComponent component = p.getComponent(); int argumentPosition = RMGOption.ARGUMENT_POS.getValue(); - try { + try + { payload = RMGUtils.makeSerializableClass(className, RMGOption.PAYLOAD_SERIAL_VERSION_UID.getValue()); payload = ((Class)payload).newInstance(); + } - } catch (CannotCompileException | InstantiationException | IllegalAccessException e) { + catch (CannotCompileException | InstantiationException | IllegalAccessException e) + { ExceptionHandler.unexpectedException(e, "payload", "creation", true); } - if( component == null ) { - - if( candidate == null) + if (component == null) + { + if (candidate == null) + { ExceptionHandler.missingSignature(); + } RemoteObjectClient client = getRemoteObjectClient(rmi); client.codebaseCall(candidate, payload, argumentPosition); @@ -416,18 +465,22 @@ public void dispatchCodebase() DGCClient dgc = new DGCClient(rmi); dgc.codebaseCall(p.getDgcMethod(), payload); + } - } else if( component == RMIComponent.REGISTRY ) { - + else if (component == RMIComponent.REGISTRY) + { RegistryClient reg = new RegistryClient(rmi); reg.codebaseCall(payload, p.getRegMethod(), RMGOption.BIND_BYPASS.getBool()); + } - } else if( component == RMIComponent.ACTIVATOR ) { - + else if (component == RMIComponent.ACTIVATOR) + { ActivationClient act = new ActivationClient(rmi); act.codebaseCall(payload); + } - } else { + else + { ExceptionHandler.internalError("dispatchCodebase", "No target was selected."); } } @@ -488,44 +541,54 @@ public void dispatchEnum() boolean enumJEP290Bypass = true; boolean marshal = true; - try { - - if( actions.contains(ScanAction.LIST) ) { - + try + { + if (actions.contains(ScanAction.LIST)) + { obtainBoundNames(); - if( !RMGOption.SSRFRESPONSE.notNull() || RMGOption.TARGET_BOUND_NAME.notNull() ) { + if (!RMGOption.SSRFRESPONSE.notNull() || RMGOption.TARGET_BOUND_NAME.notNull()) + { obtainBoundObjects(); format.listBoundNames(remoteObjects); Logger.lineBreak(); format.listCodebases(); + } - } else { + else + { remoteObjects = RemoteObjectWrapper.fromBoundNames(boundNames); format.listBoundNames(remoteObjects); } - if( RMGOption.SSRFRESPONSE.notNull() ) + if (RMGOption.SSRFRESPONSE.notNull()) + { return; + } } - if( actions.contains(ScanAction.STRING_MARSHALLING) ) { + if (actions.contains(ScanAction.STRING_MARSHALLING)) + { Logger.lineBreak(); marshal = registryClient.enumerateStringMarshalling(); } - if( actions.contains(ScanAction.CODEBASE) ) { + if (actions.contains(ScanAction.CODEBASE)) + { Logger.lineBreak(); registryClient.enumCodebase(marshal, p.getRegMethod(), RMGOption.ENUM_BYPASS.getBool()); } - if( actions.contains(ScanAction.LOCALHOST_BYPASS) ) { + if (actions.contains(ScanAction.LOCALHOST_BYPASS)) + { Logger.lineBreak(); registryClient.enumLocalhostBypass(); } + } - } catch( java.rmi.NoSuchObjectException e ) { + catch (java.rmi.NoSuchObjectException e) + { ExceptionHandler.noSuchObjectExceptionRegistryEnum(); enumJEP290Bypass = false; @@ -533,22 +596,26 @@ public void dispatchEnum() format.listCodebases(); } - if( actions.contains(ScanAction.SECURITY_MANAGER) ) { + if (actions.contains(ScanAction.SECURITY_MANAGER)) + { Logger.lineBreak(); dgc.enumSecurityManager(p.getDgcMethod()); } - if( actions.contains(ScanAction.JEP290) ) { + if (actions.contains(ScanAction.JEP290)) + { Logger.lineBreak(); dgc.enumJEP290(p.getDgcMethod()); } - if(enumJEP290Bypass && actions.contains(ScanAction.FILTER_BYPASS) ) { + if (enumJEP290Bypass && actions.contains(ScanAction.FILTER_BYPASS)) + { Logger.lineBreak(); registryClient.enumJEP290Bypass(p.getRegMethod(), RMGOption.ENUM_BYPASS.getBool(), marshal); } - if( actions.contains(ScanAction.ACTIVATOR) ) { + if (actions.contains(ScanAction.ACTIVATOR)) + { Logger.lineBreak(); ActivationClient activationClient = new ActivationClient(rmi); activationClient.enumActivator(); @@ -564,10 +631,13 @@ public void dispatchGuess() { Formatter format = new Formatter(); - try { + try + { obtainBoundObjects(); + } - } catch( NoSuchObjectException e ) { + catch (NoSuchObjectException e) + { ExceptionHandler.noSuchObjectException(e, "registry", true); } @@ -580,8 +650,10 @@ public void dispatchGuess() Logger.decreaseIndent(); format.listGuessedMethods(results); - if(results.size() > 0 && RMGOption.GUESS_CREATE_SAMPLES.getBool()) + if (results.size() > 0 && RMGOption.GUESS_CREATE_SAMPLES.getBool()) + { this.writeSamples(results); + } } /** @@ -598,10 +670,15 @@ public void dispatchKnown() KnownEndpointHolder keh = KnownEndpointHolder.getHolder(); KnownEndpoint endpoint = keh.lookup(className); - if( endpoint == null ) + if (endpoint == null) + { Logger.eprintlnMixedYellow("The specified class name", className, "isn't a known class."); + } + else + { formatter.listKnownEndpoint(endpoint); + } } /** @@ -651,8 +728,8 @@ public void dispatchRogueJMX() RogueJMX rogueJMX = new RogueJMX(listenerHost, listenerPort, RMGOption.ROGUEJMX_OBJID.getValue()); - if( RMGOption.ROGUEJMX_FORWARD_HOST.notNull() ) { - + if (RMGOption.ROGUEJMX_FORWARD_HOST.notNull()) + { String forwardHost = RMGOption.ROGUEJMX_FORWARD_HOST.getValue(); int forwardPort = RMGOption.require(RMGOption.ROGUEJMX_FORWARD_PORT); @@ -666,11 +743,14 @@ public void dispatchRogueJMX() rogueJMX.forwardTo(client); } - try { + try + { rogueJMX.export(); Logger.lineBreak(); + } - } catch( java.rmi.RemoteException e ) { + catch (java.rmi.RemoteException e) + { ExceptionHandler.unexpectedException(e, "exporting", "rogue JMX server", true); } } diff --git a/src/eu/tneitzel/rmg/operations/MethodGuesser.java b/src/eu/tneitzel/rmg/operations/MethodGuesser.java new file mode 100644 index 00000000..da4737f4 --- /dev/null +++ b/src/eu/tneitzel/rmg/operations/MethodGuesser.java @@ -0,0 +1,673 @@ +package eu.tneitzel.rmg.operations; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.springframework.remoting.support.RemoteInvocation; + +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.ProgressBar; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.RemoteInvocationHolder; +import eu.tneitzel.rmg.utils.SpringRemotingWrapper; +import eu.tneitzel.rmg.utils.UnicastWrapper; +import javassist.CannotCompileException; +import javassist.NotFoundException; + +/** + * The MethodGuesser class is used to brute force available remote methods on Java RMI endpoints. It uses + * low level Java RMI functions to invoke methods parsed from a wordlist with incorrect argument types. The + * server-side exception can be used as an indicator whether the invoked method exists on the server. + * + * When a RMI client calls a remote method, it establishes a TCP connection to the remote endpoint + * and sends (among others) the following information: + * + * - The ObjID of the RemoteObject that should receive the call + * - A method hash, that identifies the remote method to be called + * - A collection of method arguments to be used for the call + * + * During method guessing, remote-method-guesser uses a wordlist of Java methods and computes their hash + * values. The corresponding hashes are then sent to the server, together with invalid method arguments. + * If the remote method does not exist, the server throws an exception complaining about an unknown method + * hash. On the other hand, if the remote method exists, the server will complain about the invalid method + * arguments. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class MethodGuesser +{ + private int padding = 0; + private final ProgressBar progressBar; + + private Set candidates; + private List clientList; + private List knownClientList; + private List> candidateSets; + private Set invocationHolders; + private List> invocationHolderSets; + + /** + * To create a MethodGuesser you need to pass the references for remote objects you want to guess on. + * These are usually obtained from the RMI registry and can be passed as an array of UnicastWrapper. + * Furthermore, you need to specify a Set of MethodCandidates that represents the methods you want + * to guess. + * + * If one of the UnicastWrapper objects within the array is a SpringRemotingWrapper, the set of + * MethodCandidates gets cloned and transformed into a set of RemoteInvocation. Both sets are still + * available and the guessing procedure decides based on the wrapper type which set should be used. + * + * @param remoteObjects Array of looked up remote objects from the RMI registry + * @param candidates MethodCandidates that should be guessed + */ + public MethodGuesser(UnicastWrapper[] remoteObjects, Set candidates) + { + this.candidates = candidates; + + this.knownClientList = new ArrayList(); + this.candidateSets = RMGUtils.splitSet(candidates, RMGOption.THREADS.getValue()); + + if (SpringRemotingWrapper.containsSpringRemotingClient(remoteObjects)) + { + invocationHolders = SpringRemotingWrapper.getInvocationHolders(candidates); + invocationHolderSets = RMGUtils.splitSet(invocationHolders, RMGOption.THREADS.getValue()); + } + + if (!RMGOption.GUESS_FORCE_GUESSING.getBool()) + { + remoteObjects = handleKnownMethods(remoteObjects); + } + + this.clientList = initClientList(remoteObjects); + int workCount = 0; + + for (RemoteObjectClient client : clientList) + { + if (client.remoteObject instanceof SpringRemotingWrapper) + { + workCount += invocationHolders.size(); + } + + else + { + workCount += candidates.size(); + } + } + + this.progressBar = new ProgressBar(workCount, 37); + } + + /** + * This function is basically used to prevent guessing on duplicate classes. Some RMI endpoints bind + * multiple instances of the same remote class to the registry. Guessing each of them is usually not + * what you want, as they use the same implementation. This function checks the bound class names for + * duplicates and handles them according to the RMGOption.GUESS_DUPLICATES value. + * + * If guessing duplicates was requested, the function creates a new RemoteObjectClient for each bound name. + * If guessing duplicates was not requested, only the first boundName is used to create a RemoteObjectClient + * and all other bound names that are based on the same class are registered as duplicates. + * + * @param remoteObjects Array of looked up remote objects from the RMI registry + */ + private List initClientList(UnicastWrapper[] remoteObjects) + { + List remoteObjectClients = new ArrayList(); + setPadding(remoteObjects); + + if (!RMGOption.GUESS_DUPLICATES.getBool()) + { + remoteObjects = UnicastWrapper.handleDuplicates(remoteObjects); + } + + for (UnicastWrapper o : remoteObjects) + { + RemoteObjectClient client = new RemoteObjectClient(o); + remoteObjectClients.add(client); + } + + if (UnicastWrapper.hasDuplicates(remoteObjects)) + { + printDuplicates(remoteObjects); + } + + return remoteObjectClients; + } + + /** + * This function is just used for displaying the result. It is called when iterating over the boundNames + * and saves the length of the longest boundName. This is used as a padding value for the other boundNames. + * + * @param remoteObjects List containing all UnicastWrapper used during the guess operation + */ + private void setPadding(UnicastWrapper[] remoteObjects) + { + for (UnicastWrapper o : remoteObjects) + { + if (padding < o.boundName.length()) + { + padding = o.boundName.length(); + } + } + } + + /** + * This function prints a short info text that multiple bound names on the RMI server implement + * the same class / interface and that only one of them is used during method guessing. The + * output is disabled by default and only enabled if --verbose was used. + * + * @param remoteObjects List containing all UnicastWrapper used during the guess operation + */ + private void printDuplicates(UnicastWrapper[] remoteObjects) + { + Logger.disableIfNotVerbose(); + Logger.printInfoBox(); + + Logger.println("The following bound names use the same remote class:"); + Logger.lineBreak(); + Logger.increaseIndent(); + + for (UnicastWrapper remoteObject : remoteObjects) + { + String[] duplicates = remoteObject.getDuplicateBoundNames(); + + if (duplicates.length == 0) + { + continue; + } + + Logger.printlnMixedBlue("-", remoteObject.boundName); + Logger.increaseIndent(); + + for (String dup : duplicates) + { + Logger.printlnMixedYellow("-->", dup); + } + + Logger.decreaseIndent(); + } + + Logger.decreaseIndent(); + Logger.lineBreak(); + Logger.printlnMixedBlue("Method guessing", "is skipped", "for duplicate remote classes."); + Logger.printlnMixedYellow("You can use", "--guess-duplicates", "to guess them anyway."); + Logger.decreaseIndent(); + Logger.enable(); + } + + /** + * When known remote objects are encountered and --force-guessing was not used, the corresponding remote methods + * are added automatically to the list of guessed methods. This function performs this task and also prints according + * information for the user. + * + * @param remoteObjects Array of looked up remote objects from the RMI registry + * @return Array of unknown remote objects + */ + private UnicastWrapper[] handleKnownMethods(UnicastWrapper[] remoteObjects) + { + ArrayList unknown = new ArrayList(); + + for (UnicastWrapper o : remoteObjects) + { + if (!o.isKnown()) + { + unknown.add(o); + } + + else + { + List knownMethods = new ArrayList(); + RemoteObjectClient knownClient = new RemoteObjectClient(o); + + for (String method : o.knownEndpoint.getRemoteMethods()) + { + try + { + knownMethods.add(new MethodCandidate(method)); + } + + catch (CannotCompileException | NotFoundException e) + { + Logger.printlnMixedYellowFirst("Internal Error", "- Unable to compile known method with signature: " + method); + } + } + + knownClient.addRemoteMethods(knownMethods); + knownClientList.add(knownClient); + } + } + + if (knownClientList.size() != 0) + { + Logger.disableIfNotVerbose(); + Logger.printInfoBox(); + + Logger.println("The following bound names use a known remote object class:"); + Logger.lineBreak(); + Logger.increaseIndent(); + + for (RemoteObjectClient o : knownClientList) + { + Logger.printlnMixedBlue("-", o.getBoundName() + " (" + o.getBoundName() + ")"); + } + + Logger.decreaseIndent(); + Logger.lineBreak(); + Logger.printlnMixedBlue("Method guessing", "is skipped", "and known methods are listed instead."); + Logger.printlnMixedYellow("You can use", "--force-guessing", "to guess methods anyway."); + Logger.decreaseIndent(); + Logger.lineBreak(); + Logger.enable(); + } + + return unknown.toArray(new UnicastWrapper[0]); + } + + /** + * Helper function that prints some visual text when the guesser is started. Just contains information + * on the number of methods that are guessed or the concrete method signature (if specified). + */ + public void printGuessingIntro() + { + int count = candidates.size(); + + if (count == 0) + { + Logger.eprintlnMixedYellow("List of candidate methods contains", "0", "elements."); + Logger.eprintln("Please use a valid and non empty wordlist file."); + RMGUtils.exit(); + } + + else if (clientList.size() == 0) + { + return; + } + + Logger.lineBreak(); + Logger.printlnMixedYellow("Starting Method Guessing on", String.valueOf(count), "method signature(s)."); + + if (count == 1) + { + Logger.printlnMixedBlue("Method signature:", ((MethodCandidate)candidates.toArray()[0]).getSignature() + "."); + } + } + + /** + * This method starts the actual guessing process. It creates a GuessingWorker for each remoteClient in the clientList + * and for each Set of MethodCandidates in the candidateSets. If the underlying RemoteObjectWrapper type of a client + * is a SpringRemotingWrapper, the spring remoting compatible SpringGuessingWorker will be used. + * + * @return List of RemoteObjectClient containing the successfully guessed methods. Only clients containing + * guessed methods are returned. Clients without guessed methods are filtered. + */ + public List guessMethods() + { + Logger.lineBreak(); + + if (clientList.size() == 0) + { + clientList.addAll(knownClientList); + return clientList; + } + + Logger.increaseIndent(); + Logger.printlnYellow("MethodGuesser is running:"); + Logger.increaseIndent(); + Logger.printlnBlue("--------------------------------"); + + ExecutorService pool = Executors.newFixedThreadPool(RMGOption.THREADS.getValue()); + + for (RemoteObjectClient client : clientList) + { + if (client.remoteObject instanceof SpringRemotingWrapper) + { + for (Set invoHolder : invocationHolderSets) + { + Runnable r = new SpringGuessingWorker(client, invoHolder); + pool.execute(r); + } + } + + else + { + for (Set candidates : candidateSets) + { + Runnable r = new GuessingWorker(client, candidates); + pool.execute(r); + } + } + } + + try + { + pool.shutdown(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } + + catch (InterruptedException e) + { + Logger.eprintln("Interrupted!"); + } + + Logger.decreaseIndent(); + Logger.lineBreak(); + Logger.printlnYellow("done."); + Logger.lineBreak(); + + clientList = RemoteObjectClient.filterEmpty(clientList); + clientList.addAll(knownClientList); + + return clientList; + } + + /** + * The GuessingWorker class performs the actual method guessing in terms of RMI calls. It implements Runnable and + * is intended to be run within a thread pool. Each GuessingWorker gets assigned a Set of MethodCandidates and iterates + * over the corresponding set. It uses the obtained RemoteObjectClient object to dispatch a call to the candidates and + * inspects the server-side exception to determine whether the method exists on the remote object. + * + * @author Tobias Neitzel (@qtc_de) + */ + private class GuessingWorker implements Runnable + { + protected String boundName; + protected Set candidates; + protected RemoteObjectClient client; + + /** + * Initialize the guessing worker with all the required information. + * + * @param client RemoteObjectClient to the targeted remote object + * @param candidates MethodCandidates to guess + */ + public GuessingWorker(RemoteObjectClient client, Set candidates) + { + this.client = client; + this.boundName = client.getBoundName(); + this.candidates = candidates; + } + + /** + * This function is called when a guessed MethodCandidate exists. It creates a corresponding log entry and + * saves the candidate within the results map. + * + * @param candidate MethodCandidate that was successfully guessed + */ + protected void logHit(MethodCandidate candidate) + { + String prefix = Logger.blue("[ " + Logger.padRight(boundName, padding) + " ] "); + Logger.printlnMixedYellow(prefix + "HIT! Method with signature", candidate.getSignature(), "exists!"); + client.addRemoteMethod(candidate); + } + + /** + * Invokes the assigned MethodCandidates. Methods are invoked by using a specially crafting argument array. + * The array is crafted in a way that method calls will never be fully dispatched on the server side while + * simultaneously preventing corruption of the underlying TCP stream. This allows to reuse the TCP connection + * during method guessing which makes the process much faster, especially on TLS protected connections. + */ + public void run() + { + for (MethodCandidate candidate : candidates) + { + try + { + client.guessingCall(candidate); + logHit(candidate); // If there was no exception, the method exists (zero arg / valid call) + } + + catch (java.rmi.ServerException e) + { + Throwable cause = ExceptionHandler.getCause(e); + + /* + * In case of an existing method, the specially crafted argument array that is used during guessing calls + * will always lead to one of the following exceptions. These are caught and indicate an existing method. + * One could also attempt to catch the 'unrecognized method hash' exception from the server to match non + * existing methods, but this requires an additional string compare that might be slower. + */ + if (cause instanceof java.io.OptionalDataException || cause instanceof java.io.StreamCorruptedException) + { + logHit(candidate); + } + } + + catch (java.rmi.UnmarshalException e) + { + /* + * When running with multiple threads, from time to time, stream corruption can be observed. + * This seems to be non deterministically and only appears in certain setups. In my current + * setup, it seems always to break on the "String selectSurname(String email)" method. In + * future, this should be debugged. However, as in a run of 3000 methods this only occurs one + * or two times, it is probably not that important. + */ + if (RMGOption.GLOBAL_VERBOSE.getBool()) + { + String info = "Caught unexpected " + e.getClass().getName() + " while guessing the " + candidate.getSignature() + "method.\n" + +"[-]" + Logger.getIndent() + "This occurs sometimes when guessing with multiple threads.\n" + +"[-]" + Logger.getIndent() + "You can retry with --threads 1 or just ignore the exception."; + Logger.eprintlnBlue(info); + ExceptionHandler.showStackTrace(e); + } + } + + catch(Exception e) + { + /* + * If we end up here, an unexpected exception was raised that indicates a general error. + */ + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + + String info = "Caught unexpected " + e.getClass().getName() + " during method guessing.\n" + +"[-]" + Logger.getIndent() + "Please report this to improve rmg :)\n" + +"[-]" + Logger.getIndent() + "Stack-Trace:\n" + +writer.toString(); + + Logger.eprintlnBlue(info); + } + + finally + { + progressBar.taskDone(); + } + } + } + } + + /** + * The SpringGuessingWorker does basically the same as the GuessingWorker, but for spring remoting :) + * + * @author Tobias Neitzel (@qtc_de) + */ + private class SpringGuessingWorker implements Runnable + { + protected String boundName; + protected RemoteObjectClient client; + protected Set invocationHolders; + + /** + * Initialize the spring guessing worker with all the required information. + * + * @param client RemoteObjectClient to the targeted remote object + * @param invocationHolders set of RemoteInvocationHolders that contain the RemoteInvocations to guess + */ + public SpringGuessingWorker(RemoteObjectClient client, Set invocationHolders) + { + this.client = client; + this.boundName = client.getBoundName(); + this.invocationHolders = invocationHolders; + } + + /** + * This function is called when a guessed RemoteInvocation exists. It creates a corresponding log entry and + * saves the candidate within the results map. + * + * @param invoHolder RemoteInvocationHolder that contains the RemoteInvocation that lead to an successful method call + */ + protected void logHit(RemoteInvocationHolder invoHolder) + { + MethodCandidate existingMethod = invoHolder.getCandidate(); + + String prefix = Logger.blue("[ " + Logger.padRight(boundName, padding) + " ] "); + Logger.printlnMixedYellow(prefix + "HIT! Method with signature", SpringRemotingWrapper.getSignature(existingMethod), "exists!"); + client.addRemoteMethod(existingMethod); + } + + /** + * Sends the assigned RemoteInvocations to the spring remoting endpoint and inspects the response. + * RemoteInvocations send by the guesser are always malformed. They contain a method name, a list of + * argument types and a list of argument values. The argument values are purposely chosen to not match + * the argument types. This prevents actual method calls that could perform dangerous stuff. However, + * based on the thrown exeception it is still possible to identify existing methods. + */ + public void run() + { + for (RemoteInvocationHolder invocationHolder : invocationHolders) + { + try + { + client.unmanagedCall(SpringRemotingWrapper.getInvokeMethod(), new MethodArguments(invocationHolder.getInvo(), RemoteInvocation.class)); + + /* + * We always provide an invalid argument count for our SpringRemoting calls. + * We should never endup here, which would indicate a successful call. + */ + unexpectedError(invocationHolder, null); + } + + catch (java.lang.IllegalArgumentException e) + { + /* + * Since SpringRemoting calls exposed methods via reflection, we can always provide an incorrect + * argument count during the call. If the method exists, this leads to an IllegalArgumentException, + * which is used to identify valid methods. + */ + logHit(invocationHolder); + } + + catch (java.lang.NoSuchMethodException e) + { + /* + * SpringRemoting resolves remote methods via reflection. If the requested method does not + * exist, it throws a java.lang.NoSuchMethodException. So this branch means that the guessed + * method simply does not exist and we can continue. + */ + } + + catch (java.rmi.ServerException e) + { + Throwable cause = ExceptionHandler.getCause(e); + + if (cause instanceof java.lang.ClassNotFoundException) + { + /* + * Since SpringRemoting requires valid RMI calls, guessed methods may contain classes that + * are not known on the server side. In this case, ClassNotFoundExceptions are expected. This + * means that the method does not exist and we can continue. + */ + } + + else if (cause instanceof java.rmi.UnmarshalException) + { + /* + * When running with multiple threads, from time to time, stream corruption can be observed. + * This seems to be non deterministically and only appears in certain setups. In my current + * setup, it seems always to break on the "String selectSurname(String email)" method. In + * future, this should be debugged. However, as in a run of 3000 methods this only occurs one + * or two times, it is probably not that important. + */ + if (RMGOption.GLOBAL_VERBOSE.getBool()) + { + String info = "Caught unexpected " + e.getClass().getName() + " while guessing the " + SpringRemotingWrapper.getSignature(invocationHolder.getCandidate()) + " method.\n" + +"[-]" + Logger.getIndent() + "This occurs sometimes when guessing with multiple threads.\n" + +"[-]" + Logger.getIndent() + "You can retry with --threads 1 or just ignore the exception."; + Logger.eprintlnBlue(info); + ExceptionHandler.showStackTrace(e); + } + } + + else + { + /* + * If we end up here, an unexpected exception was raised that indicates a general error. + */ + unexpectedError(invocationHolder, e); + } + } + + catch (java.rmi.UnmarshalException e) + { + /* + * When running with multiple threads, from time to time, stream corruption can be observed. + * This seems to be non deterministically and only appears in certain setups. In my current + * setup, it seems always to break on the "String selectSurname(String email)" method. In + * future, this should be debugged. However, as in a run of 3000 methods this only occurs one + * or two times, it is probably not that important. + */ + if (RMGOption.GLOBAL_VERBOSE.getBool()) + { + String info = "Caught unexpected " + e.getClass().getName() + " while guessing the " + SpringRemotingWrapper.getSignature(invocationHolder.getCandidate()) + " method.\n" + +"[-]" + Logger.getIndent() + "This occurs sometimes when guessing with multiple threads.\n" + +"[-]" + Logger.getIndent() + "You can retry with --threads 1 or just ignore the exception."; + Logger.eprintlnBlue(info); + ExceptionHandler.showStackTrace(e); + } + } + + catch(Exception e) + { + /* + * If we end up here, an unexpected exception was raised that indicates a general error. + */ + unexpectedError(invocationHolder, e); + } + + finally + { + progressBar.taskDone(); + } + } + } + + /** + * If an unexpected exception was thrown, this method is called. It prints a warning message to the user, + * but does not interrupt the guessing procedure. + * + * @param invoHolder the RemoteInvocationHolder that caused the exception + * @param e the thrown Exception + */ + private void unexpectedError(RemoteInvocationHolder invoHolder, Exception e) + { + String info = ""; + StringWriter writer = new StringWriter(); + + Logger.printlnYellow(invoHolder.getCandidate().getSignature()); + + if (e != null) + { + e.printStackTrace(new PrintWriter(writer)); + + info = "Caught unexpected " + e.getClass().getName() + " during method guessing.\n" + +"[-]" + Logger.getIndent() + "Please report this to improve rmg :)\n" + +"[-]" + Logger.getIndent() + "Stack-Trace:\n" + +writer.toString(); + } + + else + { + info = "Spring Remoting call did not cause an exception. This is not expected."; + } + + Logger.eprintlnBlue(info); + } + } +} diff --git a/src/de/qtc/rmg/operations/Operation.java b/src/eu/tneitzel/rmg/operations/Operation.java similarity index 91% rename from src/de/qtc/rmg/operations/Operation.java rename to src/eu/tneitzel/rmg/operations/Operation.java index 9a1f5104..c5418bd1 100644 --- a/src/de/qtc/rmg/operations/Operation.java +++ b/src/eu/tneitzel/rmg/operations/Operation.java @@ -1,9 +1,9 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.lang.reflect.Method; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparsers; @@ -44,6 +44,9 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), CALL("dispatchCall", "", "Regularly calls a method with the specified arguments", new RMGOption[] { @@ -69,6 +72,10 @@ public enum Operation { RMGOption.CALL_ARGUMENTS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, + RMGOption.GENERIC_PRINT, }), CODEBASE("dispatchCodebase", " ", "Perform remote class loading attacks", new RMGOption[] { @@ -96,6 +103,9 @@ public enum Operation { RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, RMGOption.PAYLOAD_SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), ENUM("dispatchEnum", "[scan-action ...]", "Enumerate common vulnerabilities on Java RMI endpoints", new RMGOption[] { @@ -121,6 +131,9 @@ public enum Operation { RMGOption.ACTIVATION, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), GUESS("dispatchGuess", "", "Guess methods on bound names", new RMGOption[] { @@ -150,6 +163,9 @@ public enum Operation { RMGOption.NO_PROGRESS, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), KNOWN("dispatchKnown", "", "Display details of known remote objects", new RMGOption[] { @@ -201,6 +217,9 @@ public enum Operation { RMGOption.BIND_GADGET_NAME, RMGOption.BIND_GADGET_CMD, RMGOption.YSO, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), ROGUEJMX("dispatchRogueJMX", "[forward-host]", "Creates a rogue JMX listener (collect credentials)", new RMGOption[] { @@ -258,6 +277,9 @@ public enum Operation { RMGOption.YSO, RMGOption.FORCE_ACTIVATION, RMGOption.SERIAL_VERSION_UID, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }), UNBIND("dispatchUnbind", "", "Removes the specified bound name from the registry", new RMGOption[] { @@ -276,6 +298,9 @@ public enum Operation { RMGOption.SSRF_STREAM_PROTOCOL, RMGOption.BIND_BOUND_NAME, RMGOption.BIND_BYPASS, + RMGOption.SOCKET_FACTORY, + RMGOption.SOCKET_FACTORY_SSL, + RMGOption.SOCKET_FACTORY_PLAIN, }); private Method method; diff --git a/src/de/qtc/rmg/operations/PortScanner.java b/src/eu/tneitzel/rmg/operations/PortScanner.java similarity index 96% rename from src/de/qtc/rmg/operations/PortScanner.java rename to src/eu/tneitzel/rmg/operations/PortScanner.java index 41ef90b6..66031b54 100644 --- a/src/de/qtc/rmg/operations/PortScanner.java +++ b/src/eu/tneitzel/rmg/operations/PortScanner.java @@ -1,18 +1,18 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.rmi.server.ObjID; import java.rmi.server.RMIClientSocketFactory; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.networking.TimeoutSocketFactory; -import de.qtc.rmg.networking.TrustAllSocketFactory; -import de.qtc.rmg.utils.ProgressBar; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.networking.TimeoutSocketFactory; +import eu.tneitzel.rmg.networking.TrustAllSocketFactory; +import eu.tneitzel.rmg.utils.ProgressBar; /** diff --git a/src/de/qtc/rmg/operations/RegistryClient.java b/src/eu/tneitzel/rmg/operations/RegistryClient.java similarity index 98% rename from src/de/qtc/rmg/operations/RegistryClient.java rename to src/eu/tneitzel/rmg/operations/RegistryClient.java index f64f6e54..27c68706 100644 --- a/src/de/qtc/rmg/operations/RegistryClient.java +++ b/src/eu/tneitzel/rmg/operations/RegistryClient.java @@ -1,18 +1,18 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.rmi.server.ObjID; import javax.management.remote.rmi.RMIServerImpl_Stub; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.MaliciousOutputStream; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.YsoIntegration; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.MaliciousOutputStream; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.YsoIntegration; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; diff --git a/src/de/qtc/rmg/operations/RemoteObjectClient.java b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java similarity index 81% rename from src/de/qtc/rmg/operations/RemoteObjectClient.java rename to src/eu/tneitzel/rmg/operations/RemoteObjectClient.java index 1ceb2692..b749379a 100644 --- a/src/de/qtc/rmg/operations/RemoteObjectClient.java +++ b/src/eu/tneitzel/rmg/operations/RemoteObjectClient.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.rmi.server.ObjID; import java.util.ArrayList; @@ -6,17 +6,18 @@ import java.util.Iterator; import java.util.List; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.networking.RMIRegistryEndpoint; -import de.qtc.rmg.utils.DefinitelyNonExistingClass; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.UnicastWrapper; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.networking.RMIRegistryEndpoint; +import eu.tneitzel.rmg.utils.DefinitelyNonExistingClass; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.SpringRemotingWrapper; +import eu.tneitzel.rmg.utils.UnicastWrapper; import javassist.CannotCompileException; import javassist.CtClass; import javassist.NotFoundException; @@ -31,8 +32,8 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings("restriction") -public class RemoteObjectClient { - +public class RemoteObjectClient +{ private ObjID objID; private UnicastRef remoteRef; @@ -110,11 +111,14 @@ public UnicastWrapper assignInterface(Class intf) { UnicastWrapper remoteObject = null; - try { + try + { remoteObject = UnicastWrapper.fromRef(remoteRef, intf); this.remoteObject = remoteObject; + } - } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) + { ExceptionHandler.internalError("RemoteObjectClient.assignInterface", "Caught unexpected exception: " + e.getClass().getName()); } @@ -164,7 +168,8 @@ public String[] getBoundNames() String[] boundNames = new String[boundNamesSize + 1]; boundNames[0] = this.boundName; - for(int ctr = 0; ctr < boundNamesSize; ctr++) { + for(int ctr = 0; ctr < boundNamesSize; ctr++) + { boundNames[ctr + 1] = remoteObject.duplicates.get(ctr).boundName; } @@ -190,21 +195,26 @@ public void gadgetCall(MethodCandidate targetMethod, Object gadget, int argument MethodArguments argumentArray = prepareArgumentArray(targetMethod, gadget, attackArgument); - try { + try + { rmi.genericCall(null, -1, targetMethod.getHash(), argumentArray, false, getMethodName(targetMethod), remoteRef); Logger.eprintln("Remote method invocation didn't cause any exception."); Logger.eprintln("This is unusual and the attack probably didn't work."); + } - } catch (Exception e) { - + catch (Exception e) + { Throwable cause = ExceptionHandler.getCause(e); - if( cause instanceof java.rmi.UnmarshalException && cause.getMessage().contains("unrecognized method hash")) { + if (cause instanceof java.rmi.UnmarshalException && cause.getMessage().contains("unrecognized method hash")) + { Logger.eprintlnMixedYellow("Method", targetMethod.getSignature(), "does not exist on this remote object."); ExceptionHandler.showStackTrace(e); + } - } else { + else + { ExceptionHandler.handleGadgetCallException(e, RMIComponent.CUSTOM, "method", randomClassName); } } @@ -230,21 +240,26 @@ public void codebaseCall(MethodCandidate targetMethod, Object gadget, int argume MethodArguments argumentArray = prepareArgumentArray(targetMethod, gadget, attackArgument); - try { + try + { rmi.genericCall(null, -1, targetMethod.getHash(), argumentArray, true, methodName, remoteRef); Logger.eprintln("Remote method invocation didn't cause any exception."); Logger.eprintln("This is unusual and the attack probably didn't work."); + } - } catch (Exception e) { - + catch (Exception e) + { Throwable cause = ExceptionHandler.getCause(e); - if( cause instanceof java.rmi.UnmarshalException && cause.getMessage().contains("unrecognized method hash")) { + if (cause instanceof java.rmi.UnmarshalException && cause.getMessage().contains("unrecognized method hash")) + { Logger.eprintlnMixedYellow("Method", targetMethod.getSignature(), "does not exist on this remote object."); ExceptionHandler.showStackTrace(e); + } - } else { + else + { ExceptionHandler.handleCodebaseException(e, gadget.getClass().getName(), RMIComponent.CUSTOM, "method", randomClassName); } } @@ -261,45 +276,79 @@ public void codebaseCall(MethodCandidate targetMethod, Object gadget, int argume */ public void genericCall(MethodCandidate targetMethod, Object[] argumentArray) { + if (remoteObject instanceof SpringRemotingWrapper && ((SpringRemotingWrapper)remoteObject).isRemotingCall(targetMethod)) + { + argumentArray = new Object[] { SpringRemotingWrapper.buildRemoteInvocation(targetMethod, argumentArray) }; + targetMethod = SpringRemotingWrapper.getInvokeMethod(); + } + CtClass rtype = null; MethodArguments callArguemnts = null; - try { + try + { rtype = targetMethod.getMethod().getReturnType(); callArguemnts = RMGUtils.applyParameterTypes(targetMethod.getMethod(), argumentArray); + } - } catch(Exception e) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "preparation", "of remote method call", true); } - try { + try + { rmi.genericCall(null, -1, targetMethod.getHash(), callArguemnts, false, getMethodName(targetMethod), remoteRef, rtype); + } - } catch( java.rmi.ServerException e ) { - + catch (java.rmi.ServerException e) + { Throwable cause = ExceptionHandler.getCause(e); - if( cause instanceof java.rmi.UnmarshalException && e.getMessage().contains("unrecognized method hash")) { + if (cause instanceof java.rmi.UnmarshalException && e.getMessage().contains("unrecognized method hash")) + { ExceptionHandler.unrecognizedMethodHash(e, "call", targetMethod.getSignature()); + } - } else if( cause instanceof java.io.InvalidClassException ) { + else if (cause instanceof java.io.InvalidClassException) + { ExceptionHandler.invalidClass(e, "RMI endpoint"); + } - } else if( cause instanceof java.lang.UnsupportedOperationException ) { + else if (cause instanceof java.lang.UnsupportedOperationException) + { ExceptionHandler.unsupportedOperationException(e, "method"); + } - } else { + else + { ExceptionHandler.unexpectedException(e, "generic call", "operation", false); } + } - } catch( java.rmi.NoSuchObjectException e ) { + catch (java.rmi.NoSuchObjectException e) + { ExceptionHandler.noSuchObjectException(e, this.objID, true); + } - } catch( Exception e ) { + catch (Exception e) + { ExceptionHandler.genericCall(e); } } + /** + * Technically the same as the genericCall method, but does not perform any exception handling. + * + * @param targetMethod remote method to call + * @param argumentArray method arguments to use for the call + * @throws All possible encountered exceptions are passed to the caller + */ + public void unmanagedCall(MethodCandidate targetMethod, MethodArguments args) throws Exception + { + rmi.genericCall(null, -1, targetMethod.getHash(), args, false, targetMethod.getName(), remoteRef, null); + } + /** * Just a wrapper around the guessingCall function of the RMIEndpoint class. * @@ -322,10 +371,13 @@ public void guessingCall(MethodCandidate targetMethod) throws Exception public static List filterEmpty(List clientList) { Iterator it = clientList.iterator(); - while(it.hasNext()) { - if( it.next().remoteMethods.isEmpty() ) + while (it.hasNext()) + { + if (it.next().remoteMethods.isEmpty()) + { it.remove(); + } } return clientList; @@ -354,8 +406,10 @@ private void printGadgetIntro(MethodCandidate targetMethod, int attackArgument) */ private UnicastRef getRemoteRefByObjID() { - if(objID == null) + if (objID == null) + { ExceptionHandler.internalError("getRemoteRefByObjID", "Function was called with missing objID."); + } return rmi.getRemoteRef(objID); } @@ -369,15 +423,20 @@ private UnicastRef getRemoteRefByObjID() */ private UnicastRef getRemoteRefByName() { - if(boundName == null || !(rmi instanceof RMIRegistryEndpoint)) + if (boundName == null || !(rmi instanceof RMIRegistryEndpoint)) + { ExceptionHandler.internalError("getRemoteRefByName", "Function was called without the required fields."); + } RMIRegistryEndpoint rmiReg = (RMIRegistryEndpoint)rmi; - try { + try + { remoteObject = rmiReg.lookup(boundName).getUnicastWrapper(); + } - } catch(Exception e) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "remote reference lookup", "operation", true); } @@ -397,17 +456,22 @@ private int findNonPrimitiveArgument(MethodCandidate targetMethod, int position) { int attackArgument = 0; - try { + try + { attackArgument = targetMethod.getPrimitive(position); + } - } catch (CannotCompileException | NotFoundException e) { + catch (CannotCompileException | NotFoundException e) + { ExceptionHandler.unexpectedException(e, "search", "for primitive types", true); } - if( attackArgument == -1 ) { - - if( position == -1 ) + if (attackArgument == -1) + { + if (position == -1) + { Logger.eprintlnMixedYellow("No non primitive arguments were found for method signature", targetMethod.getSignature()); + } RMGUtils.exit(); } @@ -434,25 +498,34 @@ private MethodArguments prepareArgumentArray(MethodCandidate targetMethod, Objec { Object[] methodArguments = null; - try { + try + { methodArguments = RMGUtils.getArgumentArray(targetMethod.getMethod()); - } catch (Exception e) { + } + + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "argument array", "construction", true); } - if( RMGOption.NO_CANARY.getBool() ) + if (RMGOption.NO_CANARY.getBool()) + { methodArguments[attackArgument] = gadget; + } - else { - + else + { Object[] payloadArray = new Object[2]; Object randomInstance = null; - try { + try + { Class randomClass = RMGUtils.makeRandomClass(); randomInstance = randomClass.newInstance(); + } - } catch (Exception e) { + catch (Exception e) + { randomInstance = new DefinitelyNonExistingClass(); } @@ -465,10 +538,13 @@ private MethodArguments prepareArgumentArray(MethodCandidate targetMethod, Objec MethodArguments callArguments = null; - try { + try + { callArguments = RMGUtils.applyParameterTypes(targetMethod.getMethod(), methodArguments); + } - } catch(Exception e) { + catch (Exception e) + { ExceptionHandler.unexpectedException(e, "parameter types", "mapping", true); } @@ -487,21 +563,33 @@ private String getMethodName(MethodCandidate targetMethod) { String methodName = ""; - try { + try + { methodName = targetMethod.getName(); - } catch (CannotCompileException | NotFoundException e) { + } + + catch (CannotCompileException | NotFoundException e) + { ExceptionHandler.unexpectedException(e, "compilation", "process", true); } return methodName; } + /** + * Returns the string representation of an RemoteObjectClient. The format is host:port:identifier, + * where the identifier is either the bound name or the ObjID associated with the RemoteObjectClient. + * + * @return String representation of the RemoteObjectClient + */ public String toString() { String identifier = this.objID == null ? this.boundName : this.objID.toString(); - if( identifier == null ) + if (identifier == null) + { identifier = ""; + } return String.format("%s:%d:%s", rmi.host, rmi.port, identifier); } diff --git a/src/de/qtc/rmg/operations/ScanAction.java b/src/eu/tneitzel/rmg/operations/ScanAction.java similarity index 94% rename from src/de/qtc/rmg/operations/ScanAction.java rename to src/eu/tneitzel/rmg/operations/ScanAction.java index 80a2741a..414b6212 100644 --- a/src/de/qtc/rmg/operations/ScanAction.java +++ b/src/eu/tneitzel/rmg/operations/ScanAction.java @@ -1,10 +1,10 @@ -package de.qtc.rmg.operations; +package eu.tneitzel.rmg.operations; import java.util.EnumSet; import java.util.List; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.RMGUtils; /** * The ScanAction Enum represents available enumeration techniques that are applied during rmg's diff --git a/src/de/qtc/rmg/plugin/DefaultProvider.java b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java similarity index 85% rename from src/de/qtc/rmg/plugin/DefaultProvider.java rename to src/eu/tneitzel/rmg/plugin/DefaultProvider.java index ba8fd53c..3cca4214 100644 --- a/src/de/qtc/rmg/plugin/DefaultProvider.java +++ b/src/eu/tneitzel/rmg/plugin/DefaultProvider.java @@ -1,22 +1,21 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; import java.lang.reflect.Method; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMISocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.DGCClientSocketFactory; -import de.qtc.rmg.networking.LoopbackSocketFactory; -import de.qtc.rmg.networking.LoopbackSslSocketFactory; -import de.qtc.rmg.networking.SSRFResponseSocketFactory; -import de.qtc.rmg.networking.SSRFSocketFactory; -import de.qtc.rmg.networking.TrustAllSocketFactory; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.operations.RegistryClient; -import de.qtc.rmg.utils.RMGUtils; -import de.qtc.rmg.utils.YsoIntegration; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.DGCClientSocketFactory; +import eu.tneitzel.rmg.networking.LoopbackSocketFactory; +import eu.tneitzel.rmg.networking.SSRFResponseSocketFactory; +import eu.tneitzel.rmg.networking.SSRFSocketFactory; +import eu.tneitzel.rmg.networking.TrustAllSocketFactory; +import eu.tneitzel.rmg.operations.Operation; +import eu.tneitzel.rmg.operations.RegistryClient; +import eu.tneitzel.rmg.utils.RMGUtils; +import eu.tneitzel.rmg.utils.YsoIntegration; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; @@ -100,7 +99,7 @@ public Object[] getArgumentArray(String argumentString) ClassPool pool = ClassPool.getDefault(); try { - CtClass evaluator = pool.makeClass("de.qtc.rmg.plugin.DefaultProviderEval"); + CtClass evaluator = pool.makeClass("eu.tneitzel.rmg.plugin.DefaultProviderEval"); String evalFunction = "public static Object[] eval() {" + " return new Object[] { " + argumentString + "};" + "}"; @@ -172,11 +171,12 @@ public RMIClientSocketFactory getClientSocketFactory(String host, int port) @Override public RMISocketFactory getDefaultSocketFactory(String host, int port) { - if( RMGOption.SSRFRESPONSE.notNull() ) + if (RMGOption.SSRFRESPONSE.notNull()) + { return new DGCClientSocketFactory(); + } - RMISocketFactory fac = RMISocketFactory.getDefaultSocketFactory(); - return new LoopbackSocketFactory(host, fac, RMGOption.CONN_FOLLOW.getBool()); + return new LoopbackSocketFactory(); } /** @@ -193,15 +193,11 @@ public RMISocketFactory getDefaultSocketFactory(String host, int port) @Override public String getDefaultSSLSocketFactory(String host, int port) { - if( RMGOption.SSRFRESPONSE.notNull() ) - return "de.qtc.rmg.networking.DGCClientSslSocketFactory"; - - TrustAllSocketFactory trustAllFax = new TrustAllSocketFactory(); - - LoopbackSslSocketFactory.host = host; - LoopbackSslSocketFactory.fac = trustAllFax.getSSLSocketFactory(); - LoopbackSslSocketFactory.followRedirect = RMGOption.CONN_FOLLOW.getBool(); + if (RMGOption.SSRFRESPONSE.notNull()) + { + return "eu.tneitzel.rmg.networking.DGCClientSslSocketFactory"; + } - return "de.qtc.rmg.networking.LoopbackSslSocketFactory"; + return "eu.tneitzel.rmg.networking.LoopbackSslSocketFactory"; } } diff --git a/src/eu/tneitzel/rmg/plugin/GenericPrint.java b/src/eu/tneitzel/rmg/plugin/GenericPrint.java new file mode 100644 index 00000000..8b4174a4 --- /dev/null +++ b/src/eu/tneitzel/rmg/plugin/GenericPrint.java @@ -0,0 +1,237 @@ +package eu.tneitzel.rmg.plugin; + +import java.io.File; +import java.lang.reflect.Array; +import java.rmi.Remote; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.utils.ActivatableWrapper; +import eu.tneitzel.rmg.utils.RemoteObjectWrapper; +import eu.tneitzel.rmg.utils.SpringRemotingWrapper; +import eu.tneitzel.rmg.utils.UnicastWrapper; + +/** + * GenericPrint is an rmg ResponseHandler plugin that attempts to print all incoming + * server responses. It compares the incoming object to some known data types and chooses + * 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. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class GenericPrint implements IResponseHandler +{ + /** + * The handleResponse function is called with the incoming responseObject from the + * RMI server. Depending on the corresponding class, a different print action is + * chosen. + * + * @param responseObject Incoming object from an RMI server response + */ + public void handleResponse(Object responseObject) + { + Class responseClass = responseObject.getClass(); + + if(responseObject instanceof Collection) + { + handleCollection(responseObject); + } + + else if(responseObject instanceof Map) + { + handleMap(responseObject); + } + + else if(responseClass.isArray()) + { + handleArray(responseObject); + } + + else if(Remote.class.isAssignableFrom(responseClass)) + { + handleRemote((Remote)responseObject); + } + + else if(responseObject instanceof File) + { + handleFile((File)responseObject); + } + + else if(responseObject instanceof Byte) + { + handleByte((byte)responseObject); + } + + else + { + handleDefault(responseObject); + } + } + + /** + * For each item within an collection, call handleResponse on the corresponding + * item value. + * + * @param o Object of the Collection type + */ + public void handleCollection(Object o) + { + for(Object item: (Collection)o) + { + handleResponse(item); + } + } + + /** + * For each entry within a map, handleResponse is called on the entry key and value. + * Furthermore, an arrow is printed in an attempt to visualize their relationship. + * + * @param o Object of the Map type + */ + public void handleMap(Object o) + { + Map map = (Map)o; + + for(Entry item: map.entrySet()) + { + handleResponse(item.getKey()); + System.out.print(" --> "); + handleResponse(item.getValue()); + } + } + + /** + * For each item within an array, call the handleResponse function. + * + * @param o Object of the Array type + */ + public void handleArray(Object o) + { + Object[] objectArray = null; + Class type = o.getClass().getComponentType(); + + if(type.isPrimitive()) + { + int length = Array.getLength(o); + objectArray = new Object[length]; + + for(int ctr = 0; ctr < length; ctr++) + { + objectArray[ctr] = Array.get(o, ctr); + } + } + + else + { + objectArray = (Object[])o; + } + + for(Object item: objectArray) + { + handleResponse(item); + } + } + + /** + * For all objects that extend Remote, the details of the remote reference are printed. + * This includes the class name, the remote TCP endpoint, the assigned ObjID and the + * configured socket factories. + * + * @param o Object that extends the Remote type + */ + public void handleRemote(Remote o) + { + try + { + RemoteObjectWrapper objectWrapper = RemoteObjectWrapper.getInstance(o); + + if ((objectWrapper instanceof UnicastWrapper) || objectWrapper instanceof SpringRemotingWrapper) + { + UnicastWrapper wrapper = (UnicastWrapper)objectWrapper; + + String csf = "default"; + String ssf = "default"; + + if (wrapper.csf != null) + { + csf = wrapper.csf.getClass().getName(); + } + + if (wrapper.ssf != null) + { + ssf = wrapper.ssf.getClass().getName(); + } + + Logger.printlnYellow("Printing unicast RemoteObject:"); + Logger.increaseIndent(); + Logger.printlnMixedBlue("Remote Class:\t\t", wrapper.getInterfaceName()); + Logger.printlnMixedBlue("Endpoint:\t\t", wrapper.getTarget()); + Logger.printlnMixedBlue("ObjID:\t\t\t", wrapper.objID.toString()); + Logger.printlnMixedBlue("ClientSocketFactory:\t", csf); + Logger.printlnMixedBlue("ServerSocketFactory:\t", ssf); + } + + else if (objectWrapper instanceof ActivatableWrapper) + { + ActivatableWrapper wrapper = (ActivatableWrapper)objectWrapper; + + Logger.printlnYellow("Printing activatable RemoteObject:"); + Logger.increaseIndent(); + Logger.printlnMixedBlue("Remote Class:\t\t", wrapper.getInterfaceName()); + Logger.printlnMixedBlue("Activator:\t\t", wrapper.getActivatorEndpoint()); + Logger.printlnMixedBlue("ActivationID:\t\t", wrapper.activationUID.toString()); + } + + else + { + Logger.eprintlnYellow("Unsupported object type."); + } + } + + catch (Exception e) + { + ExceptionHandler.unexpectedException(e, "constructing", "RemoteObjectWrapper", true); + } + + finally + { + Logger.decreaseIndent(); + } + } + + /** + * For File objects, print their absolute path. + * + * @param o File object + */ + public void handleFile(File o) + { + Logger.println(o.getAbsolutePath()); + } + + /** + * Byte objects are converted to hex and printed. As a single byte is most likely part of a + * sequence, we print without a newline. + * + * @param o File object + */ + public void handleByte(byte o) + { + Logger.printPlain(String.format("%02x", o)); + } + + /** + * The default action for each object is to print it using it's toString method. + * + * @param o Object that did not matched one of the previously mentioned types. + */ + public void handleDefault(Object o) + { + Logger.println(o.toString()); + } +} diff --git a/src/de/qtc/rmg/plugin/IArgumentProvider.java b/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java similarity index 95% rename from src/de/qtc/rmg/plugin/IArgumentProvider.java rename to src/eu/tneitzel/rmg/plugin/IArgumentProvider.java index 3e996d09..4af76901 100644 --- a/src/de/qtc/rmg/plugin/IArgumentProvider.java +++ b/src/eu/tneitzel/rmg/plugin/IArgumentProvider.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; /** * The IArgumentProvider interface is used during rmg's 'call' action to obtain the argument array that should be diff --git a/src/de/qtc/rmg/plugin/IPayloadProvider.java b/src/eu/tneitzel/rmg/plugin/IPayloadProvider.java similarity index 91% rename from src/de/qtc/rmg/plugin/IPayloadProvider.java rename to src/eu/tneitzel/rmg/plugin/IPayloadProvider.java index f1a69d78..94ec0819 100644 --- a/src/de/qtc/rmg/plugin/IPayloadProvider.java +++ b/src/eu/tneitzel/rmg/plugin/IPayloadProvider.java @@ -1,6 +1,6 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; -import de.qtc.rmg.operations.Operation; +import eu.tneitzel.rmg.operations.Operation; /** * The IPayloadProvider interface is used during all rmg actions that send payload objects to the remote server. diff --git a/src/de/qtc/rmg/plugin/IResponseHandler.java b/src/eu/tneitzel/rmg/plugin/IResponseHandler.java similarity index 94% rename from src/de/qtc/rmg/plugin/IResponseHandler.java rename to src/eu/tneitzel/rmg/plugin/IResponseHandler.java index a652dd31..9235416f 100644 --- a/src/de/qtc/rmg/plugin/IResponseHandler.java +++ b/src/eu/tneitzel/rmg/plugin/IResponseHandler.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; /** * The IResponseHandler interface is used during rmg's 'call' action to handle the return value of an invoked method. diff --git a/src/de/qtc/rmg/plugin/ISocketFactoryProvider.java b/src/eu/tneitzel/rmg/plugin/ISocketFactoryProvider.java similarity index 98% rename from src/de/qtc/rmg/plugin/ISocketFactoryProvider.java rename to src/eu/tneitzel/rmg/plugin/ISocketFactoryProvider.java index 68cc4881..0a57ac9b 100644 --- a/src/de/qtc/rmg/plugin/ISocketFactoryProvider.java +++ b/src/eu/tneitzel/rmg/plugin/ISocketFactoryProvider.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMISocketFactory; diff --git a/src/de/qtc/rmg/plugin/PluginSystem.java b/src/eu/tneitzel/rmg/plugin/PluginSystem.java similarity index 89% rename from src/de/qtc/rmg/plugin/PluginSystem.java rename to src/eu/tneitzel/rmg/plugin/PluginSystem.java index f66e3401..a40cf9ec 100644 --- a/src/de/qtc/rmg/plugin/PluginSystem.java +++ b/src/eu/tneitzel/rmg/plugin/PluginSystem.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.plugin; +package eu.tneitzel.rmg.plugin; import java.io.File; import java.io.FileInputStream; @@ -9,11 +9,12 @@ import java.util.jar.JarInputStream; import java.util.jar.Manifest; -import de.qtc.rmg.exceptions.MalformedPluginException; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.utils.RMGUtils; +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; /** * The PluginSystem class allows rmg to be extended by user defined classes. It can be used to setup @@ -49,8 +50,15 @@ public static void init(String pluginPath) argumentProvider = provider; socketFactoryProvider = provider; - if(pluginPath != null) + if (RMGOption.GENERIC_PRINT.getBool()) + { + responseHandler = new GenericPrint(); + } + + if (pluginPath != null) + { loadPlugin(pluginPath); + } } /** @@ -73,55 +81,73 @@ private static void loadPlugin(String pluginPath) JarInputStream jarStream = null; File pluginFile = new File(pluginPath); - if(!pluginFile.exists()) { + if (!pluginFile.exists()) + { Logger.eprintlnMixedYellow("Specified plugin path", pluginPath, "does not exist."); RMGUtils.exit(); } - try { + try + { jarStream = new JarInputStream(new FileInputStream(pluginFile)); Manifest mf = jarStream.getManifest(); pluginClassName = mf.getMainAttributes().getValue(manifestAttribute); jarStream.close(); - if(pluginClassName == null) + if (pluginClassName == null) + { throw new MalformedPluginException(); + } + } - } catch(Exception e) { + catch (Exception e) + { Logger.eprintlnMixedYellow("Caught", e.getClass().getName(), "while reading the Manifest of the specified plugin."); Logger.eprintlnMixedBlue("Plugins need to be valid JAR files that contain the", manifestAttribute, "attribute."); RMGUtils.exit(); } - try { + try + { URLClassLoader ucl = new URLClassLoader(new URL[] {pluginFile.toURI().toURL()}); Class pluginClass = Class.forName(pluginClassName, true, ucl); pluginInstance = pluginClass.newInstance(); - } catch(Exception e) { + } + + catch (Exception e) + { Logger.eprintMixedYellow("Caught", e.getClass().getName(), "while reading plugin file "); Logger.printlnPlainBlue(pluginPath); ExceptionHandler.showStackTrace(e); RMGUtils.exit(); } - if(pluginInstance instanceof IPayloadProvider) { + if (pluginInstance instanceof IPayloadProvider) + { payloadProvider = (IPayloadProvider)pluginInstance; inUse = true; + } - } if(pluginInstance instanceof IResponseHandler) { + if (pluginInstance instanceof IResponseHandler) + { responseHandler = (IResponseHandler)pluginInstance; inUse = true; + } - } if(pluginInstance instanceof IArgumentProvider) { + if(pluginInstance instanceof IArgumentProvider) + { argumentProvider = (IArgumentProvider)pluginInstance; inUse = true; + } - } if(pluginInstance instanceof ISocketFactoryProvider) { + if(pluginInstance instanceof ISocketFactoryProvider) + { socketFactoryProvider = (ISocketFactoryProvider)pluginInstance; inUse = true; } - if(!inUse) { + if (!inUse) + { Logger.eprintMixedBlue("Plugin", pluginPath, "was successfully loaded, but is "); Logger.eprintlnPlainYellow("not in use."); Logger.eprintlnMixedYellow("Plugins should implement at least one of the", "IPayloadProvider, IResponseHandler, IArgumentProvider or ISocketFactoryProvider", "interfaces."); diff --git a/src/eu/tneitzel/rmg/plugin/ReturnValueProvider.java b/src/eu/tneitzel/rmg/plugin/ReturnValueProvider.java new file mode 100644 index 00000000..504c8844 --- /dev/null +++ b/src/eu/tneitzel/rmg/plugin/ReturnValueProvider.java @@ -0,0 +1,35 @@ +package eu.tneitzel.rmg.plugin; + +/** + * ReturnValueProvider is a helper class that can be used to capture return values of custom + * RMI calls. Normally these return values are ignored and users can define a custom IResponseHandler + * to process it. For some use cases (e.g. spring remoting) remote-method-guesser also requires + * access to return values. This can be achieved by temporarily using this provider, that stores + * the return value of a call within a static variable. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class ReturnValueProvider implements IResponseHandler +{ + private Object value = null; + + /** + * Just store the return value within a static class variable. + * + * @param responseObject object returned by the RMI call + */ + public void handleResponse(Object responseObject) + { + value = responseObject; + } + + /** + * Obtain the currently set value. + * + * @return currently saved response object + */ + public Object getValue() + { + return value; + } +} diff --git a/src/de/qtc/rmg/utils/ActivatableWrapper.java b/src/eu/tneitzel/rmg/utils/ActivatableWrapper.java similarity index 96% rename from src/de/qtc/rmg/utils/ActivatableWrapper.java rename to src/eu/tneitzel/rmg/utils/ActivatableWrapper.java index 759a9c95..053024df 100644 --- a/src/de/qtc/rmg/utils/ActivatableWrapper.java +++ b/src/eu/tneitzel/rmg/utils/ActivatableWrapper.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.io.IOException; import java.lang.reflect.Field; @@ -7,12 +7,12 @@ import java.rmi.server.RemoteRef; import java.rmi.server.UID; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.networking.RMIEndpoint; -import de.qtc.rmg.operations.ActivationClient; -import de.qtc.rmg.plugin.IResponseHandler; -import de.qtc.rmg.plugin.PluginSystem; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.networking.RMIEndpoint; +import eu.tneitzel.rmg.operations.ActivationClient; +import eu.tneitzel.rmg.plugin.IResponseHandler; +import eu.tneitzel.rmg.plugin.PluginSystem; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; diff --git a/src/de/qtc/rmg/utils/DefinitelyNonExistingClass.java b/src/eu/tneitzel/rmg/utils/DefinitelyNonExistingClass.java similarity index 96% rename from src/de/qtc/rmg/utils/DefinitelyNonExistingClass.java rename to src/eu/tneitzel/rmg/utils/DefinitelyNonExistingClass.java index 8e1e1d56..bc1b2bd5 100644 --- a/src/de/qtc/rmg/utils/DefinitelyNonExistingClass.java +++ b/src/eu/tneitzel/rmg/utils/DefinitelyNonExistingClass.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.io.Serializable; diff --git a/src/de/qtc/rmg/utils/EmptyWrapper.java b/src/eu/tneitzel/rmg/utils/EmptyWrapper.java similarity index 96% rename from src/de/qtc/rmg/utils/EmptyWrapper.java rename to src/eu/tneitzel/rmg/utils/EmptyWrapper.java index deb7fa7a..0b776d1d 100644 --- a/src/de/qtc/rmg/utils/EmptyWrapper.java +++ b/src/eu/tneitzel/rmg/utils/EmptyWrapper.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; /** * EmptyWrapper is basically a dummy class that extends RemoteWrapper. It is only used @@ -20,4 +20,4 @@ public EmptyWrapper(String boundName) { super(boundName); } -} \ No newline at end of file +} diff --git a/src/de/qtc/rmg/utils/ProgressBar.java b/src/eu/tneitzel/rmg/utils/ProgressBar.java similarity index 94% rename from src/de/qtc/rmg/utils/ProgressBar.java rename to src/eu/tneitzel/rmg/utils/ProgressBar.java index 6b3c11ac..e38b4ac5 100644 --- a/src/de/qtc/rmg/utils/ProgressBar.java +++ b/src/eu/tneitzel/rmg/utils/ProgressBar.java @@ -1,7 +1,7 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; /** * Simple progress bar that is used during the guess and scan operations. diff --git a/src/de/qtc/rmg/utils/RMGUtils.java b/src/eu/tneitzel/rmg/utils/RMGUtils.java similarity index 91% rename from src/de/qtc/rmg/utils/RMGUtils.java rename to src/eu/tneitzel/rmg/utils/RMGUtils.java index 453eb78a..299743c5 100644 --- a/src/de/qtc/rmg/utils/RMGUtils.java +++ b/src/eu/tneitzel/rmg/utils/RMGUtils.java @@ -1,6 +1,7 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.io.InvalidClassException; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -24,12 +25,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.MethodArguments; -import de.qtc.rmg.internal.MethodCandidate; -import de.qtc.rmg.internal.RMIComponent; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.io.MaliciousOutputStream; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodArguments; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.internal.RMIComponent; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.io.MaliciousOutputStream; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; @@ -54,8 +55,8 @@ * @author Tobias Neitzel (@qtc_de) */ @SuppressWarnings({ "rawtypes", "deprecation", "restriction" }) -public class RMGUtils { - +public class RMGUtils +{ private static ClassPool pool; private static CtClass dummyClass; private static CtClass remoteClass; @@ -73,15 +74,19 @@ public static void init() { pool = ClassPool.getDefault(); - try { + try + { remoteClass = pool.getCtClass(Remote.class.getName()); serializable = pool.getCtClass("java.io.Serializable"); remoteStubClass = pool.getCtClass(RemoteStub.class.getName()); - } catch (NotFoundException e) { + } + + catch (NotFoundException e) + { ExceptionHandler.internalError("RMGUtils.init", "Caught unexpected NotFoundException."); } - dummyClass = pool.makeInterface("de.qtc.rmg.Dummy"); + dummyClass = pool.makeInterface("eu.tneitzel.rmg.Dummy"); createdClasses = new HashSet(); } @@ -205,13 +210,69 @@ public static Class makeActivatableRef() throws CannotCompileException return Class.forName("sun.rmi.server.ActivatableRef"); } catch (ClassNotFoundException e) { // TODO needs to be implemented correctly - Logger.printlnYellow("[-] Not implemented yet!"); + Logger.eprintlnYellow("Not implemented yet!"); RMGUtils.exit(); } return null; } + /** + * Dynamically create a socket factory class that implements RMIClientSocketFactory. This function is used when + * the RMI server uses a custom socket factory class. In this case, remote-method-guesser attempts to connect + * with it's default LoopbackSslSocketFactory or LoopbackSocketFactory (depending on the value of the settings + * --ssl, --socket-factory-ssl and --socket-factory-plain) which works if the custom socket factory provided by + * the server is not too different. + * + * To achieve this, remote-method-guesser just clones LoopbackSslSocketFactory or LoopbackSocketFactory and assigns + * it a new name. As in the case of Stub classes with unusual serialVersionUIDs, the serialVersionUID is determined + * error based. The factory is first created using a default serialVersionUID. This should cause an exception revealing + * the actual serialVersionUID. This is then used to recreate the class. + * + * Check the CodebaseCollector class documentation for more information. + * + * @return socket factory class that implements RMIClientSocketFactory + * @throws CannotCompileException + */ + public static Class makeSocketFactory(String className, long serialVersionUID) throws CannotCompileException + { + try + { + return Class.forName(className); + } + + catch (ClassNotFoundException e) {} + + CtClass ctClass = null; + + try + { + + if (!RMGOption.SOCKET_FACTORY_PLAIN.getBool() && (RMGOption.CONN_SSL.getBool() || RMGOption.SOCKET_FACTORY_SSL.getBool())) + { + ctClass = pool.getAndRename("eu.tneitzel.rmg.networking.LoopbackSslSocketFactory", className); + } + + else if (!RMGOption.SOCKET_FACTORY_SSL.getBool() && (!RMGOption.CONN_SSL.getBool() || RMGOption.SOCKET_FACTORY_PLAIN.getBool())) + { + ctClass = pool.getAndRename("eu.tneitzel.rmg.networking.LoopbackSocketFactory", className); + } + + else + { + Logger.eprintlnYellow("Invalid combination of SSL related options."); + exit(); + } + + ctClass.addInterface(serializable); + addSerialVersionUID(ctClass, serialVersionUID); + } + + catch (NotFoundException e) {} + + return ctClass.toClass(); + } + /** * Creates a method from a signature string. Methods need to be assigned to a class, therefore the static * dummyClass is used that is created during the initialization of RMGUtils. The class relationship of the @@ -224,8 +285,7 @@ public static Class makeActivatableRef() throws CannotCompileException */ public static CtMethod makeMethod(String signature) throws CannotCompileException { - CtMethod method = CtNewMethod.make("public " + signature + ";", dummyClass); - return method; + return CtNewMethod.make("public " + signature + ";", dummyClass); } /** @@ -547,7 +607,7 @@ public static void exit() */ public static void enableCodebase() { - System.setProperty("java.rmi.server.RMIClassLoaderSpi", "de.qtc.rmg.internal.CodebaseCollector"); + System.setProperty("java.rmi.server.RMIClassLoaderSpi", "eu.tneitzel.rmg.internal.CodebaseCollector"); System.setProperty("java.rmi.server.useCodebaseOnly", "false"); } @@ -558,7 +618,7 @@ public static void enableCodebase() */ public static void enableCodebaseCollector() { - System.setProperty("java.rmi.server.RMIClassLoaderSpi", "de.qtc.rmg.internal.CodebaseCollector"); + System.setProperty("java.rmi.server.RMIClassLoaderSpi", "eu.tneitzel.rmg.internal.CodebaseCollector"); } /** @@ -596,63 +656,6 @@ private static void addSerialVersionUID(CtClass ctClass, long serialVersionUID) ctClass.addField(serialID, CtField.Initializer.constant(serialVersionUID)); } - /** - * Helper method that adds remote methods present on known remote objects to the list of successfully guessed methods. - * The known remote object classes are looked up by using the CtClassPool. Afterwards, all implemented interfaces - * of the corresponding CtClass are iterated and it is checked whether the interface extends java.rmi.Remote (this - * is required for all methods, that can be called from remote). From these interfaces, all methods are obtained - * and added to the list of successfully guessed methods. - * - * @param boundName bound name that is using the known class - * @param className name of the class implemented by the bound name - * @param guessedMethods list of successfully guessed methods (bound name -> list) - */ - public static List getKnownMethods(String className) - { - List knownMethods = new ArrayList(); - - try { - CtClass knownClass = pool.getCtClass(className); - - if( knownClass.isInterface() ) - addKnownMethods(knownClass, knownMethods); - - for(CtClass intf : knownClass.getInterfaces()) { - - if(! isAssignableFrom(intf, "java.rmi.Remote")) - continue; - - addKnownMethods(intf, knownMethods); - } - - } catch(Exception e) { - ExceptionHandler.unexpectedException(e, "translation process", "of known remote methods", false); - } - - return knownMethods; - } - - /** - * Same as the previous addKnownMethods function, but takes the corresponding interface as argument directly. - * This function is called by the previous addKnownMethods function to add the methods. - * - * @param intf Interface class to add methods from - * @param boundName bound name that is using the known class - * @param guessedMethods list of successfully guessed methods (bound name -> list) - */ - public static void addKnownMethods(CtClass intf, List knownMethodCandidates) - { - try { - CtMethod[] knownMethods = intf.getDeclaredMethods(); - - for(CtMethod knownMethod: knownMethods) - knownMethodCandidates.add(new MethodCandidate(knownMethod)); - - } catch(Exception e) { - ExceptionHandler.unexpectedException(e, "translation process", "of known remote methods", false); - } - } - /** * Returns a human readable method signature of a CtMethod. Builtin methods only return the signature in * a non well formatted format. This function is used to display known remote methods as the result of a @@ -1071,7 +1074,7 @@ public static ObjID parseObjID(String objIdString) * @param remoteObject Object to obtain the class from * @return Class name of the implementor or one of its interfaces in case of a Proxy */ - public static String getClassName(Remote remoteObject) + public static String getInterfaceName(Remote remoteObject) { if( Proxy.isProxyClass(remoteObject.getClass()) ) { @@ -1270,4 +1273,49 @@ public static long getSerialVersionUID(InvalidClassException e) return Long.parseLong(message); } + + /** + * Convert a CtClass back to an ordinary Class object. This method is intended to be called + * for classes that are known to already exist within the JVM. No compilation is triggered but + * the Class object is simply obtained by Class.forName (including handling for all the edge + * cases). + * + * @param type the CtClass that should be converted back to a Class object + * @return Class associated to the specified CtClass + */ + public static Class ctClassToClass(CtClass type) throws ClassNotFoundException, NotFoundException + { + if (type.isPrimitive()) + { + if (type == CtPrimitiveType.intType) { + return int.class; + } else if (type == CtPrimitiveType.booleanType) { + return boolean.class; + } else if (type == CtPrimitiveType.byteType) { + return byte.class; + } else if (type == CtPrimitiveType.charType) { + return char.class; + } else if (type == CtPrimitiveType.shortType) { + return short.class; + } else if (type == CtPrimitiveType.longType) { + return long.class; + } else if (type == CtPrimitiveType.floatType) { + return float.class; + } else if (type == CtPrimitiveType.doubleType) { + return double.class; + } else { + throw new Error("unrecognized primitive type: " + type); + } + } + + else if (type.isArray()) + { + return Array.newInstance(ctClassToClass(type.getComponentType()), 0).getClass(); + } + + else + { + return Class.forName(type.getName()); + } + } } diff --git a/src/eu/tneitzel/rmg/utils/RemoteInvocationHolder.java b/src/eu/tneitzel/rmg/utils/RemoteInvocationHolder.java new file mode 100644 index 00000000..092a1c33 --- /dev/null +++ b/src/eu/tneitzel/rmg/utils/RemoteInvocationHolder.java @@ -0,0 +1,88 @@ +package eu.tneitzel.rmg.utils; + +import java.util.Arrays; + +import org.springframework.remoting.support.RemoteInvocation; + +import eu.tneitzel.rmg.internal.MethodCandidate; + + +/** + * RemoteInvocation objects do not contain all information that MethodCandidates contain. When converting + * a MethodCandidate to a RemoteInvocation, information like the signature or the return value get lost. + * Moreover, two RemoteInvocations can be considered the same when they have a similar name and similar + * method arguments. The return value does not matter. + * + * The RemoteInvocationHolder class is designed to overcome these problems. It tracks the associated + * MethodCandidate to each RemoteInvocation and implements methods that allow to compare RemoteInvocations + * and to filter duplicates. + * + * @author Tobias Neitzel (@qtc_de) + */ +public class RemoteInvocationHolder +{ + private RemoteInvocation invo; + private MethodCandidate candidate; + + /** + * An RemoteInvocationWrapper simply contains a RemoteInvocation and the associated MethodCandidate. + * + * @param invo the RemoteInvocation + * @param candidate the associated MethodCandidate + */ + public RemoteInvocationHolder(RemoteInvocation invo, MethodCandidate candidate) + { + this.invo = invo; + this.candidate = candidate; + } + + /** + * Two RemoteInocationHolders are the same, if their contained RemoteInvocation uses the same + * method name and the same argument types. + * + * @param other Object to compare with + * @return true if the objects can be considered the same + */ + public boolean equals(Object other) + { + if (other instanceof RemoteInvocationHolder) + { + RemoteInvocationHolder otherInvocation = (RemoteInvocationHolder)other; + + if (otherInvocation.getName().equals(this.getName())) + { + if (Arrays.equals(otherInvocation.getTypes(), this.getTypes())) + { + return true; + } + } + } + + return false; + } + + public int hashCode() + { + return invo.toString().hashCode(); + } + + public String getName() + { + return invo.getMethodName(); + } + + public Class[] getTypes() + { + return invo.getParameterTypes(); + } + + public MethodCandidate getCandidate() + { + return candidate; + } + + public RemoteInvocation getInvo() + { + return invo; + } +} diff --git a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java b/src/eu/tneitzel/rmg/utils/RemoteObjectWrapper.java similarity index 81% rename from src/de/qtc/rmg/utils/RemoteObjectWrapper.java rename to src/eu/tneitzel/rmg/utils/RemoteObjectWrapper.java index d3b63ab9..e5f897ff 100644 --- a/src/de/qtc/rmg/utils/RemoteObjectWrapper.java +++ b/src/eu/tneitzel/rmg/utils/RemoteObjectWrapper.java @@ -1,11 +1,11 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.rmi.Remote; import java.rmi.server.RemoteRef; -import de.qtc.rmg.endpoints.KnownEndpoint; -import de.qtc.rmg.endpoints.KnownEndpointHolder; -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.endpoints.KnownEndpoint; +import eu.tneitzel.rmg.endpoints.KnownEndpointHolder; +import eu.tneitzel.rmg.internal.ExceptionHandler; import javassist.tools.reflect.Reflection; import sun.rmi.server.UnicastRef; @@ -23,11 +23,12 @@ @SuppressWarnings("restriction") public abstract class RemoteObjectWrapper { - public String className; public String boundName; public Remote remoteObject; public KnownEndpoint knownEndpoint; + private String interfaceName; + /** * This constructor is only used for special purposes during the enum action. The resulting * RemoteObjectWrapper is not fully functional and should not be used for other purposes than @@ -54,8 +55,8 @@ public RemoteObjectWrapper(String boundName, Remote remoteObject) this.boundName = boundName; this.remoteObject = remoteObject; - this.className = RMGUtils.getClassName(remoteObject); - this.knownEndpoint = KnownEndpointHolder.getHolder().lookup(className); + this.interfaceName = RMGUtils.getInterfaceName(remoteObject); + this.knownEndpoint = KnownEndpointHolder.getHolder().lookup(interfaceName); } /** @@ -84,16 +85,32 @@ public static RemoteObjectWrapper getInstance(Remote remote) throws IllegalArgum public static RemoteObjectWrapper getInstance(Remote remote, String boundName) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { RemoteObjectWrapper wrapper = null; + RemoteRef ref = RMGUtils.extractRef(remote); + String interfaceName = RMGUtils.getInterfaceName(remote); if (ref instanceof UnicastRef) - wrapper = new UnicastWrapper(remote, boundName, (UnicastRef)ref); + { + if (interfaceName.equals(SpringRemotingWrapper.invocationHandlerClass)) + { + wrapper = new SpringRemotingWrapper(remote, boundName, (UnicastRef)ref); + } + + else + { + wrapper = new UnicastWrapper(remote, boundName, (UnicastRef)ref); + } + } else if (ref.getClass().getName().contains("ActivatableRef")) + { wrapper = new ActivatableWrapper(remote, boundName, ref); + } else + { ExceptionHandler.internalError("RemoteObjectWrapper.getInstance", "Unexpected reference type"); + } return wrapper; } @@ -110,8 +127,10 @@ public static RemoteObjectWrapper getByName(String boundName, RemoteObjectWrappe { for(RemoteObjectWrapper o : list) { - if( o != null && o.boundName.equals(boundName) ) + if (o != null && o.boundName.equals(boundName)) + { return o; + } } return null; @@ -142,12 +161,24 @@ public static RemoteObjectWrapper[] fromBoundNames(String[] boundNames) */ public boolean isKnown() { - if( knownEndpoint == null ) + if (knownEndpoint == null) + { return false; + } return true; } + /** + * Return the interface name that is implemented by the remote object. + * + * @return interface name implemented by the remote object + */ + public String getInterfaceName() + { + return interfaceName; + } + /** * Transform an RemoteObjectWrapper into a UnicastWrapper. If the RemoteObjectWrapper is already * a UnicastWrapper, it is simply returned. If it is an ActivatableWrapper instead, it is activated. @@ -159,15 +190,22 @@ public UnicastWrapper getUnicastWrapper() UnicastWrapper returnValue = null; if (this instanceof UnicastWrapper) + { returnValue = (UnicastWrapper)this; + } else - - try { + { + try + { returnValue = ((ActivatableWrapper)this).activate(); - } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + } + + catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) + { ExceptionHandler.unexpectedException(e, "activate", "call", true); } + } return returnValue; } @@ -188,14 +226,18 @@ public static UnicastWrapper[] getUnicastWrappers(RemoteObjectWrapper[] wrappers { if (wrappers[ctr] instanceof UnicastWrapper) { - unicastWrappers[ctr] = (UnicastWrapper) wrappers[ctr]; + unicastWrappers[ctr] = (UnicastWrapper)wrappers[ctr]; } else { - try { + try + { unicastWrappers[ctr] = ((ActivatableWrapper)wrappers[ctr]).activate(); - } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { + } + + catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) + { ExceptionHandler.unexpectedException(e, "activate", "call", true); } } diff --git a/src/de/qtc/rmg/utils/RogueJMX.java b/src/eu/tneitzel/rmg/utils/RogueJMX.java similarity index 97% rename from src/de/qtc/rmg/utils/RogueJMX.java rename to src/eu/tneitzel/rmg/utils/RogueJMX.java index 29ea49de..7495afa2 100644 --- a/src/de/qtc/rmg/utils/RogueJMX.java +++ b/src/eu/tneitzel/rmg/utils/RogueJMX.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -12,10 +12,10 @@ import javax.management.remote.rmi.RMIConnection; import javax.management.remote.rmi.RMIServer; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.LimitedSocketFactory; -import de.qtc.rmg.operations.RemoteObjectClient; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.LimitedSocketFactory; +import eu.tneitzel.rmg.operations.RemoteObjectClient; import sun.rmi.server.UnicastServerRef; import sun.rmi.transport.LiveRef; @@ -225,4 +225,4 @@ public RMIConnection newClient(Object credentials) throws IOException return conditionalForward(credentials, "Authentication failed!"); } -} \ No newline at end of file +} diff --git a/src/de/qtc/rmg/utils/Security.java b/src/eu/tneitzel/rmg/utils/Security.java similarity index 96% rename from src/de/qtc/rmg/utils/Security.java rename to src/eu/tneitzel/rmg/utils/Security.java index 213dcb3f..39b8a651 100644 --- a/src/de/qtc/rmg/utils/Security.java +++ b/src/eu/tneitzel/rmg/utils/Security.java @@ -1,10 +1,10 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.util.regex.Matcher; import java.util.regex.Pattern; -import de.qtc.rmg.exceptions.UnexpectedCharacterException; -import de.qtc.rmg.internal.RMGOption; +import eu.tneitzel.rmg.exceptions.UnexpectedCharacterException; +import eu.tneitzel.rmg.internal.RMGOption; /** * During sample creation, remote-method-guesser creates sample files with filenames and contents controlled by diff --git a/src/eu/tneitzel/rmg/utils/SpringRemotingWrapper.java b/src/eu/tneitzel/rmg/utils/SpringRemotingWrapper.java new file mode 100644 index 00000000..79e60cfe --- /dev/null +++ b/src/eu/tneitzel/rmg/utils/SpringRemotingWrapper.java @@ -0,0 +1,248 @@ +package eu.tneitzel.rmg.utils; + +import java.rmi.Remote; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.remoting.support.RemoteInvocation; + +import eu.tneitzel.rmg.endpoints.KnownEndpointHolder; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.MethodCandidate; +import eu.tneitzel.rmg.operations.RemoteObjectClient; +import eu.tneitzel.rmg.plugin.IResponseHandler; +import eu.tneitzel.rmg.plugin.PluginSystem; +import eu.tneitzel.rmg.plugin.ReturnValueProvider; +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.NotFoundException; +import sun.rmi.server.UnicastRef; + +/** + * SpringRemoting represents a wrapper around regular Java RMI. Exposed methods are not directly available via + * RemoteObjects, but are invoked using a dispatcher object that supports an invoke method. This class contains + * functions to convert an ordinary RMI method call into a SpringRemoting call. + * + * @author Tobias Neitzel (@qtc_de) + */ +@SuppressWarnings("restriction") +public class SpringRemotingWrapper extends UnicastWrapper +{ + public final static String invocationHandlerClass = "org.springframework.remoting.rmi.RmiInvocationHandler"; + public final static String methodGetStr = "java.lang.String getTargetInterfaceName()"; + public final static String methodInvokeStr = "java.lang.Object invoke(org.springframework.remoting.support.RemoteInvocation invo)"; + + private static MethodCandidate methodGet; + private static MethodCandidate methodInvoke; + + private static String remotingInterfaceName; + + /** + * Within it's constructor, SpringRemotingWrapper already sends an RMI call to the server using the getTargetInterfaceName method. + * This is required to tell remote-method-guesser what the underlying interface class is. This information is displayed within the + * enum output and used for identifying potentialy known endpoints. + * + * @param remoteObject the spring remoting remoteObject obtained from the registry + * @param boundName the boundName that is associated with the remoteObject + * @param ref a UnicastRef that can be used to call methods on the remoteObject + */ + public SpringRemotingWrapper(Remote remoteObject, String boundName, UnicastRef ref) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException + { + super(remoteObject, boundName, ref); + + ReturnValueProvider respHandler = new ReturnValueProvider(); + IResponseHandler cachedHandler = PluginSystem.getResponseHandler(); + PluginSystem.setResponeHandler(respHandler); + + RemoteObjectClient client = new RemoteObjectClient(this); + client.genericCall(getInterfaceNameMethod(), new Object[] {}); + + remotingInterfaceName = (String)respHandler.getValue(); + knownEndpoint = KnownEndpointHolder.getHolder().lookup(remotingInterfaceName); + + PluginSystem.setResponeHandler(cachedHandler); + } + + /** + * Return a MethodCandidate for the getTargetInterfaceName method that is exposed by the RmiInvocationHandler + * remote object. + * + * @return method candidate for the getTargetInterfaceName method + */ + public static MethodCandidate getInterfaceNameMethod() + { + if (methodGet == null) + { + try + { + methodGet = new MethodCandidate(methodGetStr); + } + + catch (CannotCompileException | NotFoundException e) + { + ExceptionHandler.internalError("SpringRemoting.getInterfaceMethod", e.getMessage()); + } + } + + return methodGet; + } + + /** + * Return a MethodCandidate for the invoke method that is exposed by the RmiInvocationHandler + * remote object. + * + * @return method candidate for the invoke method + */ + public static MethodCandidate getInvokeMethod() + { + if (methodInvoke == null) + { + try + { + methodInvoke = new MethodCandidate(methodInvokeStr); + } + + catch (CannotCompileException | NotFoundException e) + { + ExceptionHandler.internalError("SpringRemoting.getInterfaceMethod", e.getMessage()); + } + } + + return methodInvoke; + } + + /** + * Determines whether the method to call is a known spring remoting method, that needs to be processed by the + * remoting wrapper itself, or whether it is an RMI method implemented by the underlying object. + * + * @return true if the method needs to be dispatched using spring remoting + */ + public boolean isRemotingCall(MethodCandidate targetMethod) + { + long targetHash = targetMethod.getHash(); + + if (targetHash == getInvokeMethod().getHash() || targetHash == getInterfaceNameMethod().getHash()) + { + return false; + } + + return true; + } + + /** + * Return the interface name of the underlying interface class that can be accessed via spring remoting. + * + * @return interface name implemented by the underlying remote object + */ + public String getInterfaceName() + { + return remotingInterfaceName; + } + + /** + * Prepare a RemoteInvocation object from a MethodCandidate and the user specified arguments. The resulting + * object can be passed to the spring remoting endpoint in order to call the specified MethodCandidate. + * + * @param targetMethod method that should be called via spring remoting + * @param args arguments that should be used for the call + * @return RemoteInvocation that can be passed to the spring remoting server + */ + public static RemoteInvocation buildRemoteInvocation(MethodCandidate targetMethod, Object[] args) + { + RemoteInvocation invo = new RemoteInvocation(); + + try + { + CtClass[] argTypes = targetMethod.getParameterTypes(); + Class[] parameterTypes = new Class[argTypes.length]; + + try + { + for (int ctr = 0; ctr < argTypes.length; ctr++) + { + parameterTypes[ctr] = RMGUtils.ctClassToClass(argTypes[ctr]); + } + } + + catch (ClassNotFoundException e) + { + ExceptionHandler.internalError("SpringRemoting.buildRemoteInvocation", e.getMessage()); + } + + String methodName = targetMethod.getName(); + + invo.setMethodName(methodName); + invo.setArguments(args); + invo.setParameterTypes(parameterTypes); + } + + catch (CannotCompileException | NotFoundException e) + { + ExceptionHandler.cannotCompile(e, "while building", "Spring RemoteInvocation", true); + } + + return invo; + } + + /** + * Transform a set of MethodCandidate to a set of RemoteInvocationHolders. + * + * @param candidates v set of MethodCandidate to transform + * @return set of RemoteInvocationHolders + */ + public static Set getInvocationHolders(Set candidates) + { + Set invocationHolderSet = new HashSet(); + + for (MethodCandidate candidate : candidates) + { + Object[] args = new Object[] {}; + + if (candidate.getArgumentCount() == 0) + { + args = new Object[] {1}; + } + + RemoteInvocationHolder invoHolder = new RemoteInvocationHolder(buildRemoteInvocation(candidate, args), candidate); + invocationHolderSet.add(invoHolder); + } + + return invocationHolderSet; + } + + /** + * Returns a method signature for the specified MethodCandidate that lacks the return value. The + * return value is replaced with three question marks. The reason is that spring remoting treats + * similar methods with different return values the same. Therefore, it is not possible to determine + * the return value of a successfully guessed method. + * + * @param method MethodCandidate to obtain the modified signature from + * @return modified signature without a return value + */ + public static String getSignature(MethodCandidate method) + { + String signature = method.getSignature(); + + return "???" + signature.substring(signature.indexOf(' ')); + } + + /** + * Helper function that checks whether a list of UnicastWrapper objects contains a SpringRemotingWrapper + * (which is a child class of UnicastWrapper). + * + * @param wrappers list of UnicastWrapper objects + * @return true if at least one SpringRemotingWrapper is contained within the list + */ + public static boolean containsSpringRemotingClient(UnicastWrapper[] wrappers) + { + for (UnicastWrapper wrapper : wrappers) + { + if (wrapper instanceof SpringRemotingWrapper) + { + return true; + } + } + + return false; + } +} diff --git a/src/de/qtc/rmg/utils/UnicastWrapper.java b/src/eu/tneitzel/rmg/utils/UnicastWrapper.java similarity index 81% rename from src/de/qtc/rmg/utils/UnicastWrapper.java rename to src/eu/tneitzel/rmg/utils/UnicastWrapper.java index ee543fd0..ec8e8cc5 100644 --- a/src/de/qtc/rmg/utils/UnicastWrapper.java +++ b/src/eu/tneitzel/rmg/utils/UnicastWrapper.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.lang.reflect.Field; import java.lang.reflect.Proxy; @@ -6,14 +6,11 @@ import java.rmi.server.ObjID; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; -import java.rmi.server.RMISocketFactory; import java.rmi.server.RemoteObjectInvocationHandler; import java.util.ArrayList; import java.util.List; -import javax.rmi.ssl.SslRMIClientSocketFactory; - -import de.qtc.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.ExceptionHandler; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; @@ -95,33 +92,6 @@ public String getTarget() return getHost() + ":" + getPort(); } - /** - * Checks whether the socket factory used by the remote object is TLS protected. This function - * returns 1 if the default SslRMIClientSocketFactory class is used. -1 if the default RMISocketFactory - * class is used and 0 if none of the previously mentioned cases applies. Notice that a client - * socket factory with a value of null implies the default socket factory (RMISocketFactory). - * - * @return 1 -> SslRMIClientSocketFactory, -1 -> RMISocketFactory, 0 -> Unknown - */ - public int isTLSProtected() - { - if( csf != null ) { - - Class factoryClass = csf.getClass(); - - if( factoryClass == SslRMIClientSocketFactory.class ) - return 1; - - if( factoryClass == RMISocketFactory.class ) - return -1; - - } else if( remoteObject != null ) { - return -1; - } - - return 0; - } - /** * Checks whether the Wrapper has any duplicates (other remote objects that implement the same * remote interface). @@ -130,8 +100,10 @@ public int isTLSProtected() */ public boolean hasDuplicates() { - if( this.duplicates.size() == 0 ) + if (this.duplicates.size() == 0) + { return false; + } return true; } @@ -156,8 +128,10 @@ public String[] getDuplicateBoundNames() { List duplicateNames = new ArrayList(); - for(UnicastWrapper o : this.duplicates) + for (UnicastWrapper o : this.duplicates) + { duplicateNames.add(o.boundName); + } return duplicateNames.toArray(new String[0]); } @@ -173,8 +147,10 @@ public String[] getDuplicateBoundNames() */ public static UnicastWrapper fromRef(UnicastRef unicastRef, Class intf) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException { - if( !Remote.class.isAssignableFrom(intf) ) + if (!Remote.class.isAssignableFrom(intf)) + { ExceptionHandler.internalError("UnicastWrapper.fromRef", "Specified interface is not valid"); + } RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef); Remote remoteObject = (Remote)Proxy.newProxyInstance(intf.getClassLoader(), new Class[] { intf }, remoteObjectInvocationHandler); @@ -193,11 +169,12 @@ public static UnicastWrapper[] handleDuplicates(UnicastWrapper[] list) { List unique = new ArrayList(); - outer: for(UnicastWrapper current : list) { - - for(UnicastWrapper other : unique) { - - if(other.className.equals(current.className)) { + outer: for (UnicastWrapper current : list) + { + for (UnicastWrapper other : unique) + { + if (other.getInterfaceName().equals(current.getInterfaceName())) + { other.addDuplicate(current); continue outer; } @@ -217,10 +194,12 @@ public static UnicastWrapper[] handleDuplicates(UnicastWrapper[] list) */ public static boolean hasDuplicates(UnicastWrapper[] list) { - for(UnicastWrapper o : list) { - - if( o.hasDuplicates() ) + for (UnicastWrapper o : list) + { + if (o.hasDuplicates()) + { return true; + } } return false; diff --git a/src/de/qtc/rmg/utils/YsoIntegration.java b/src/eu/tneitzel/rmg/utils/YsoIntegration.java similarity index 98% rename from src/de/qtc/rmg/utils/YsoIntegration.java rename to src/eu/tneitzel/rmg/utils/YsoIntegration.java index b49d66e8..9a16c813 100644 --- a/src/de/qtc/rmg/utils/YsoIntegration.java +++ b/src/eu/tneitzel/rmg/utils/YsoIntegration.java @@ -1,4 +1,4 @@ -package de.qtc.rmg.utils; +package eu.tneitzel.rmg.utils; import java.io.File; import java.lang.reflect.Constructor; @@ -22,10 +22,10 @@ import javax.net.ServerSocketFactory; -import de.qtc.rmg.internal.ExceptionHandler; -import de.qtc.rmg.internal.RMGOption; -import de.qtc.rmg.io.Logger; -import de.qtc.rmg.networking.DummySocketFactory; +import eu.tneitzel.rmg.internal.ExceptionHandler; +import eu.tneitzel.rmg.internal.RMGOption; +import eu.tneitzel.rmg.io.Logger; +import eu.tneitzel.rmg.networking.DummySocketFactory; import sun.rmi.server.UnicastRef; import sun.rmi.transport.LiveRef; import sun.rmi.transport.tcp.TCPEndpoint; diff --git a/tests/generic/tests/error-handling.yml b/tests/generic/tests/error-handling.yml index 6efec272..d8708971 100644 --- a/tests/generic/tests/error-handling.yml +++ b/tests/generic/tests/error-handling.yml @@ -65,25 +65,6 @@ tests: - 'The specified port is probably closed' - - title: Missing Route - description: |- - 'Attempts to use rmg on a host with missing route' - - command: - - rmg - - enum - - 10.10.10.10 - - 9010 - - ${OPTIONS} - - validators: - - error: True - - contains: - values: - - 'Caugth SocketException' - - 'The specified target is not reachable' - - - title: Connection Reset description: |- 'Attempts to use rmg without --ssl on ncat ssl listener' diff --git a/tests/generic/tests/rogue-jmx.yml b/tests/generic/tests/rogue-jmx.yml index ecc6f210..38b94832 100644 --- a/tests/generic/tests/rogue-jmx.yml +++ b/tests/generic/tests/rogue-jmx.yml @@ -12,7 +12,7 @@ tester: containers: - name: 'rmg-ssrf' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:1.4' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-ssrf-server:2.0' network_mode: host diff --git a/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml b/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml index bca36ce3..e1cb8e51 100644 --- a/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml +++ b/tests/generic/tests/rogue-jmx/rogue-jmx-child.yml @@ -17,7 +17,7 @@ plugins: - --no-color - --objid - "'${ROGUE_OBJID}'" - - '&>' + - '>' - ${JMX_LOG_1} - os_command: @@ -38,7 +38,7 @@ plugins: - ${FORWARD_PORT-0-1} - --forward-objid - "'${FORWARD_OBJID-0-1}'" - - '&>' + - '>' - ${JMX_LOG_2} diff --git a/tests/generic/tests/ssrf-response.yml b/tests/generic/tests/ssrf-response.yml index ebd8cead..a9d4d834 100644 --- a/tests/generic/tests/ssrf-response.yml +++ b/tests/generic/tests/ssrf-response.yml @@ -78,7 +78,7 @@ tests: values: - 'plain-server' - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' - - 'Endpoint: iinsecure.dev:37797 TLS: no ObjID: [79bf1d8a:17b14e4e4b0:-7ff8, -8372830402508756097]' + - 'Endpoint: iinsecure.dev:37797 CSF: RMISocketFactory ObjID: [79bf1d8a:17b14e4e4b0:-7ff8, -8372830402508756097]' - 'http://iinsecure.dev/well-hidden-development-folder/' @@ -150,7 +150,7 @@ tests: - contains: values: - 'NotBoundException' - - 'unbind was accepeted' + - 'unbind was accepted' - 'Vulnerability Status: Vulnerable' @@ -317,7 +317,7 @@ tests: values: - 'plain-server' - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' - - 'Endpoint: iinsecure.dev:37797 TLS: no ObjID: [79bf1d8a:17b14e4e4b0:-7ff8, -8372830402508756097]' + - 'Endpoint: iinsecure.dev:37797 CSF: RMISocketFactory ObjID: [79bf1d8a:17b14e4e4b0:-7ff8, -8372830402508756097]' - 'http://iinsecure.dev/well-hidden-development-folder/' @@ -389,7 +389,7 @@ tests: - contains: values: - 'NotBoundException' - - 'unbind was accepeted' + - 'unbind was accepted' - 'Vulnerability Status: Vulnerable' diff --git a/tests/generic/tests/ssrf.yml b/tests/generic/tests/ssrf.yml index ac6bec6c..9eceb236 100644 --- a/tests/generic/tests/ssrf.yml +++ b/tests/generic/tests/ssrf.yml @@ -110,7 +110,7 @@ tests: - error: False - contains: values: - - '4a524d4900024c50aced000577220000000000000002000000000000000000000000000000000000f6b6898d8bf28643757200185b4c6a6176612e726d692e7365727665722e4f626a49443b871300b8d02c647e02000074000a496e76616c696455524c787000000000770800000000000000007372002b64652e7174632e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c617373000000000000000202000071007e00017870770101' + - '4a524d4900024c50aced000577220000000000000002000000000000000000000000000000000000f6b6898d8bf28643757200185b4c6a6176612e726d692e7365727665722e4f626a49443b871300b8d02c647e02000074000a496e76616c696455524c787000000000770800000000000000007372003065752e746e6569747a656c2e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c617373000000000000000202000071007e00017870770101' - title: SSRF Enum JEP290 @@ -238,7 +238,7 @@ tests: - error: False - contains: values: - - '4a524d4900024c50aced00057722000000000000000000000000000000000000000000000000000244154dc9d4e63bdf737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c75657372002b64652e7174632e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c6173730000000000000002020000707870787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b02000071007e0001787000000000' + - '4a524d4900024c50aced00057722000000000000000000000000000000000000000000000000000244154dc9d4e63bdf737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c75657372003065752e746e6569747a656c2e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c6173730000000000000002020000707870787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b02000071007e0001787000000000' - title: SSRF Enum (Stream Protocol) description: |- @@ -344,7 +344,7 @@ tests: - error: False - contains: values: - - '4a524d4900024b00093132372e302e312e310000000050aced000577220000000000000002000000000000000000000000000000000000f6b6898d8bf28643757200185b4c6a6176612e726d692e7365727665722e4f626a49443b871300b8d02c647e02000074000a496e76616c696455524c787000000000770800000000000000007372002b64652e7174632e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c617373000000000000000202000071007e00017870770101' + - '4a524d4900024b00093132372e302e312e310000000050aced000577220000000000000002000000000000000000000000000000000000f6b6898d8bf28643757200185b4c6a6176612e726d692e7365727665722e4f626a49443b871300b8d02c647e02000074000a496e76616c696455524c787000000000770800000000000000007372003065752e746e6569747a656c2e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c617373000000000000000202000071007e00017870770101' - title: SSRF Enum JEP290 (Stream Protocol) @@ -478,4 +478,4 @@ tests: - error: False - contains: values: - - '4a524d4900024b00093132372e302e312e310000000050aced00057722000000000000000000000000000000000000000000000000000244154dc9d4e63bdf737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c75657372002b64652e7174632e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c6173730000000000000002020000707870787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b02000071007e0001787000000000' + - '4a524d4900024b00093132372e302e312e310000000050aced00057722000000000000000000000000000000000000000000000000000244154dc9d4e63bdf737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c75657372003065752e746e6569747a656c2e726d672e7574696c732e446566696e6974656c794e6f6e4578697374696e67436c6173730000000000000002020000707870787200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b02000071007e0001787000000000' diff --git a/tests/jdk11/jdk11.yml b/tests/jdk11/jdk11.yml index bd6af49c..a34f4e7d 100644 --- a/tests/jdk11/jdk11.yml +++ b/tests/jdk11/jdk11.yml @@ -10,7 +10,7 @@ tester: containers: - name: 'rmg-jdk11' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk11' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk11' volumes: - '${volume}:${volume-d}' aliases: diff --git a/tests/jdk11/tests/enum.yml b/tests/jdk11/tests/enum.yml index 89a2af92..6ecc50fc 100644 --- a/tests/jdk11/tests/enum.yml +++ b/tests/jdk11/tests/enum.yml @@ -30,17 +30,17 @@ tests: ignore_case: True values: - 'ssl-server' - - 'de.qtc.rmg.server.interfaces.ISslServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISslServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'secure-server' - - 'de.qtc.rmg.server.interfaces.ISecureServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISecureServer (unknown class)' - regex: description: |- 'Check whether objID values are displayed' match: - - 'Endpoint: iinsecure.example:\d+ TLS: (yes|no|unknown) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' + - 'Endpoint: iinsecure.example:\d+ CSF: (RMISocketFactory|SslRMIClientSocketFactory) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' - contains: description: |- @@ -76,11 +76,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - 'de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - 'eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -120,7 +120,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -185,17 +185,17 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' invert: - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -235,7 +235,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -301,16 +301,16 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -351,7 +351,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -423,11 +423,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - '_de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - '_eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -467,7 +467,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: diff --git a/tests/jdk8/jdk8.yml b/tests/jdk8/jdk8.yml index 04acc49c..489cd56d 100644 --- a/tests/jdk8/jdk8.yml +++ b/tests/jdk8/jdk8.yml @@ -10,7 +10,7 @@ tester: containers: - name: 'rmg-jdk8' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk8' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk8' volumes: - '${volume}:${volume-d}' aliases: diff --git a/tests/jdk8/tests/dgc.yml b/tests/jdk8/tests/dgc.yml index 4c5df219..e81e4759 100644 --- a/tests/jdk8/tests/dgc.yml +++ b/tests/jdk8/tests/dgc.yml @@ -38,7 +38,7 @@ tests: - contains: values: - 'Caught unexpected AccessControlException during deserialization attack' - - 'The servers SecurityManager may refused the operation.' + - "The server's SecurityManager may refused the operation." - title: Gadget Call (SSL) diff --git a/tests/jdk8/tests/enum.yml b/tests/jdk8/tests/enum.yml index 44d140d6..3a029ce5 100644 --- a/tests/jdk8/tests/enum.yml +++ b/tests/jdk8/tests/enum.yml @@ -30,17 +30,17 @@ tests: ignore_case: True values: - 'ssl-server' - - 'de.qtc.rmg.server.interfaces.ISslServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISslServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'secure-server' - - 'de.qtc.rmg.server.interfaces.ISecureServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISecureServer (unknown class)' - regex: description: |- 'Check whether objID values are displayed' match: - - 'Endpoint: iinsecure.example:\d+ TLS: (yes|no|unknown) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' + - 'Endpoint: iinsecure.example:\d+ CSF: (SslRMIClientSocketFactory|RMISocketFactory) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' - contains: description: |- @@ -79,11 +79,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - 'de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - 'eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -124,7 +124,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -190,17 +190,17 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' invert: - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -241,7 +241,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -308,16 +308,16 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -359,7 +359,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -432,11 +432,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - '_de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - '_eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -477,7 +477,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: diff --git a/tests/jdk9/jdk9.yml b/tests/jdk9/jdk9.yml index 57a6449e..4904d613 100644 --- a/tests/jdk9/jdk9.yml +++ b/tests/jdk9/jdk9.yml @@ -10,7 +10,7 @@ tester: containers: - name: 'rmg-jdk9' - image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:4.0-jdk9' + image: 'ghcr.io/qtc-de/remote-method-guesser/rmg-example-server:5.0-jdk9' volumes: - '${volume}:${volume-d}' aliases: diff --git a/tests/jdk9/tests/bind.yml b/tests/jdk9/tests/bind.yml index 8908e2b8..712af8a9 100644 --- a/tests/jdk9/tests/bind.yml +++ b/tests/jdk9/tests/bind.yml @@ -93,8 +93,8 @@ tests: - error: False - contains: values: - - Registry rejected bind call because it was not send from localhost. - - Localhost bypass was used but failed. + - Registry rejected bind call because it was not sent from localhost. + - Localhost bypass was used, but failed. - title: Verify Bind diff --git a/tests/jdk9/tests/enum.yml b/tests/jdk9/tests/enum.yml index acd22df3..0acbbecf 100644 --- a/tests/jdk9/tests/enum.yml +++ b/tests/jdk9/tests/enum.yml @@ -30,17 +30,17 @@ tests: ignore_case: True values: - 'ssl-server' - - 'de.qtc.rmg.server.interfaces.ISslServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISslServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'secure-server' - - 'de.qtc.rmg.server.interfaces.ISecureServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.ISecureServer (unknown class)' - regex: description: |- 'Check whether objID values are displayed' match: - - 'Endpoint: iinsecure.example:\d+ TLS: (yes|no|unknown) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' + - 'Endpoint: iinsecure.example:\d+ CSF: (RMISocketFactory|SslRMIClientSocketFactory) ObjID: \[[0-9a-f:-]+, [0-9-]+\]' - contains: description: |- @@ -79,11 +79,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - 'de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - 'eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -124,7 +124,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Caught NotBoundException during unbind call (unbind was accepeted). + [+] - Caught NotBoundException during unbind call (unbind was accepted). [+] Vulnerability Status: Vulnerable - contains: @@ -190,17 +190,17 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' invert: - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -241,7 +241,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -308,16 +308,16 @@ tests: ignore_case: True values: - 'activation-test' - - 'de.qtc.rmg.server.activation.IActivationService (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'activation-test2' - - 'de.qtc.rmg.server.activation.IActivationService2 (unknown class)' + - 'eu.tneitzel.rmg.server.activation.IActivationService2 (unknown class)' - 'Activator: iinsecure.example:1098 ActivationID:' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'java.rmi.activation.ActivationSystem' - 'sun.rmi.server.Activation$ActivationSystemImpl_Stub (known class: RMI Activation System)' - - 'TLS: yes ObjID:' + - 'CSF: SslRMIClientSocketFactory ObjID:' - contains: description: |- @@ -359,7 +359,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Registry rejected unbind call cause it was not send from localhost. + [+] - Registry rejected unbind call cause it was not sent from localhost. [+] Vulnerability Status: Non Vulnerable - contains: @@ -432,11 +432,11 @@ tests: ignore_case: True values: - 'plain-server2' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'plain-server' - - 'de.qtc.rmg.server.interfaces.IPlainServer (unknown class)' + - 'eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)' - 'legacy-service' - - '_de.qtc.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' + - '_eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)' - contains: description: |- @@ -477,7 +477,7 @@ tests: - |- [+] RMI registry localhost bypass enumeration (CVE-2019-2684): [+] - [+] - Caught NotBoundException during unbind call (unbind was accepeted). + [+] - Caught NotBoundException during unbind call (unbind was accepted). [+] Vulnerability Status: Vulnerable - contains: diff --git a/tests/shared/bind.yml b/tests/shared/bind.yml index 8d93039d..c63b2f57 100644 --- a/tests/shared/bind.yml +++ b/tests/shared/bind.yml @@ -57,7 +57,7 @@ tests: - contains: values: - Registry rejected bind call - - Localhost bypass was used but failed + - Localhost bypass was used, but failed - title: Bind Call (Activation System localhost bypass) @@ -79,7 +79,7 @@ tests: - contains: values: - Registry rejected bind call - - Localhost bypass was used but failed + - Localhost bypass was used, but failed - title: Rebind Call @@ -122,7 +122,7 @@ tests: ignore_case: True values: - Registry rejected rebind call - - Localhost bypass was used but failed + - Localhost bypass was used, but failed - title: Unbind Call @@ -163,4 +163,4 @@ tests: ignore_case: True values: - Registry rejected unbind call - - Localhost bypass was used but failed + - Localhost bypass was used, but failed diff --git a/tests/shared/codebase.yml b/tests/shared/codebase.yml index 63265fe9..ec7c964e 100644 --- a/tests/shared/codebase.yml +++ b/tests/shared/codebase.yml @@ -155,7 +155,7 @@ tests: - error: True - contains: values: - - 'The specified aciton requires one of the --component, --objid, --bound-name options' + - 'The specified action requires one of the --component, --objid, --bound-name options' - title: No Class Load diff --git a/tests/spring/spring.yml b/tests/spring/spring.yml new file mode 100644 index 00000000..f57ebf36 --- /dev/null +++ b/tests/spring/spring.yml @@ -0,0 +1,22 @@ +tester: + title: Spring Remoting Tests + description: |- + 'Launches some tests for Spring Remoting based endpoints' + + id: '005' + groups: + - spring + + +containers: + - name: 'spring-remoting-server' + image: 'ghcr.io/qtc-de/remote-method-guesser/spring-remoting-server:1.0' + volumes: + - '${volume}:${volume-d}' + aliases: + DOCKER-spring-remoting-server-IP: DOCKER-IP + DOCKER-spring-remoting-server-GATEWAY: DOCKER-GW + + +testers: + - ./tests/* diff --git a/tests/spring/tests/call.yml b/tests/spring/tests/call.yml new file mode 100644 index 00000000..0c4679d2 --- /dev/null +++ b/tests/spring/tests/call.yml @@ -0,0 +1,60 @@ +tester: + title: Calling Tests + description: |- + Perform tests for calling methods on Spring Remoting + + id: '005-003' + groups: + - call + id_pattern: '005-003-{:03}' + + +variables: + file-exec: spring-remoting-execute.txt + file-system: spring-remoting-system.txt + + +tests: + - title: Execute Call + description: |- + Invokes the execute function on the spring remoting interface + + command: + - rmg + - call + - ${TARGET-SPRING} + - '"touch ${volume-d}/${file-exec}"' + - --bound-name + - spring-remoting + - --signature + - 'void execute(String dummy)' + - ${OPTIONS} + + validators: + - error: False + - file_exists: + cleanup: True + files: + - '${volume}/${file-exec}' + + - title: System Call + description: |- + Invokes the system function on the spring remoting interface + + command: + - rmg + - call + - ${TARGET-SPRING} + - '"touch", new String[] { "${volume-d}/${file-system}" }' + - --bound-name + - spring-remoting + - --signature + - 'void system(String command, String[] args)' + - ${OPTIONS} + + validators: + - error: False + - file_exists: + cleanup: True + files: + - '${volume}/${file-system}' diff --git a/tests/spring/tests/enum.yml b/tests/spring/tests/enum.yml new file mode 100644 index 00000000..50b34687 --- /dev/null +++ b/tests/spring/tests/enum.yml @@ -0,0 +1,35 @@ +tester: + title: Enumeration Tests + description: |- + Perform tests on the enum action for Spring Remoting + + id: '005-001' + groups: + - enum + id_pattern: '005-001-{:03}' + + +tests: + - title: RMI Server Enumeration + description: |- + Plain enum call. This call should detect that spring remoting is used + and should display the actual implemented interface instead of the + Spring Remoting interface + + command: + - rmg + - enum + - ${TARGET-SPRING} + - ${OPTIONS} + + validators: + - error: False + + - contains: + description: |- + Check whether Spring Remoting is detected and whether the real + interface class is correctly dispalyed + values: + - 'spring-remoting' + - 'org.springframework.remoting.rmi.RmiInvocationHandler (known class: Spring RmiInvocationHandler)' + - 'Spring Remoting Interface: eu.tneitzel.rmg.springremoting.ServerOperations (unknown class)' diff --git a/tests/spring/tests/guess.yml b/tests/spring/tests/guess.yml new file mode 100644 index 00000000..6ce81a1b --- /dev/null +++ b/tests/spring/tests/guess.yml @@ -0,0 +1,31 @@ +tester: + title: Guessing Tests + description: |- + Test method guessing on Spring Remoting endpoints + + id: '005-002' + groups: + - guess + id_pattern: '005-002-{:03}' + + +tests: + - title: Plain Guess + description: |- + Perform method guessing on a Spring Remoting endpoint + + command: + - rmg + - guess + - ${TARGET-SPRING} + - --verbose + - ${OPTIONS} + + validators: + - error: False + - contains: + values: + - '[ spring-remoting ] HIT! Method with signature ??? system(String dummy, String[] dummy2) exists!' + - '[ spring-remoting ] HIT! Method with signature ??? execute(String dummy) exists!' + - '--> ??? system(String dummy, String[] dummy2)' + - '--> ??? execute(String dummy)' diff --git a/tests/tricot.yml b/tests/tricot.yml index de63a113..faf25cbd 100644 --- a/tests/tricot.yml +++ b/tests/tricot.yml @@ -18,7 +18,7 @@ tester: ge: 1.12.0 variables: - rmg: rmg-4.4.1-jar-with-dependencies.jar + rmg: rmg-5.0.0-jar-with-dependencies.jar volume: /tmp/rmg-tricot-test/ volume-d: /rce/ codebase-class: CodebaseTest @@ -29,6 +29,9 @@ variables: - ${DOCKER-IP} - 1090 - --ssl + TARGET-SPRING: + - ${DOCKER-IP} + - 1099 TARGET-ACT: - ${DOCKER-IP} - 1098 @@ -59,3 +62,4 @@ testers: - ./jdk9/jdk9.yml - ./jdk11/jdk11.yml - ./generic/generic.yml + - ./spring/spring.yml diff --git a/tests/utils/PluginTest.java b/tests/utils/PluginTest.java index f13f7616..825f828b 100644 --- a/tests/utils/PluginTest.java +++ b/tests/utils/PluginTest.java @@ -2,67 +2,78 @@ import java.util.HashMap; import java.util.Collection; -import de.qtc.rmg.operations.Operation; -import de.qtc.rmg.plugin.DefaultProvider; -import de.qtc.rmg.plugin.IResponseHandler; -import de.qtc.rmg.plugin.IPayloadProvider; -import de.qtc.rmg.plugin.IArgumentProvider; +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 { - +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) { + if (responseObject instanceof Collection) + { + for (Object o: (Collection)responseObject) + { System.out.println(o.toString()); } + } - } else if(responseObject instanceof Map) { - + else if (responseObject instanceof Map) + { Map map = (Map)responseObject; - for(Object o: map.keySet()) { + 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) { + else if (responseObject.getClass().isArray()) + { + for(Object o: (Object[])responseObject) + { System.out.println(o.toString()); } + } - } else { - + else + { System.out.println(responseObject.toString()); } } public Object[] getArgumentArray(String argumentString) { - if( argumentString.equals("login") ) { - + if (argumentString.equals("login")) + { HashMap credentials = new HashMap(); credentials.put("username", "admin"); credentials.put("password", "admin"); return new Object[]{credentials}; + } - } else { + else + { return defProv.getArgumentArray(argumentString); } } public Object getPayloadObject(Operation action, String name, String args) { - if( name.equals("custom") ) { + if (name.equals("custom")) + { return defProv.getPayloadObject(action, "CommonsCollections6", args); + } - } else { + else + { return defProv.getPayloadObject(action, name, args); } }