Request Method Can Matter

Posted by on August 15, 2012

One of the nice features of ASP.Net is that many of the server controls populate their values based upon the request method.  Lets look at a quick example.   If the developer has created a text box on the web form, called txtUserName, then on a post back the Text property will be populated from the proper request collection based on the request method.  So if the form was sent via a GET request, then txtUserName.Text is populated from Request.QueryString[“txtUserName”].  If the form was sent via a POST request, then txtUserName.Text is populated from Request.Form[“txtUserName”].  I know, master pages and other nuances may change the actual name of the client id, but hopefully you get the point. 

GET REQUEST

txtUserName.Text = Request.QueryString[“txtUserName”]

POST REQUEST

txtUserName.Text = Request.Form[“txtUserName”]

Although this is very convenient for the developer, there are some concerns on certain functionality that should be considered. Think about a login form.  One of the security rules we always want to follow is to never send sensitive information in the URL.  With that in mind, the application should not allow the login form to be submitted using a GET request because the user name and password would be passed via the query string.  By default, most login forms will accept both GET and POST requests because of how the framework and server controls work.  Why would someone use a get request?  Automation?  Easy login, think for example if I craft the proper URL and bookmark the login page so it auto logs me in to the site.  Although not very common we, as developers, have to protect our users from abusing this type of flaw.  In no way am I saying not to use the server controls.. they are great controls. The point is to be aware of the pitfalls in some situations.

The good news!!  We just need to check the request method and only accept POST requests.  If we receive a GET request, just reject it.  Yes, a user can still submit the GET request, but it won’t authenticate them and defeats the purpose of the user.  Lets take a moment to look at some code that uses the default functionality.

Below is a VERY simple method that demonstrates how both GET and POST requests act.  Although it is not anything more than Response.Write calls, it is sufficient to demonstrate the point.

protected void Page_Load(object sender, EventArgs e)
{
       if (Page.IsPostBack)
       {
            // Encode the Value from the TextBox
            Response.Write(HttpUtility.HtmlEncode(txtUserName.Text));
            Response.Write(":");
            // Encode the value from the TextBox
            Response.Write(HttpUtility.HtmlEncode(txtPassword.Text));
        }
}

Here is a POST Request:

POST http://localhost:60452/Default.aspx HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://localhost:60452/Default.aspx
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: localhost:60452
Content-Length: 316
Connection: Keep-Alive
Pragma: no-cache
Cookie: .ASPXANONYMOUS=YQOZB3SHzQEkAAAANDE
4NDczYzctZjVmNi00ZWEzLWJkYTMtZDZjYmZhN
zY0Y2MylOoTQwCvDTUzA6LJuOO33witVabGXV
4RoXUeyg52RDY1; 
ASP.NET_SessionId=jkne3su2dwemw0lu1kqbjrdq

__VIEWSTATE=%2FwEPDwUJNjk1NzE5OTc4ZGRjhRm%2FtVkqPqFadKC
IAA0lQHoBH0FsR4xVM%2FiTGn7Sew%3D%3D&__EVENTVALIDATION=%2
FwEWBALk1bPACQKMrfuqAgKOsPfSCQKP5u6DCONM%2B3R6i2D%2
FIRsWvIhZp5wqldnzoa%2BjoUVRrng5kifu&ctl00%24
MainContent%24txtUserName=jjardine&ctl00%24MainContent%24
txtPassword=password&ctl00%24MainContent%24cmdSubmit=

The application will write out “jjardine:password” to the browser.  This makes sense since those two values were passed.  Now lets take a look at a GET Request and see the same request:

http://localhost:60452/Default.aspx?__VIEWSTATE=%2FwEPDwUJNjk1N
zE5OTc4ZGRjhRm%2FtVkqPqFadKCIAA0lQHoBH0FsR4xVM%2FiTGn7Sew
%3D%3D&__EVENTVALIDATION=%2FwEWBALk1bPACQKMrfuqAgKOsP
fSCQKP5u6DCONM%2B3R6i2D%2FIRsWvIhZp5wqldnzoa%2BjoUVRrng
5kifu&ctl00%24MainContent%24txtUserName=jjardine
&ctl00%24MainContent%24txtPassword=password&
ctl00%24MainContent%24cmdSubmit=

Again, this output will write out “jjardine:password” to the browser.  The big difference here is that we are sending sensitive information in the URL which, as mentioned above, is a big no-no.  We can’t stop a user from submitting a request like this.  However, we can decide to not process it which should be enough to get someone to stop doing it. 

It is important to note that any form that has sensitive information and uses the server controls like this can be vulnerable to this issue.  There are some mitigations that can be put in place. 

Check the Request Method

It is very easy to check the request method before processing an event.  The below code shows how to implement this feature:

protected void Page_Load(object sender, EventArgs e)
{
    if (Page.IsPostBack)
    {
        if (Request.RequestType == "POST")
        {
            // Encode the Value from the TextBox
            Response.Write(HttpUtility.HtmlEncode(txtUserName.Text));
            Response.Write(":");
            // Encode the value from the TextBox
            Response.Write(HttpUtility.HtmlEncode(txtPassword.Text));
        }
    }
}

Now, if the request is not a POST, it will not process this functionality.  Again, this is a very simplistic example.

Implement CSRF Protection

Implementing CSRF protection is beyond the scope of this post, but the idea is that there is something unique about this request per the user session.  As we saw in the GET request example, there are more parameters than just the user name and password.  However, in that example, these fields are all static.  There is no randomness.  CSRF protection adds randomness to the request so even if the user was able to send a get request, their next session attempt would no longer work because of this missing random value.

Large Data

So large data doesn’t sound like a lot here, but it is a mitigation based on the construction of the page.  If the viewstate and other parameters become really long, then they will be too large to put in the URL (remember this is usually limited on length).  If that is the case, the user will not be able to send all the parameters required and will be blocked.  This is usually not the case on login pages as there is usually very little data that is sent.  The viewstate is usually not that big so make sure you are aware of those limits.

 

Why do we care?

Although this may not really seem like that big of an issue, it does pose a risk to an application.  Due to compliance reasons related to storing sensitive information in log files, doing our part in protecting users data (especially authentication data), and the fact that this WILL show up on penetration testing reports, this is something that should be investigated.  As you can see, it is not difficult to resolve this issue, especially for the login screen. 

In addition to the login screen, if other forms are set up to support both GETS and POSTS, it could make CSRF attacks easier as well.  Although we can do a CSRF attack with a POST request, a GET can be deployed in more ways.  This risk is often overlooked, but is an easy win for developers to implement.   Happy Coding!!

Comments

Comments are closed.