Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Do Not GRANT ALL PRIVILEGES to Your Production Users

DZone's Guide to

Do Not GRANT ALL PRIVILEGES to Your Production Users

What started as a problem involving connecting a user to a MySQL server quickly became a Google nightmare. Here is a lesson in production-grade database security.

· Database Zone ·
Free Resource

RavenDB vs MongoDB: Which is Better? This White Paper compares the two leading NoSQL Document Databases on 9 features to find out which is the best solution for your next project.  

Thanks to the generous contributions of Timur Shaidullin, jOOQ 3.11 will now support GRANT and REVOKE statements through #6812. While implementing integration tests for these new features, I had researched the different ways how these statements work on a variety of databases, and the good news is, they’re all mostly quite standardized (in fact, they’re even part of the SQL standard).

The less good news is that a lot of people do not seem to care about security – at all!

Granted (great pun!) MySQL seems to be lacking a feature here. When I create a new user:

-- Obviously, you will choose a better password
CREATE USER 'NO_RIGHTS'@'%' IDENTIFIED BY 'NO_RIGHTS';


… then this user can connect to the server, but not to any databases yet. From JDBC, we most often use the connection string:

jdbc:mysql://host/database


After all, we don’t just want to connect to a server, but also to a database. This is not allowed, and that’s a reasonable default, of course:

Caused by: java.sql.SQLSyntaxErrorException: Access denied for user 'NO_RIGHTS'@'%' to database 'test'
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:116)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:853)
at com.mysql.cj.jdbc.ConnectionImpl.(ConnectionImpl.java:440)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:221)
at org.jooq.test.jOOQAbstractTest.getConnection1(jOOQAbstractTest.java:1132)
at org.jooq.test.jOOQAbstractTest.getConnection0(jOOQAbstractTest.java:1064)
... 31 more


No doubt about that. But how can I grant the right to connect to this database? There is no such grant in the documentation:

https://dev.mysql.com/doc/refman/8.0/en/grant.html

That’s unfortunate, because in order to start working with the database, the first thing I’d like to do is something like the hypothetical:

GRANT CONNECT ON test TO 'NO_RIGHTS'@'%';


From then on, I could start working and add more and more grants, step by step, depending on what I would like to allow the user NO_RIGHTS to do on the database. I have created a feature request for this: https://bugs.mysql.com/bug.php?id=89030

A Workaround

In fact, any grant to any object inside of the database implicitly grants the “CONNECT” privilege to this user. I could, for instance, do this:

GRANT SELECT ON test.bank_account_transactions TO 'NO_RIGHTS'@'%';


But I don’t want to do that! That’s already much too big of a GRANT for the fact that I don’t actually want this user to be able to do anything at this point.

Here’s a more viable (but ugly) workaround:

-- Create a dummy view that is never going to be used:
CREATE VIEW v_unused AS SELECT 1;

-- Now grant showing (not selecting) this view to the user:
GRANT SHOW VIEW ON test.v_unused TO 'NO_RIGHTS'@'%';


Among all the possible grants, that’s about the most harmless I could find that will now allow this user to connect to the test database (and to show this view):

SHOW TABLES;


Yielding:

Tables_in_test
--------------
v_unused


Note, with my security background and being the pessimist I am, I don’t even grant the SELECT privilege on this view, but just the SHOW VIEW privilege.

I could possibly live with that. Or if I cannot create any view myself, perhaps I could grant “SHOW VIEW” of all views. I don’t like that thought, but it seems to be about the least intrusive privilege to get that implied “CONNECT” privilege.

What Does the Internet Recommend?

I was obviously Googling for this topic. The best way to Google for this is by Googling the JDBC error message:

Access denied for user ‘NO_RIGHTS’@’%’ to database ‘test’

Because that’s what people do, right? Google error messages. I would have expected tons of advice how to solve that particular problem. The problem of getting the “CONNECT” privilege, and the “CONNECT” privilege only

Here are some of the first results, which all shocked me completely.

1. The MySQL manual

At first, it recommends this:

mysql> CREATE USER 'finley'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'finley'@'localhost'
    ->     WITH GRANT OPTION;


OK, perhaps not the best first example, I mean I really don’t trust this guy finley. But OK, it’s the manual, and it later proceeds to showing more restrictive GRANT options.

