In this post, we’ll cover Percona’s thoughts about the current MySQL community discussion happening around MySQL LOCAL INFILE security issues.
This post is released given the already public discussion of this particular issue, with the exploitation code currently redacted to ensure forks of MySQL client libraries have sufficient time to implement their response strategies.
This post has been updated to now include previously redacted content, in line with responsible disclosure sufficient time has passed to allow forks to update and get those updates out for circulation.
MySQL’s LOCAL INFILE feature is fully documented by Oracle MySQL, and there is a legitimate use for the LOCAL INFILE feature to upload data to a MySQL server in a single statement from a file on the client system.
However, some MySQL clients can be coerced into sending contents local to the machine they are running upon, without having issued a LOCAL INFILE directive. This appears to be linked to how Adminer php web interface was attacked to point to a MALICIOUSLY crafted MySQL service to extract file data from the host on which Adminer was deployed. This malicious “server” has, it would appear, existed since early 2013.
The attack requires the use of a malicious/crafted MySQL “server”, to send a request for the file in place of the expected response to the SQL query in the normal query response flow.
IF however the client checks for the expected response, there is no file ex-filtration without further additional effort. This was noted with Java & ProxySQL testing, as a specific response was expected, and not sending the expected response would cause the client to retry.
I use the term “server” loosely here ,as often this is simply a service emulating the MySQL v10 protocol, and does not actually provide complete MySQL interaction capability—though this is theoretically possible, given enough effort or the adaption of a proxy to carry out this attack whilst backing onto a real MySQL server for the interaction capability.
For example, the “server” always responds OK to any auth attempt, regardless of credentials used, and doesn’t interpret any SQL sent. Consequently, you can send any string as a query, and the “server” responds with the request for a file on the client, which the client dutifully provides if local_infile is enabled.
There is potential, no doubt, for a far more sophisticated “server”. However, in my testing I did not go to this length, and instead produced the bare minimum required to test this theory—which proved to be true where local_infile was enabled.
The following MySQL clients were tested via their respective docker containers; and default configurations, the bash script which orchestrated this is as follows: <REDACTED>
This tests the various forks of the MySQL client; along with some manual testing the results were:
There are many more clients out there ranging from protocol compatible implementations to wrappers of the underlying c library.
Your own research will ensure you are taking appropriate measures should you choose/need to mitigate this risk in your controls.
This is a particularly tricky issue to correct in code, as the MySQL client needs to be aware of a LOAD LOCAL INFILE SQL statement getting sent. MariaDB’s proposed path implements this. Even then, if a stored procedure issues a file request via LOAD LOCAL INFILE..., the client has no awareness of this even being needed until the packet is received with the request, and local_infile can be abused. However, the intent is to allow the feature to load data, and as such DBAs/Admins should seek to employ compensating controls to reduce the risk to their organization:
mysqli_options($conn, MYSQLI_OPT_LOCAL_INFILE, false); failed to mitigate this in testing, YMMV (Your Mileage May Vary). Here I provide an example “FAST” format rule for your IDS/IPS system;
Note however YMMV; this works with Snort, Suricata, and _may_ work with Zeek (formerly Bro), OSSEC, etc. However, please test and adapt as needed;
alert tcp any any <> any any (msg: “MySQL LOCAL INFILE request packet detected”; “content:”|00 00 01 FB|”; rawbytes)
Note this is only an example, this doesn’t detect any packets flowing over TLS connections.
If you are running an Intrusion Prevention System (IPS), you should change the rule action from alert to drop.
Here the rule is set to any any as an adversary may wish to not use 3306 in an attempt to avoid detection you can of course change this as desired to suit your needs.
You must also assess if your applications are running local_infile legitimately and conduct your own threat modeling as well as impact analysis, prior to implementing such a rule.
Note increasing the “noise” threshold for your team, will likely only result in your team becoming desensitized to the “noise” and potentially missing an important alert as a result.
For example, you could modify the left and right side any any, to be anything not in your internal network range communicating to anything not in your internal network range:
alert tcp 192.168.1.0/24 any <> !192.168.1.0/24 any (msg:”MySQL LOCAL INFILE request packet detected”; “content:”|00 00 01 FB|”; rawbytes)
Adapting to your environment is key for this IDS rule to be effective.
As noted this issue is already being publicly discussed, as such I add links here to sources relevant to this discussion and exploitation.
Here I am using wireshark to show the tcp communication flow client to MySQL ‘server’, here the ‘server’ is of course malicious and set to sent the file request on each receipt of ‘Request Query’: as can be seen here the ‘server’ masquerades as a 5.1.66 MySQL server running on Debian squeeze.

