{"id":595,"date":"2009-09-14T17:27:09","date_gmt":"2009-09-14T10:27:09","guid":{"rendered":"http:\/\/adityo.blog.binusian.org\/?p=595"},"modified":"2009-09-14T17:27:09","modified_gmt":"2009-09-14T10:27:09","slug":"recent-wordpress-admin-login-security-vulnerability","status":"publish","type":"post","link":"https:\/\/adityo.blog.binusian.org\/?p=595","title":{"rendered":"Recent WordPress Admin login Security Vulnerability"},"content":{"rendered":"<p>It was discovered by\u00a0Laurent Gaffie, who posted it to Full-Disclosure\u00a0\u00a0<a href=\"http:\/\/seclists.org\/fulldisclosure\/2009\/Aug\/0113.html\" target=\"_blank\">http:\/\/seclists.org\/fulldisclosure\/2009\/Aug\/0113.html<\/a>. \u00a0I quote from\u00a0<a href=\"http:\/\/preachsecurity.blogspot.com\/2009\/08\/wordpress-bugs-disturbing-vulnerability.html\">http:\/\/preachsecurity.blogspot.com\/2009\/08\/wordpress-bugs-disturbing-vulnerability.html<\/a><\/p>\n<p>From the disclosure, submitting a query into the server like so<\/p>\n<blockquote><p>http:\/\/domain_name.tld\/wp-login.php?action=rp&amp;<span>key[]<\/span>=<\/p><\/blockquote>\n<p>causes it to simply skip verification and just re-set the password&#8230; how interesting!<\/p>\n<p>Under ordinary circumstances, a user clicks on &#8220;forgot my password&#8221; then the server sends a one-time URL complete with random &#8220;key&#8221; to re-set the password. The thing to remember here is that the field in the user&#8217;s record remains empty (<span>null<\/span>) until a password reset is requested!  The user then gets an email with a URL that looks something like this:<\/p>\n<blockquote><p><span>http:\/\/domain_name.tld\/wordpress\/wp-login.php?action=rp&amp;<\/span><span>key=RANDOM0987654321<\/span><\/p><\/blockquote>\n<p>which he or she will click on.  The server will then parse the request and compare the <span>key<\/span> parameter against the <span>key<\/span> stored in that user&#8217;s database record.  Easy so far.<\/p>\n<p>Guessing the key is probably not realistic and not worth the attack bandwidth&#8230; but if you could just reset passwords without knowing the key then that would be insanely annoying, wouldn&#8217;t it? The $64k question is&#8230; why does submitting a <span>key[] (an empty array)<\/span> work?<\/p>\n<p>Looking at this issue it&#8217;s clear there are several things possibly at play, including a little bug that may plague more PHP apps than we give it credit for &#8211; casting. Looking at the very first relevant line:<\/p>\n<blockquote><p>$key = preg_replace(&#8216;\/[^a-z0-9]\/i&#8217;, &#8221;, $key);<\/p><\/blockquote>\n<p>it  appears as though this looks and works just fine&#8230; as long as the input in the $key variable is a <span>string<\/span> the line goes about making sure you only have alpha-numeric values in the $key parameter&#8230; golden so far. If you happen to try and stuff an <span>array<\/span> in there (or worse, an empty array), as evidenced by the key[]= above, this all goes sideways. As you&#8217;ll now end up with an array in the $key variable&#8230; which the rest of the code is clearly not expecting.<\/p>\n<p>Moving on&#8230; let&#8217;s look at the SQL query string that gets built&#8230;<\/p>\n<blockquote><p>$user = $wpdb-&gt;get_row($wpdb-&gt;prepare(&#8220;SELECT * FROM $wpdb-&gt;users WHERE  user_activation_key = <span>%s<\/span>&#8220;, <span>$key<\/span>));<\/p><\/blockquote>\n<p>Aha!  If you&#8217;ve got an array in the $key variable, and you try to run this select statement then you end up with a very ugly <span>array<\/span> being flattened and stuffed into the %s (<span>string<\/span>)&#8230; What happens here is that WordPress will flatten the array &#8211; take the first value of the key[] which will be null and shove it into %s&#8230; and then go off and perform its database query. It ends up ooking something like this:<\/p>\n<p>SELECT * FROM $wpdb-&gt;users WHERE  user_activation_key = <span>null<\/span><span><\/p>\n<p><\/span>Since the administrator has <span>not requested a password change<\/span>, the value of $key<span>,<\/span> in the admin record will be <span>null<\/span>&#8230; thus you have a match and a password reset takes place.  Makes perfect sense&#8230; Whoops.<\/p>\n<p>Now presumably this hits <span>admin<\/span> every time since this is the first user typically created in the database, but I&#8217;m just speculating now since I haven&#8217;t had a chance to thoroughly test this yet.<\/p>\n<p>Even more interesting is the fix from the WordPress folks&#8230;<br \/>\n<span><\/p>\n<blockquote><p>if ( empty( $key ) <ins>|| is_array( $key ) <\/ins><span>)<\/span><\/p><\/blockquote>\n<p>The &#8220;patch&#8221; simply replaces the broken <span>if empty<\/span> statement and adds an <span>is_array<\/span>, which will check to see if we&#8217;ve <span>accidentally<\/span> left key blank or passed in an array and bomb if we have.  Of course, this patches <span>this<\/span> bug but &#8230;<\/p>\n<p>Mike Bailey (@mckt_ on Twitter) and I were poking at this and wondered&#8230; why wouldn&#8217;t you use &#8220;<a href=\"http:\/\/us.php.net\/function.is_string\">is_string<\/a>&#8221; instead?  Hasty coding?  Why take the blacklist vs. whitelist approach?<\/p>\n<p>Obviously, you have to wonder now&#8230; how many other bugs are there of this type in other PHP apps? What about other parts of WordPress? Hey&#8230; isn&#8217;t there a rumor going around that Kaminsky and Matasano got taken by an undisclosed WordPress bug? Hrmm&#8230; While I agree with Mike&#8217;s assessment that these types of bugs are rare, PHP certainly makes them more possible, especially when it doesn&#8217;t have the opportunity to throw errors in a situation like this.<\/p>\n<p>I think Mike&#8217;s comment sums it up nicely though&#8230;<br \/>\n<span><\/p>\n<blockquote><p>&#8220;It&#8217;s [the source] full of WTF moments&#8230; I&#8217;m amazed anybody uses WordPress&#8221;<\/p><\/blockquote>\n<p><\/span>Indeed.<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>It was discovered by\u00a0Laurent Gaffie, who posted it to Full-Disclosure\u00a0\u00a0http:\/\/seclists.org\/fulldisclosure\/2009\/Aug\/0113.html. \u00a0I quote from\u00a0http:\/\/preachsecurity.blogspot.com\/2009\/08\/wordpress-bugs-disturbing-vulnerability.html From the disclosure, submitting a query into the server like so http:\/\/domain_name.tld\/wp-login.php?action=rp&amp;key[]= causes it to simply skip verification and just re-set the password&#8230; how interesting! Under ordinary circumstances, a user clicks on &#8220;forgot my password&#8221; then the server sends a one-time URL [&hellip;]<\/p>\n","protected":false},"author":386,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[40],"tags":[3470],"class_list":["post-595","post","type-post","status-publish","format-standard","hentry","category-wordpress","tag-wordpress-security-vulnerability"],"_links":{"self":[{"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/posts\/595","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/users\/386"}],"replies":[{"embeddable":true,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=595"}],"version-history":[{"count":1,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/posts\/595\/revisions"}],"predecessor-version":[{"id":596,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=\/wp\/v2\/posts\/595\/revisions\/596"}],"wp:attachment":[{"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=595"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=595"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/adityo.blog.binusian.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=595"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}