This blog post examines the recent MySQL® ransomware attacks, and what open source database security best practices could have prevented them.
Unless you’ve been living under a rock, you know that there has been an uptick in ransomware for MongoDB and Elasticsearch deployments. Recently, we’re seeing the same for MySQL.
Let’s look and see if this is MySQL’s fault.
Let’s briefly touch on how Elasticsearch and MongoDB became easy targets…
Elasticsearch® does not implement any access control: neither authentication nor authorization. For this, you need to deploy the Elastic’s shield offering. As such, if you have an Elasticsearch deployment that is addressable from the Internet, you’re asking for trouble. We see many deployments have some authentication around their access, such as HTTP Basic Auth – though sadly, some don’t employ authentication or network isolation. We already wrote a blog about this here.
MongoDB (< 2.6.0) does allow for access control through account creation. It binds to 0.0.0.0 by default (allowing access from anywhere). This is now changed in /etc/mongod.conf in versions >= 2.6.0. Often administrators don’t realize or don’t know to look for this. (Using MongoDB? My colleague David Murphy wrote a post on this issue here).
We began to see incidents where both Elasticsearch and MongoDB had their datasets removed and replaced with a README/note instructing the user to pay a ransom of 0.2BTC (Bitcoin) to the specified wallet address (if they wanted their data back).
So is this latest (and similar) attack on MySQL MySQL’s fault? We don’t think so. MySQL and Percona Server® for MySQL by default do not accept authentication from everywhere without a password for the root user.
Let’s go over the various security options MySQL has, and describe some other best practices in order to protect your environment.
MySQL currently still binds to 0.0.0.0 (listen to all network interfaces) by default. However, Percona Server for MySQL and Percona XtraDB Cluster have different defaults, and only bind on 127.0.0.1:3306 in its default configuration (Github pull request).
Recall, if you will, CVE-2012-2122. This ALONE should be enough to ensure that you as the administrator use best practices, and ONLY allow access to the MySQL service from known good sources. Do not setup root level or equivalent access from any host ( % indicates any host is allowed). Ideally, you should only allow root access from 127.0.0.1 – or if you must, from a subset of a secured network (e.g., 10.10.0.% would only allow access to 10.10.0.0/24).
Also, does the MySQL database really need a publicly accessible IP address? If you do have a valid reason for this, then you should firewall port 3306 and whitelist access only from hosts that need to access the database directly. You can easily use iptables for this.
MySQL DOES NOT by default create accounts that can be exploited for access. This comes later through an administrator’s lack of understanding, sadly. More often than not, the grant will look something like the following.
|
1 |
GRANT ALL PRIVILEGES TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; |
You may scoff at the above (and rightly so). However, don’t discount this just yet: “123456” was the MOST USED password in 2016! So it’s reasonable to assume that somewhere out there this is a reality.
You can deploy max_connection_errors with a suitably low value to help mitigate a direct attack. This will not prevent a distributed attack, where many thousands of hosts are used. Network isolation is the only way to ensure your mitigation against this attack vector.
Since MySQL 5.7, a random password is generated for the only root user ( root@localhost) when you install MySQL for the first time. That password is then written in the error log and has to be changed. Miguel Ángel blogged about this before.
MySQL 5.7.17 introduced a new open source plugin called Connection Control. When enabled, it delays the authentication of users that failed to login by default more than three times. This is also part as of Percona Server for MySQL 5.7.17.
Here’s an example where the 4th consecutive try caused a one-second delay (default settings were used):
|
1 |
$ time mysql -u bleh2 -pbleh<br>ERROR 1045 (28000): Access denied for user 'bleh2'@'localhost' (using password: YES)<br>real 0m0.009s<br><br>$ time mysql -u bleh2 -pbleh<br>ERROR 1045 (28000): Access denied for user 'bleh2'@'localhost' (using password: YES)<br>real 0m0.008s<br><br>$ time mysql -u bleh2 -pbleh<br>ERROR 1045 (28000): Access denied for user 'bleh2'@'localhost' (using password: YES)<br>real 0m0.008s<br><br>$ time mysql -u bleh2 -pbleh<br>ERROR 1045 (28000): Access denied for user 'bleh2'@'localhost' (using password: YES)<br>real 0m1.008s<br><br>mysql> SELECT * FROM INFORMATION_SCHEMA.CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS;<br>+---------------------+-----------------+<br>| USERHOST | FAILED_ATTEMPTS |<br>+---------------------+-----------------+<br>| 'bleh2'@'localhost' | 4 |<br>+---------------------+-----------------+<br>1 row in set (0.01 sec) |
MySQL 5.6.6 and later versions also ship with a password validation plugin, which prevents creating users with unsafe passwords (such as 123456) by ensuring passwords meet certain criteria: https://dev.mysql.com/doc/refman/5.7/en/validate-password-plugin.html
In order to get stung, one must ignore the best practices mentioned above (which in today’s world, should take some effort). These best practices include:
Hopefully, these are helpful security tips for MySQL users. Comment below!
Resources
RELATED POSTS