As part of the book I've been developing some sample code for each chapter; and for chapter 4 the code has taken far more time than the chapter itself. That chapter deals with query strings and forms and covers Cross Site Request Forgery (CSRF).

CSRF is a exploit where a form request comes from another site and your site proceeds to act upon it because a user is already authenticated. I’ve covered this in more detail previously and released AntiCSRF to codeplex to help you protect against it.

One of the things Alex and I discovered whilst going through the ASP.NET pipeline is that ASP.NET does not take the HTTP verb into consideration when deciding if a request is a postback or not. It is possible to take the contents of a webform and send them as query string parameters to an ASP.NET page which then processes them as it a “normal” postback occurred. For example, if I had a simple webform which contains a single button:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
Inherits="PostbackDemo._Default" EnableViewState="false" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <
html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Postback demo</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Literal ID="postbackFired" Visible="false" runat="server">
I am a postback. K THNX BAI</asp:Literal> <asp:Button ID="submit" Text="submit" runat="server"/> </div> </form> </body> </html>

And the code behind

using System;

namespace PostbackDemo
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (this.IsPostBack)
                this.postbackFired.Visible = true;
        }
    }
}

If we run this it behaves as expected, the literal text appears once the submit button is pressed. The source for the page is just as simple:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
    Postback demo
</title></head>
<body>
    <form name="form1" method="post" action="Default.aspx" id="form1">
<div>
    <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTU3MTgxNzExM2Rk17kjochnZ6GSWK9FD4U2bce0ogM=" />
</div>
<div>
    <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAgLnrsDxAwLcu4S2BNBcZ9GBqXRjfMJaY7UNcFBNg3Mp" />
</div>
    <div>        
        <input type="submit" name="submit" value="submit" id="submit" />
    </div>
    </form>
</body>
</html>

We have ViewState because we’ve set the text on the button and event validation because we’re going to be firing a postback at some point. When our form submits three pieces of information are sent in the POSTDATA with the request

__VIEWSTATE /wEPDwUKLTU3MTgxNzExM2Rk17kjochnZ6GSWK9FD4U2bce0ogM=
__EVENTVALIDATION /wEWAgLnrsDxAwLcu4S2BNBcZ9GBqXRjfMJaY7UNcFBNg3Mp
submit submit

If I take these values and build a query string out of them;

http://localhost:19979/Default.aspx?submit=submit
&__VIEWSTATE=/wEPDwUKLTU3MTgxNzExM2Rk17kjochnZ6GSWK9FD4U2bce0ogM=
&__EVENTVALIDATION=/wEWAgLnrsDxAwLcu4S2BNBcZ9GBqXRjfMJaY7UNcFBNg3Mp

and load that into the browser our literal appears. ASP.NET considers this a postback. Why is this important? The HTTP specifications state that GET requests should be idempotent – a second request should not change the result). If, instead of simply showing a literal, I had deleted the first record from a database a second loading of the URL would delete another, different record, thus violating the HTTP specification. Admittedly no-where can I find a definition for a postback that states it indicates the use of the POST verb, MSDN defines IsPostBack as

Gets a value indicating whether the page is being loaded in response to a client postback, or if it is being loaded and accessed for the first time.

I’d wager that most of us assumed, given its name, that a postback only occurs when a form POST occurs. As you can see this is not the case. Is this bad? It depends. If you don’t have a CSRF token, or you’re not using a ViewStateUserKey then you are vulnerable to someone crafting such a URL and, for example, embedding it in a page as the source of an image tag. ViewState user keys are offered as a panacea against a type of CSRF attack called a one-click attack where a form is submitted via javascript, however as you can see it doesn’t even need a form, what we have here is the classic CSRF attack.

So what can you do? Firstly in addition to checking IsPostBack you should also check the HTTP verb, changing your check to be

if (this.IsPostBack && Request.HttpMethod=="POST")
    this.postbackFired.Visible = true;

You should also consider adding a CSRF token to all your forms (remember my AntiCSRF module for ASP.NET does this for you – and as a bonus detects this types of query strings and throws an exception) and if you are using ViewState you should always set a ViewStateUserKey.

Technorati Tags: ,,