{"id":145,"date":"2009-01-01T23:05:54","date_gmt":"2009-01-01T21:05:54","guid":{"rendered":"http:\/\/code.openark.org\/blog\/?p=145"},"modified":"2009-01-01T23:05:54","modified_gmt":"2009-01-01T21:05:54","slug":"using-triggers-to-block-malicious-code-an-example","status":"publish","type":"post","link":"https:\/\/code.openark.org\/blog\/mysql\/using-triggers-to-block-malicious-code-an-example","title":{"rendered":"Using triggers to block malicious code: an example"},"content":{"rendered":"<p>Web applications face constant exploitation attempts. Those with a user base must keep their users&#8217; private data, well&#8230; private.<\/p>\n<p>While the MySQL security model allows restricting users access to databases, tables and even columns, it has no built in feature for restricting the rows access within the given table.<\/p>\n<p>One cannot allow a user to only update rows 0 through 99, but restrict that user from updating rows 100 to 199. Such restrictions are usually managed in the application level, by adding a necessary &#8220;&#8230; AND filtering_column = some_value&#8230;&#8221;<\/p>\n<p>Many web application have the notion of an &#8216;admin&#8217; account, or several such accounts, which provide greater control over the application. The &#8216;admin&#8217; account is one account to which many attacks are targeted. One such attack is an attempt to modify the admin&#8217;s password, such that the attacker can later log in with and access restricted data.<!--more--><\/p>\n<p>Assume the following table:<\/p>\n<blockquote>\n<pre><strong>CREATE TABLE <\/strong>my_users (\r\n  ID <strong>INT NOT NULL AUTO_INCREMENT PRIMARY KEY<\/strong>,\r\n  username <strong>VARCHAR<\/strong>(32) <strong>CHARSET <\/strong>ascii <strong>NOT NULL<\/strong>,\r\n  password <strong>VARCHAR<\/strong>(32) <strong>CHARSET <\/strong>ascii <strong>NOT NULL COLLATE<\/strong> ascii_bin,\r\n  <strong>UNIQUE KEY<\/strong>(username)\r\n);<\/pre>\n<\/blockquote>\n<p>Let us also assume we are somewhat careful, so that the passwords are not plaintext, but rather encoded with MD5.<\/p>\n<blockquote>\n<pre><strong>INSERT INTO<\/strong> my_users (username, password) <strong>VALUES<\/strong>\r\n  ('admin', MD5('qwerty')) ; <span style=\"color: #008000;\">-- Safe password as can be found!<\/span>\r\n<strong>INSERT INTO<\/strong> my_users (username, password) <strong>VALUES<\/strong>\r\n  ('alice', MD5('123456')) ; <span style=\"color: #008000;\">-- Safer yet!<\/span>\r\n\r\n<strong>SELECT <\/strong>* <strong>FROM <\/strong>my_users;\r\n+----+----------+----------------------------------+\r\n| ID | username | password                         |\r\n+----+----------+----------------------------------+\r\n|  1 | admin    | d8578edf8458ce06fbc5bb76a58c5ca4 |\r\n|  2 | alice    | e10adc3949ba59abbe56e057f20f883e |\r\n+----+----------+----------------------------------+\r\n2 rows in set (0.00 sec)<\/pre>\n<\/blockquote>\n<p>An attacker will try to set the password for the admin account using security holes in the web application. The web application may execute the following query:<\/p>\n<blockquote>\n<pre><strong>UPDATE <\/strong>my_users <strong>SET <\/strong>password=MD5('att@cker!') <strong>WHERE <\/strong>username='admin';<\/pre>\n<\/blockquote>\n<p>The issued query is valid, and should generally be allowed. However, we may decide to block changes to the specific &#8216;admin&#8217; row, in the following manner:<\/p>\n<blockquote>\n<pre><code>DELIMITER $$\r\nDROP TRIGGER IF EXISTS my_users_bu $$\r\nCREATE TRIGGER my_users_bu BEFORE UPDATE ON my_users\r\nFOR EACH ROW\r\nBEGIN\r\n  IF (NEW.username='admin') THEN\r\n    SELECT 0 INTO @admin_error FROM `Cannot modify admin data!`;\r\n  END IF;\r\nEND $$\r\nDELIMITER ;<\/code><\/pre>\n<\/blockquote>\n<p>Let&#8217;s try running again the query:<\/p>\n<blockquote>\n<pre><strong>UPDATE <\/strong>my_users <strong>SET <\/strong>password=MD5('att@cker!') <strong>WHERE <\/strong>username='admin';\r\n\r\n<span style=\"color: #993300;\">ERROR 1146 (42S02): Table 'world.Cannot modify admin data!' doesn't exist<\/span><\/pre>\n<\/blockquote>\n<p>The query fails, since the <strong>BEFORE UPDATE<\/strong> trigger fails.<br \/>\nWe can tweak the trigger to only allow specific users to modify the row:<\/p>\n<blockquote>\n<pre><code>DELIMITER $$\r\nDROP TRIGGER IF EXISTS my_users_bu $$\r\nCREATE TRIGGER my_users_bu BEFORE UPDATE ON my_users\r\nFOR EACH ROW\r\nBEGIN\r\n  IF (NEW.username='admin' AND USER() != 'root@localhost') THEN\r\n    SELECT 0 INTO @admin_error FROM `Cannot modify admin data!`;\r\n  END IF;\r\nEND $$\r\nDELIMITER ;<\/code><\/pre>\n<\/blockquote>\n<p>This way it is possible for the root user to modify the password at will. We can further tweak the trigger to INSERT INTO some log table. The information we may wish to register is USER(), the CURRENT_TIMESTAMP(), old password and new password, and perhaps the CONNECTION_ID(). More data means more means to locate the security breach, and monitoring the log table allows for immediate response for such an attempt.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Web applications face constant exploitation attempts. Those with a user base must keep their users&#8217; private data, well&#8230; private. While the MySQL security model allows restricting users access to databases, tables and even columns, it has no built in feature for restricting the rows access within the given table. One cannot allow a user to [&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":[16,21,30],"class_list":["post-145","post","type-post","status-publish","format-standard","hentry","category-mysql","tag-security","tag-sql","tag-triggers"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p2bZZp-2l","_links":{"self":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/145","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=145"}],"version-history":[{"count":15,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/145\/revisions"}],"predecessor-version":[{"id":465,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/posts\/145\/revisions\/465"}],"wp:attachment":[{"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/media?parent=145"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/categories?post=145"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/code.openark.org\/blog\/wp-json\/wp\/v2\/tags?post=145"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}