Database honeypot by design: Mind-blowing Attack Vector on MySQL Clients

This article was published in “Xakep” #176 magazine, оriginal russian version - PDF
Presentation from defconmoscow - SLIDESHARE
Original version of rogue mysql server - GITHUB
Modern version of rogue mysql server - GITHUB
Thanks to all my collegues from Positive Technologies, who helped me with this research.

Database Honeypot by Design: Mind-blowing Attack Vector on MySQL

A database is a tempting target for a hacker. But in the pursuit of information in a DBMS, the attacker can become a victim themselves. Due to a loophole left by the developer, the client, instead of reading data from the database, can unwittingly transfer an arbitrary file from their system to the server! How is this possible?

SUMMARY FOR THE IMPATIENT

A warm June day before the weekend. Nothing foretold trouble, and then Denis Baranov, together with Mikhail Firstov and Alexey Osipov, discovered a 0-day feature in the implementation of the SQL operator LOAD DATA LOCAL INFILE and wrote an exploit for it.

NOW IN DETAIL

It all started with an amusing hacking quest found on the internet. At one of the levels, it was necessary to influence the connection string to the database. The idea is clear: if you replace the DBMS address, the MySQL client will connect not to the developer’s server, but to a server controlled by the hacker. This means that to bypass authentication, it is enough to raise your own MySQL server and replace the user table with your own. This is not a new topic: you can read more about attacks with connection string changes, for example, on rdot.org. But an idea came up: if a person connects to your server themselves, why not find a way to gain access to the host from which they are connecting?

There is an idea – you need to try it! The wonderful LOAD DATA LOCAL operator immediately came to mind, which allows a database user to read files from their local system and send them to the database server. A convenient feature, so you don’t have to write scripts or upload files to the server. Moreover, there are no elevated privileges, as it is assumed that the client is reading their own files. It would be great to find a way to execute the LOAD DATA LOCAL INFILE command without the client’s intention.

First, we study materials on the topic, for example, on rdot.org. Besides the described tricks, you can also try playing with triggers, but they are useless for select queries.

To solve the problem, you need to imagine the logic of a programmer developing a DBMS. To execute a query, it needs to be parsed. After that, you can execute the necessary commands and return data to the client. Accordingly, a query with the LOAD DATA LOCAL operator must also be parsed somewhere. There are only two options: on the server or on the client, but we immediately discard the second case, as it would be absolutely wrong from an architectural standpoint. So, the operator is processed on the server, after which the client apparently receives a service message, “Give me a file with such and such a name.” The client receives the message and sends the requested data to the server.

And now the main thing! What if, in response to any SQL query from the client, you send a service message, “Give me a file”? :)

PRACTICE

Typically, interaction with the DB looks like this: we authenticate → if the authentication is successful, we send a query → we get a result. The last two steps can be repeated as many times as needed. However, for LOAD DATA LOCAL INFILE, it’s a bit different. Notice the IP addresses and ports from which the requests come:

  1. Suppose we are already authenticated and send the query LOAD DATA LOCAL INFILE “C:\Windows\system32\drivers\etc\hosts” INTO TABLE mysql.test.
  2. The server responds with a packet containing the file name that was sent by the client.
  3. The client sends the file content to the server.

Strange behavior, isn’t it? It immediately stands out that this sequence can be reduced to one step - step 3. Sensing something amiss, we decided to investigate. After fiddling with proxy settings, we got an unexpected result.

If we send packet #2 in response to any MySQL client’s query, the client will immediately send us the file content, regardless of the initial query!

The server asks for a file? Give it the file!

READ THE FILE! PLEASE

A small exploit called rogue_mysql was hastily written in Python. Its entire functionality was that it accepted connections from a MySQL client (regardless of the login and password used for the connection) and responded to any client query with a request to read a specific file. The interaction looks like this:

  1. The client executes the query SELECT * FROM mysql.user (1).
  2. The server responds: “Instead, read your file c:/boot.ini” (2).
  3. Read the file? Okay, here you go (3).

As a result, a simple Python script pretending to be a MySQL server can read any file from the connecting client (in this case, boot.ini).

Isn’t that wonderful?

IN THE WILD

Launch method: python rogue_mysql.py. By default, a random file from the list at the beginning of the script is read.
Where can it be useful? For example, now the installation scripts for WordPress or phpMyAdmin with non-working MySQL databases can be useful. We connect to our server and get a full-fledged file reader in the context of the vulnerable client. Below is a small example for clarity:

1
2
3
<?php
$conn = mysql_connect($_GET['mysql_host_port'], 'root', '12345'); mysql_query('SELECT * FROM mysql.user');
?>

Another option: you can implement a small honeypot, where files will be read from the brute forcer/attacker’s server. This can generally turn into a fun experiment with interesting results. For example, by leaving the rogue_mysql.py script running, we collected a fair amount of hosts files belonging to our servers from which we tested brute force :).

A LITTLE BIT OF TAR

The vulnerability is interesting, but unfortunately (or fortunately?), not all MySQL clients are compiled with the flag that allows the LOAD DATA LOCAL feature by default. This is a serious limitation (the guys were very upset when they couldn’t exploit my GUI client on Mac or the standard MySQL client running on Ubuntu; apparently, they won’t be invited to the PT office again. — Editor’s note).

You can determine if the client supports this feature from the first packet sent to the server by the client, in the Server Greeting section. An example of data from such a packet is shown in the figure.

INSTEAD OF A CONCLUSION

Truth be told, the LOAD DATA LOCAL operator is already an interesting thing that has helped us more than once during penetration tests, even before we found the possibility of a reverse attack.

After digging into the documentation a bit, we found that the developers are partially aware of this feature. It is stated on the official website that, theoretically, a third party can intervene between the MySQL client and server and modify the LOAD DATA LOCAL query, resulting in a completely different file being written to the database. But such a situation is unlikely. However, what is really possible is the honeypot concept described today.

As you can see, the topic of attacking clients from the server side is not yet fully disclosed. It is quite possible to find similar amusing features for other services available on other ports. Remember, hacker: if you connect to a foreign server, be careful! The legal owner of the resource can attack you too!