2. Some random forum

Plenty of talk about:

CREATE DATABASE `zabbix_db`;
GRANT ALL PRIVILEGES ON `zabbix_db`.* TO `zabbix_user`@'localhost' IDENTIFIED BY 'XXXXXXXXX';
FLUSH PRIVILEGES;


Great, so now we know that this particular user can do everything on this forum. Excellent. Let’s find a SQLi vulnerability somewhere.

3. A random ServerFault question

Two answers suggesting:

CREATE USER 'username'@'192.168.22.2' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON databasename.* TO 'username'@'192.168.22.2';


And also

GRANT ALL on database.* to 'user'@'localhost' identified by 'paswword';


Great advice, folks!

4. Another random forum

… where the user asking the question had already followed one of the previous forums’ advice:

GRANT USAGE ON *.* TO 'someusr'@'localhost'
GRANT ALL PRIVILEGES ON `qorbit_store`.* TO 'someusr'@'localhost'


Great, so qorbit_store also has a user with total privileges.

5. A random GitHub issue

So… this is the Travis CI server itself, isn’t it?

mysql -u root -e "CREATE DATABASE mydb;"
mysql -u root -e "GRANT ALL PRIVILEGES ON mydb.* TO 'travis'@'%';";


What are you folks thinking??

6. A random MariaDB forum

I am trying to get some software installed but am running into problems. […] I used the command “grant all privileges on newdb.* to sam@localhost;” […]

True to the idea, let’s just hammer out commands until this dang thing works. The Unix equivalent would be:

chmod -R 777 *


Solves all problems right?

7. Another random forum

[…] you may need to give your MySQL user proper permissions. Log in to MySQL as the MySQL root user and issue these commands:

GRANT ALL PRIVILEGES ON database_name TO user@host IDENTIFIED BY ‘password’;
 FLUSH PRIVILEGES;

“Proper permissions” – GRANT ALL is never proper!

To be fair, this particular forum then proceeds advising:

If you want (or need) to be more restrictive with database permissions: You will need to at least grant the ALTER, CREATE, DELETE, INSERT, SELECT, and UPDATE permissions to your database user. ALTER and CREATE permissions are only needed for installing and upgrading Geeklog, as well as for installing plugins and other add-ons.

But as we’ve seen before, people don’t read all that advice, they just use the first thing that works.

8. Another random forum, on Drupal

This is a true gem. Not only GRANT ALL, but also an unredacted password. I redacted it, but you can easily look this up if you want some data exchange business with swisha swisha:

define('MYSQL_HOST','localhost');
define('MYSQL_USER','swhisa_swhisa');
define('MYSQL_PASS','12345678'); // Redaction mine
define('MYSQL_DB','swhisa_swhisadb');
if(!mysql_connect (MYSQL_HOST, MYSQL_USER, MYSQL_PASS)){die (mysql_error()); } mysql_select_db(MYSQL_DB);

$grt = "GRANT ALL ON *.* TO 'swhisa_swhisa'@'%'";
mysql_query($grt) or die(mysql_error());


“GRANT ALL” and a public password...

In case you wondered, this is me losing faith in humanity. Not a single post recommended first to grant the minimal rights necessary for the user. All of them (including the official documentation) just defaulted to granting everything.

Wrap Up

For crying out loud. I Googled for an error message about being unable to connect, and almost every single answer Google gave me was someone recommending to just GRANT ALL. That’s like googling for an alternative to tacos and being recommended to join the NASA/SpaceX missions to Mars because of the excellent astronaut food.

Of course, as a developer, we like to GRANT ALL. That’s convenient. If we don’t ship to production but just want to play around with some features, we might not need security.

But the fact is that if the readers of such posts are never exposed to even the mere idea of thinking about a security model and user design where not every user can access (let alone modify, drop, shutdown) every resource, then these readers are simply going to ship GRANT ALL to production.

SQL injection is a serious threat on its own, but if the database user that exposes the vulnerability has complete privileges on your database, then you’re doubly doomed.

Always start with users that have no rights – then add rights as you need them, and add them sparingly!

Now go out there and start REVOKING.

Do you pay to use your database? What if your database paid you? Learn more with RavenDB.

Topics:
database ,jooq ,production ,privileges ,database security ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}