Sometimes Standards Matter; GET and POST, and WebApp Security

by Andrew Barber 29. June 2009 12:04

I have read, very conservatively speaking, at least 200 books about web-based programming frameworks and languages. I won't even try to put a number on how many web pages/sites I've read on the topics. Through that time, I've seen mentions of the basic difference between GET and POST requests as relates to web applications. As a side-note, a few comments are usually made concerning the security implications. Many of those brief remarks are, IMHO, not especially helpful. In essence, those remarks often boil down to, "GET is less secure because the data is visible on the query string". I say 'not especially helpful' here, rather than, 'patently incorrect' because one could make a somewhat convoluted case that what the authors are talking about sort of means the same thing I'm about to write about. But I think it's too clear that they mean the statement literally - that the fact that a user can 'see' the query string is the cause for concern, as opposed to the fact that POST data is not visible in a web browser.

Background; Basic Stuff

A web page is delivered to a web browser (such as Internet Explorer or Firefox) in response to a stream of data the web browser sends called a Request. A user sees only the information in their browser's address bar, but that is only a tiny - though of course critical - portion of what is sent to the web server. A Request is a stream of text data which follows a certain protocol; HTTP, in this case. A Request could literally be viewed as if it was a text file; Each line of text represents one variable being sent to the server, and the request is ended with a double newline, so the server knows when the request is complete, and it can start building the response.

An extremely (unrealistically) basic request for a web page might look like this;

GET /index.php?pageID=36475 http/1.1
Host: www.google.com

This request would appear in your web browser's address bar like this; http://www.google.com/index.php?pageID=36475 The above request contained only two lines; the 'GET' line, which includes the method (GET), the path being requested (including query string, if any), and the protocol being used. (http1.1), and the Host: line, which tells the server which site is being connected to (because a single server can have multiple sites).

A real request will have many more lines of data being sent. Your web browser and operating system version, any cookie values that were previously set by the server, data about what types of responses it will accept, and encoded POST, PUT, or file upload data are some possibilities. The important thing to note here is that as far as security is concerned, the only difference between GET and POST requests is that GET requests, using query string data, can possibly be seen visibly in the address bar. That can be a security issue, definitely, just like having a 'password' box on a secure web site, which shows the password in plain text, and not with the obfuscating dots/asterisks. But from the standpoint of a web application developer, that's not really even the point, or the problem.

What is the Problem, Then?

RFC number 2616 defines the HTTP protocol. Section 9 (link) contains information about the various request methods. Toward the very top of that page is 9.1.1, which states in pertinent part;

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.

Emphasis added. When speaking of 'safe' above, the W3C likely was referring to more than just security, but also accidental harm to an application by pressing 'Refresh', for example. However, there are security implications as well, and no; I do not mean that someone will walk by your screen and memorize the query string on your browser.

Semantics, Schemantics

The problem is related to the visibility of the query string. But wait; didn't I just suggest that isn't really the problem? Yes - private data should never be displayed 'in the clear'. That's a given. However, what about when that data isn't private at all - just harmful. Consider this totally fictional URL:

http://server.com/PromoteToAdministrator.php?userName=a.grossman

This URL does not contain any data that any employee of the company would not be able to easily guess. What if I saw that on an address bar, and went to my own terminal and typed this in:

http://server.com/PromoteToAdministrator.php?userName=a.barber

I hear you chuckling now. "Surely, Andrew, this application on 'server.com' requires that you be logged in as a user authorized to promote people to administrators before it will work. Your URL will present you with a logon screen, and probably log the request, and then an admin will see what you tried to do." Nevermind that you would likely be wrong about an admin actually having/taking the time to peruse the logs; in all likelihood, my attempt would go totally unnoticed. Of course, there's also this distressing bit of information; All too many applications actually are vulnerable to just what I showed an example of above. The old "Security by obscurity" saying never reached some folks' ears...

The Plot Sickens