Now we jump to the malicious response packet, sent in reply to the earlier ‘Request Query’; which through legitimate use of SQL where LOCAL INFILE is issued this request packet would be the expected response from the ‘server’. In this case however the ‘server’ is requesting the file: /proc/self/environ this file can contain a wealth of information including anything you may have stored in an environment variable, in practise this can be set to any full filepath.
I’ve highlighted the two key parts of this packet, the tail of mysql.packet_length (blue) which heads the request and the file path being requested (red).
Here the client responds with the content of the requested file path, and displays no indication the file content has been sent to the MySQL ‘server’.
As shown here the complete TCP conversation flow between the MySQL binary client and MySQL ‘server’ (content has been redacted).
Note the SQL sent was I AM MYSQL BINARY, this is not valid SQL; however this also demonstrates that the MySQL client does not parse SQL before sending it to the ‘server’.
If you’re one whom enjoys CTF’s (Capture the flag) challenges, you may find a version of the above here: https://github.com/Oneiroi/ctf/tree/master/pcap/mysql/data_exfil this includes a pcap file showing a similar packet flow.
I have shared my modified version of the rogue mysql server on Github here: https://github.com/Oneiroi/Rogue-MySql-Server/tree/local_infile_abuse_test some minor edits were required to make functional (there are ~40 some forks of the original project on GH, some with additional functionality)
This assessment was not a single person effort, here I would like to link to and give thanks where appropriate to the following individuals whom have helped with this investigation:
Willem de Groot – For sharing insights into the Adminer exploitation and for graciously responding to an inquiry from myself (this helped me get the PoC working, thank you).
Gifts – original author of evil mysql server implementation (in 2013!), from which I was able to adapt to function for this investigation.
Ceri Williams – for helping me with proxySQL testing.
Marcelo Altman – for discussing MySQL protocol in depth.
Sergei Golubchik – for responding to my email notice for MariaDB, and implementing a workaround mitigation so quickly, as well providing me with a notice on the Connector/J announcement url.
Peter Zaitsev – for linking me to the original reddit discussion and for feedback.
Resources
RELATED POSTS
I’m happy that this ancient problem is getting proper attention now. Thank you David. A couple of comments:
1) You don’t mention the LOAD XML statement. Is this a mistake or is it more secure for some reason?
2) LOAD DATA is not allowed in stored procedures/functions. And cannot be used as a prepared statement (I mention this because prepared statements can always run in stored procedures).
Hi Federico,
1) The ‘feature abuse’occurs in the client handling of the INFILE packet request from the server, this is made clear in the network flows which unfortunately are redacted at this moment in time, once we’ve had response from the one fork whim has not provided indicate they have made an appropriate response or Feb 28th is reached I will update this post with the redacted detail in line this will provide more clarity on where the abuse / exploitation occurs. As the SQL issues by the client is ignored, the client need not actually send a LOAD XML / LOAD DATA … the malicious server will always just respond with the file request packet and where local_infile is enabled the client will respond with the file contents if accessible. I’ve taken this approach to ensure responsible disclosure practises are followed the rest of this content however is already in the public domain, hence this post has been released with this detail adding testing and mitigation recomendations.
2) This was added as a consideration based on feedback from one of our dev leads, this consideration was for the fix only I’ll follow up with them and query this. As noted in post and in response above, the exploitation ocurrs when the client has local_infile enabled, it will always respond with the file contents if accessible; this allows the ‘server’ to send the packet in responds to any SQL sent in testing.
This seems to paint Percona in a particularly good light, but ultimately PXC / Percona Server seems to be in the same situation as upstream MySQL. If you disable local-infile at the client level you’re fine, but once local_infile is enabled it seems just as susceptible. The default is off in *some* pre-compiled distribution releases but not if you compile from source or apparently you use the ubuntu packages. Is MariaDB much different if one takes care to disable local_infile?
There is a bug in the rogue_mysql_server.py implementation which underspecifies the salt in the handshake packet which may explain this discrepancy.
Hi Andrew,
Percona Server being downstream of Oracle MySQL (as well as PXC) it makes sense that this would mirror upstream by default (and yes if you enabled local_infile and re-run the tests they are vulnerable to this issue, however what I have highlighted in this post is the default OTB state of each fork and version tested).
There’s no intent to paint Percona Server nor PXC in a positive light, had they been vulnerable by default this would have been noted here regardless, we do not (and should not) show bias to even our own products at Percona.
The difference here was MariaDB was allowing local_infile by default, there should now be workaround fix in place such that if there is no SQL statement issues which would require the server to send the file request packet, if a packet is received it is instead ignored (as noted from MariaDB to myself in email).
w.r.t rogue_mysql_server.py I’d suggest such discrepancies were intentional to ensure only those whom understand the protocol can re-arm the rogue_mysql_server to achieve the desired result, as stated I am waiting on response from a fork at this time and can not go into much detail at the moment on this.
w.r.t the disabling of local_infile; and the vulnerabilities of enabling it again, that is true and for the record I personally think MariaDB has the best proposed workaround here in having the client respond only if SQL is known to have been issued with LOAD … and ignore all other requests if this has not been the case.
_if_ you compile from source and ensure that local_infile support is disabled, and this fits your use case in your deployments, excellent; of course this will protect against this particular issue.
However you have to weigh this against maintaining your own builds for future releases which patch other security vulnerabilities.
The analysis here has been intended to cover all forks, not to paint one or another more or less favourably by any means.