Harden Wordpress using database permissions2013-01-14
Here is a small idea that I would like to throw into the world: most web applications use only one database user for most operations (installation, administration, common usage). Couldn't we harness the database to protect a bit your data?
This is how you could do it:
- Create one user (called 'user') with full privileges on the database
- Create another user with no privileges (let's call him 'read')
- Create a copy of wp-config.php that you will name wp-config-admin.php
- Write the 'read' credentials in the wp-config.php and the normal credentials in wp-config-admin.php (don't forget to use different auth, secure auth, logged in and nonce keys)
- Create a copy of wp-load.php that you will name wp-load-admin.php
- Replace in wp-load-admin.php the reference to wp-config.php by wp-config-admin.php
- Replace in wp-login.php and wp-admin/* the references to wp-load.php by wp-load-admin.php
- Now, you can use the admin interface, create posts, etc.
- Grant some permissions to the 'read' database user: GRANT SELECT ON `db`.* TO 'read'; GRANT INSERT, UPDATE ON `db`.`wp_comments` TO 'read';
That was a bit of work, but not that hard! So, what did we do here? We created a user for the admin interface with full privileges on the database (create/update posts, change the taxonomy, approve the comments, etc) and another one for the front end interface, with only read privileges on all tables (that bothers me too, but read on).
This means that SQL injections, either in plugins or in Wordpress code (out of the admin panel) will be much harder to implement with this setup. Beware of the custom tables for some plugins. Those will require specific permissions. Depending on the plugin, some could be read only for common usage.
That's nice, but not enough in my opinion. As I said, the full select permission for the 'read' user bothers me. Couldn't we restrict a bit the permissions on wp_users? Some of the columns are needed, but do we need to access the user_pass column? Also, the "ALL PRIVILEGES" for 'user' is a bit too much. Do we really use the "FILE" privilege (out of SQL injections :D)?
Without further ado, here are the SQL commands you should use:
GRANT SELECT, INSERT, UPDATE ON `db`.`wp_comments` TO 'read';
GRANT SELECT ON `db`.`wp_commentmeta` TO 'read';
GRANT SELECT ON `db`.`wp_links` TO 'read';
GRANT SELECT ON `db`.`wp_options` TO 'read';
GRANT SELECT ON `db`.`wp_term_taxonomy` TO 'read';
GRANT SELECT ON `db`.`wp_usermeta` TO 'read';
GRANT SELECT ON `db`.`wp_terms` TO 'read';
GRANT SELECT ON `db`.`wp_term_relationships` TO 'read';
GRANT SELECT ON `db`.`wp_postmeta` TO 'read';
GRANT SELECT ON `db`.`wp_posts` TO 'read';
GRANT SELECT (user_activation_key, id, user_login, user_nicename, user_status, user_url, display_name, user_email, user_registered) ON `db`.`wp_users` TO 'read';
REVOKE ALL PRIVILEGES ON `db`.* from 'user';
GRANT SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, INDEX ON `db`.* TO 'user';
With these commands, 'user' can only manipulate tables. If you're an evil DBA, you can even revoke the "CREATE, DROP, ALTER" permission after install, and reactivate them only for upgrades or plugin installation. The 'read' user has the same permissions as before on wp_comments, has "SELECT" on all tables except the wp_users. For wp_users, we grant "SELECT" on all columns except the user_pass one.
Thanks to this configuration, even a SQL injection in a plugin will not reach the password hashes! We also removed dangerous permissions like "FILE". I'd like to prevent timing attacks like "SELECT BENCHMARK(5000000,ENCODE('MSG','by 5 seconds'));" but i did not figure out what is the right syntax for this (I tried variations around: "revoke execute on function benchmark from read", without result).
Thankfully, WordPress mostly works with this configuration, and I think that a lot of other applications could be protected like this. Imagine: you could grant insert but not select on the credit card table in an e-commerce application, and process transactions with a background task with the right permissions.
Database privileges are indeed a powerful tool to protect your code from SQL injections. They might require some architectural changes, but the profits can be huge for your security.