Thursday, September 19, 2019

HTTP Request Security

Today I learned some very important security implications of how HTTP requests are formed, and how authentication data is handled.  It started with this article: https://thehackernews.com/2019/09/phpmyadmin-csrf-exploit.html


In a nutshell, phpMyAdmin, the primary management tool for MySQL and MariaDB databases, has a serious security flaw that allows attackers to delete entire DB servers with a trivial exploit.  It works like this: A webpage is setup with a link or even just a tag that attempts to load an external resource.  (The example in the article is an img tag.)  If a browser with an open phpMyAdmin session loads the page (in the case of automatically loading the resource) or someone using that browser clicks the link, a command will be sent to the DB to delete the server.  It's that simple.

This exploit relies on several vulnerabilities.  One vulnerability is that phpMyAdmin uses the same default URL on all systems, and almost no one changes it.  This allows an attacker to craft a URL that will work on nearly 100% of phpMyAdmin instances.  This is more a problem with the database administrators using phpMyAdmin than the software itself.

The second vulnerability is that phpMyAdmin does not check the referrer field of the HTTP request.  If it did that, it would be able to easily weed out HTTP requests that didn't come from the phpMyAdmin instance that is currently open.  This would make the exploit significantly more difficult, as spoofing the referrer is not something that can generally be done remotely.  The victim of the attack would have to instruct the browser to send inaccurate referrer information for this to be circumvented, though security flaws in browsers might still allow exceptions.  This is the obvious mitigation point, but it is not the end of the road.  There is an even more fundamental vulnerability here, which does not seem to be widely known, even in the security field.

The third vulnerability is the use of the GET method in handing the request to delete the server.  At this point, we actually need to look at the code used for this exploit.

http://server/phpmyadmin/setup/index.php?page=server&mode=remove&id=1
This is the URL used in the img tag, in the example from the article linked above.  If "server" is replaced with the appropriate domain name or IP address, and you have an open session of phpMyAdmin at that URL in your browser, clicking a link that points to that URL will delete the server.  The reason this works is that the session authentication data is stored in a cookie.  When the browser loads the above URL, that existing session cookie is sent with it, telling phpMyAdmin that it is a legitimate and authorized request.  There are two problems here.  The first is the use of a GET request to do something that changes data on the server.  Officially, the GET method is only for requests that get data, not for requests that send or change data.  The POST method is supposed to be used for requests that send data.  The PUT method is supposed to be used for requests that change data.  The DELETE method is supposed to be used for requests that delete data.  The default method is GET, and simple hyperlinks will always use the GET method.  Trivially loading external resources, like images and videos, will also always use the GET method.  To use any other method, one must either create a form, where the method used can be specified, or make an AJAX request using Javascript.  Injecting forms and Javascript is a lot more difficult than injecting a simple link or resource loading tag.  Plenty of web publication sites allow hyperlinks and external images, but they typically filter out Javascript, forms, and anything else that could interfere with the functioning of the website.  The problem with the GET method is that an attacker can include all of the command information in the URL itself, which is trivial.  In the URL above, we can see that the attacker was able to specify which phpMyAdmin page the request was coming from, the command from that page to execute, and the server id (which will default to 1).  If the context and the command were instead expected to be sent in a POST request, an attacker would have to either craft a form or an AJAX request to send the command.

It gets worse though.  Merely using a POST request instead of a GET request would make this attack much more difficult.  Many sites that allow the injection of GET requests will not allow the injection of any other kind of request, severely limiting an attacker's options.  But, given a site that allows this, an attacker could still do the work to implement the exploit, because the most fundamental problem here isn't even in the request type.  The request type merely guarantees the problem will exist.  The fundamental problem is actually using cookies to transmit the session data.

Cookies are very convenient for web developers, because they automate certain critical processes.  By default, HTTP is stateless.  This means the server does not know what page you are currently viewing nor what you have viewed in the past.  The solution to this is for the client to store the state data, and it does this using cookies.  Cookies can give the server some ability to track state as well, for example, whether or not a particular browser is logged into a website.  In fact, session management is the most common use of cookies.  There are two benefits to this.  One is that the server can easily put session authentication data in a cookie instead of having to embed it directly in the HTML.  The other is that the server can avoid sending session data multiple times.  In theory, this second benefit provides greater security, because it is sending sensitive data fewer times.  In practice though, it makes little difference, because the cookie will still be sent with every request.  Sending it half the times does not double security.  All it does is makes an attacker wait marginally longer to get the session data, a small fraction of the time.  In short, the only significant benefit is convenience.  The problem with cookies is this: They are sent automatically, regardless of where the request comes from.  For example, a POST request that relies on the cookie for authentication could still be used to attack phpMyAdmin, even if it did not allow GET requests to change the DB, because the cookie is sent automatically.  A POST request that relies on session data stored in the HTML of the page and sent in the POST data would not be vulnerable to this attack, because an attack coming from an outside web page wouldn't have the authentication data, nor would it have any way to obtain it.  This is true of any cookie based session management.  The browser automatically sends the cookie with any request to the site the cookie is associated with, which means that even a request to that site from an external site will be authenticated, if an open session exists on the browser being used.

How can this vulnerability be mitigated?  Surely you cannot be expected to abandon cookies and keep authentication data in the HTML.  Perhaps, but there is one more benefit of cookies.  If you open a new tab and navigate to a site where your browser has an open session, if it uses cookies, it will already be logged in.  If it uses HTML for storing session data though, you will not be logged in, in the new tab.  This can be a serious nuisance.  It would certainly avoid the problem we are trying to avoid, but the cost would be far too high to many users.  There are a few other potential solutions.  Perhaps the best one is to split the session data.  Put most of the session data in a cookie, but keep a little bit in the HTML, to be sent with form requests and such.  That little bit extra is what authorizes the server to make changes to data.  Without it, a request with the cookie will still have the page logged in, but if the request attempted to change something, the server will deny the request and a message will be included on the response page indicating that the requested operation failed because it did not appear to come from an authorized web page.  This seems to be the best solution to the problem of cookies, without causing users too much grief.

In short, there are several changes phpMyAdmin needs to be secure against attacks of this nature.  First, it needs to check the referrer and deny requests that do not come from its own pages.  Second, it needs to apply the HTTP methods correctly, using GET only for requesting data, and using POST and perhaps also PUT and DELETE for changing data.  Third, it needs to rely less on cookies for storing authentication data, requiring some kind of authentication embedded in the HTML of its own pages for any request that alters the data or the database.  All of these together would be pretty bulletproof, and it would avoid an security vulnerability inherent to using cookies to store session data.

Of course, these strategies should probably be applied to any website that stores sensitive data.   None of this is difficult, and none of it introduces much additional complexity.  The only potential issue is that it might be a little more difficult to include a special write session key to be sent with POST, PUT, and DELETE requests, if you are using certain frameworks designed to make this easier.  Even then though, it should still be possible, and the benefit is certainly worth the cost.