All is likely not well, though. So, I could not use that URL on my own. I don't need to, anyway. I can force an authorized user to do it for me. If the 'PromoteToAdministrator.php' script only accepted data via POST, and not GET, what I am about to demonstrate would not work. And this is terribly simple; All I have to do is get an authorized admin to visit my link for me, presumably while they are already logged in to the system for their daily work on it.

How do I do that? I just build a page somewhere which includes an IFrame that is hidden via CSS or some other means, and loads that URL. I could also use XSS to inject that link onto a trusted site of theirs. There are lots of other tricks to do, but the ultimate issue is that it was so easy to get around this system, because it used a GET/Query String for an 'unsafe' action.

How Does This Happen?

Sometimes, it can be easier to put together a simple script passing data on the query string than via FORMS or Cookies. Simple links can be built on a page, for example in this case, with each existing user's username in the URL. Just click someone's name on the 'promote user' list, and viola', all done! No one can view this list without being logged in as an admin, and no one can visit that link without being admin, so it's safe, right? As we've seen no; it's not at all safe.

And yes - this truly does happen. My fake example is based on a very real one, which is no less simple at all to exploit. There are open-source web applications in use right now which have pages that enable a user to change their own password, completely via query string. This enables anyone who can trick someone else into loading a specially malformed URL (perhaps by posting a 'cool link' on the forums module built into that appliction) to take over anyone's account.

Solutions!

First, remember what the W3C says about GET (and HEAD) methods; They should always be 'safe', in every way. They should not change anything (aside from, say, a hit/view counter); they should primarily be only for choosing which data to display, what set of options to view, and the like. It's also important to note that the Query String is not strictly limited to GET/HEAD requests. Query Strings can be used in POST requests, in exactly the same manner. It is not the use of POST methods that is the mitigation here; it is the lack of use of potentially damaging information in the query string.

It is appropriate, for example, to use the query string to indicate that your user wants to view the list of users to potentially be promoted to admin (if that's a valid business action in your app, of course!) That URL by itself won't "do" anything; it simply helps choose what is displayed to an authorized user.

A Danger to Look Out For

PHP and ASP.NET both have built-in methods to access the entire Request collection as a unified body, lumping query string, forms and cookies values together. Many applications use this functionality when retrieving the values. This means that a request could submit cookies, forms (POST) or query string values, and it will work. So even if you alter the page in the example above that lists the users to promote so that it sends the users via POST, my hand-built query string hack will still work.

Other Possible Mitigations

Of course, there are also other ways to mitigate such an issue. Perhaps you have discovered such an issue, but you can not get it fixed immediately. Mitigating factors here are sort of a mine field; they may work completely, partially, or not at all. It all depends on the application itself. In the end, it might be easier to fix the application itself than attempt to institute some of these. But...

Limiting the length of administrative logons can reduce the effectiveness of this attack, but it's a very random process, obviously. In some scenarios where a XSS issue in the application itself is the vector to deliver the payload, this is obviously of no value whatsoever.

If the resulting page in question has some sort of verification mechanism which requires manual intervention, that may prevent this from working. However, script injection, XSS and browser vulnerabilities can make that moot as well. That at least makes it more difficult to do.

A one-time verification value being added to the query string can also mitigate this somewhat, but the source of that value could cause other issues, and it would likely be easier to address the situation directly.

Limiting the locations from which the administrative interface can be used may help a small amount.

Conclusion

As old as this type of thing is, it sometimes is surprising to see the issue pop up in new applications; but it does. As is often the case, this issue is perhaps often created due to a lack of understanding of the risk, and a desire to rush a feature through. It's a reality I know we all deal with, so just keep this one in mind!

Why Eels?

No one can really be certain. But those slimey underwater critters obviously have something going for them!

Links/Profile

Andrew Barber's Profiles:
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent the views of employees, contractors or clients of Inkwell Creative Group, LLC in any way.

© Copyright 2008, 2009 Andrew Barber