{"id":3667,"date":"2011-06-22T08:39:01","date_gmt":"2011-06-22T06:39:01","guid":{"rendered":"http:\/\/code.openark.org\/blog\/?p=3667"},"modified":"2011-06-22T08:39:01","modified_gmt":"2011-06-22T06:39:01","slug":"mysql-security-inconsistencies","status":"publish","type":"post","link":"https:\/\/code.openark.org\/blog\/mysql\/mysql-security-inconsistencies","title":{"rendered":"MySQL security: inconsistencies"},"content":{"rendered":"<p>Doing some work with MySQL security, I&#8217;ve noticed a few inconsistencies. They&#8217;re mostly not-too-terrible for daily work, except they get in my way right now.<\/p>\n<h4>The ALL PRIVILEGES inconsistency<\/h4>\n<p>The preferred way of assigning account privileges in MySQL is by way of using <a href=\"http:\/\/dev.mysql.com\/doc\/refman\/5.1\/en\/grant.html\">GRANT<\/a>.<\/p>\n<p>With <strong>GRANT<\/strong>, one assigns one or more privileges to an account, such as <strong>SELECT<\/strong>, <strong>UPDATE<\/strong>, <strong>ALTER<\/strong>, <strong>SUPER<\/strong> ,etc. Sometimes it makes sense for an account to have complete control over a domain. For example, the <strong>root<\/strong> account is typically assigned with all privileges. Or, some user may require all possible privileges on a certain schema.<\/p>\n<p>Instead of listing the entire set of privileges, the <strong>ALL PRIVILEGES<\/strong> meta-privilege can be used. There is a fine issue to notice here; typically this is not a problem, but I see it as a flaw. Assume the following account:<\/p>\n<blockquote>\n<pre>root@mysql-5.1.51&gt; GRANT <strong>ALL PRIVILEGES<\/strong> ON world.* TO 'world_user'@'localhost';\r\n\r\nroot@mysql-5.1.51&gt; SHOW GRANTS FOR 'world_user'@'localhost';\r\n+---------------------------------------------------------------+\r\n| Grants for world_user@localhost\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n+---------------------------------------------------------------+\r\n| GRANT USAGE ON *.* TO 'world_user'@'localhost'\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n| GRANT <strong>ALL PRIVILEGES<\/strong> ON `world`.* TO 'world_user'@'localhost' |\r\n+---------------------------------------------------------------<\/pre>\n<\/blockquote>\n<p>This makes sense. We granted <strong>ALL PRIVILEGES<\/strong> and we see that the account is granted with <strong>ALL PRIVILEGES<\/strong>.<\/p>\n<p>Now notice the following:<!--more--><\/p>\n<blockquote>\n<pre>root@mysql-5.1.51&gt; GRANT ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, EXECUTE, INDEX, INSERT, LOCK TABLES, REFERENCES, SELECT, SHOW VIEW, TRIGGER, UPDATE ON `world`.* TO 'other_user'@'localhost';\r\n\r\nroot@mysql-5.1.51&gt; SHOW GRANTS FOR 'other_user'@'localhost';\r\n+---------------------------------------------------------------+\r\n| Grants for other_user@localhost\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n+---------------------------------------------------------------+\r\n| GRANT USAGE ON *.* TO 'other_user'@'localhost'\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n| GRANT <strong>ALL PRIVILEGES<\/strong> ON `world`.* TO 'other_user'@'localhost' |\r\n+---------------------------------------------------------------+<\/pre>\n<\/blockquote>\n<p>I didn&#8217;t <em>ask<\/em> for <strong>ALL PRIVILEGES<\/strong>. I explicitly listed what I thought should be an account&#8217;s privileges. It just so happens that these make for the entire set of privileges available on the schema domain.<\/p>\n<p>You might think this is a nice feature, an ease out MySQL provides with. I do not see it this way.<\/p>\n<p>My preferred way of upgrading MySQL version involves exporting and importing of the GRANTs. That is, I do not dump and load the <strong>mysql<\/strong> system tables, but rather export all the <strong>SHOW GRANTS FOR &#8230;<\/strong> (e.g. with mk-show-grants), then execute these on the new version. This process was extremely useful on upgrades from <strong>5.0<\/strong> to <strong>5.1<\/strong>, where some <strong>mysql<\/strong> system tables were modified.<\/p>\n<p>Now, consider the case where some new MySQL version introduced a new set of privileges. My <strong>&#8216;other_user&#8217;@&#8217;localhost&#8217;<\/strong> was not created with that set of privileges, nor did I intend it to have them. However, when exporting with <strong>SHOW GRANTS<\/strong>, the account is said to have <strong>ALL PRIVILEGES<\/strong>. When executed on the new version, the account will have privileges which I <em>never assigned it<\/em>.<\/p>\n<p>Typically, this is not an issue. I mean, how many times do I assign an account with the entire set of privileges, yet do not intend it to have all privileges? Nevertheless, this makes for an inconsistency. It is unclear, by way of definition, which privileges are assigned to a user, without knowing the context of the version and the set of privileges per version. It makes for an inconsistency when moving between versions. And right now I&#8217;m working on some code which doesn&#8217;t like these inconsistencies.<\/p>\n<h4>The WITH GRANT OPTION inconsistency<\/h4>\n<p>An account can be granted with the <strong>WITH GRANT OPTION<\/strong> privilege, which means the account&#8217;s user can assign her privileges to other accounts. The inconsistency I found is that the <strong>GRANT<\/strong> mechanism is fuzzy with regard to <strong>GRANT OPTION<\/strong>, and falsely presents us with the wrong impression.<\/p>\n<p>Let&#8217;s begin with the bottom line: the <strong>WITH GRANT OPTION<\/strong> can only be set globally for an account-domain combination. Consider:<\/p>\n<blockquote>\n<pre>root@mysql-5.1.51&gt; GRANT INSERT, DELETE, UPDATE ON world.City TO 'gromit'@'localhost';\r\nQuery OK, 0 rows affected (0.00 sec)\r\n\r\nroot@mysql-5.1.51&gt; GRANT SELECT ON world.City TO 'gromit'@'localhost' WITH GRANT OPTION;\r\nQuery OK, 0 rows affected (0.00 sec)\r\n\r\nroot@mysql-5.1.51&gt; SHOW GRANTS FOR 'gromit'@'localhost';\r\n+--------------------------------------------------------------------------------------------------+\r\n| Grants for gromit@localhost\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n+--------------------------------------------------------------------------------------------------+\r\n| GRANT USAGE ON *.* TO 'gromit'@'localhost'\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |\r\n| GRANT <strong>SELECT, INSERT, UPDATE, DELETE<\/strong> ON `world`.`City` TO 'gromit'@'localhost' <strong>WITH GRANT OPTION<\/strong> |\r\n+--------------------------------------------------------------------------------------------------+\r\n<\/pre>\n<\/blockquote>\n<p>The syntax of first two queries leads us to believe that we&#8217;re only providing the <strong>WITH GRANT OPTION<\/strong> for the <strong>SELECT<\/strong> privilege. But that is not so: the <strong>WITH GRANT OPTION<\/strong> is assigned for all privileges on <strong>world.City<\/strong> to <strong>&#8216;gromit&#8217;@&#8217;localhost&#8217;<\/strong>.<\/p>\n<p>The syntax would be more correct if we were to write something like:<\/p>\n<blockquote>\n<pre>GRANT <strong>GRANT_OPTION<\/strong> ON world.* TO 'gromit'@'localhost';<\/pre>\n<\/blockquote>\n<p>That would make it clear that this privilege does not depend on other privileges set on the specified domain.<\/p>\n<h4>The USAGE inconsistency<\/h4>\n<p>You can <strong>GRANT<\/strong> the <strong>USAGE<\/strong> privilege, but you may never <strong>REVOKE<\/strong> it. To revoke <strong>USAGE<\/strong> means to <strong>DROP USER<\/strong>.<\/p>\n<h4>The missing ROUTINES_PRIVILEGES inconsistency<\/h4>\n<p><strong>INFORMATION_SCHEMA<\/strong> provides with four privileges tables: <strong>USER_PRIVILEGES<\/strong>, <strong>SCHEMA_PRIVILEGES<\/strong>, <strong>TABLE_PRIVILEGES<\/strong>, <strong>COLUMN_PRIVILEGES<\/strong>, which map well to <strong>mysql<\/strong>&#8216;s <strong>user<\/strong>, <strong>db<\/strong>, <strong>tables_priv<\/strong> and <strong>columns_priv<\/strong> tables, respectively.<\/p>\n<p>Ahem, which <strong>INFORMATION_SCHEMA<\/strong> table maps to <strong>mysql.procs_priv<\/strong>?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Doing some work with MySQL security, I&#8217;ve noticed a few inconsistencies. They&#8217;re mostly not-too-terrible for daily work, except they get in my way right now. The ALL PRIVILEGES inconsistency The preferred way of assigning account privileges in MySQL is by way of using GRANT. With GRANT, one assigns one or more privileges to an account, [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"enabled":false},"version":2}},"categories":[5],"tags":[24,16],"class_list":["post-3667","post","type-post","status-publish","format-standard","hentry","category-mysql","tag-information_schema","tag-security"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2bZZp-X9","_links":{"self":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/3667","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/comments?post=3667"}],"version-history":[{"count":21,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/3667\/revisions"}],"predecessor-version":[{"id":3763,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/3667\/revisions\/3763"}],"wp:attachment":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/media?parent=3667"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/categories?post=3667"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/tags?post=3667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}