1
votes

I'm using the [ValidateAntiForgeryToken] attribute on my [HttpPost] actions. Out of curiosity, I went to a page with a form, deleted the __RequestVerificationToken cookie, and submitted the form.

Result:

System.Web.Mvc.HttpAntiForgeryException: The required anti-forgery cookie "__RequestVerificationToken" is not present.

In the same spirit, I altered the value of the cookie and submitted the form.

Result:

System.Web.Mvc.HttpAntiForgeryException: The anti-forgery token could not be decrypted. If this application is hosted...

It seems to me that, instead of exploding, the application should simply check the SessionID and, if valid, set a new token and redisplay the view.

I wonder, then, if there is a reason that MVC throws an exception instead of doing what I've described. I am planning to handle the exception and do as I've described above but before I do I would like to know if there is a good reason—perhaps related to security?—that this scenario isn't more gracefully handled by default.

3

3 Answers

4
votes

@Jason I liked your idea so much I decided to implement it.

UPDATE: By the way, I do not see any security concerns with handling these exceptions in the following way. It is certainly MUCH MUCH safer than not using anti-forgery validation. Basically, the only thing a hacker can do is set the query string of the GET request, and anti-XSRF generally is only needed for POST requests (unless you're modifying data with GET requests...tisk-tisk), and if the hacker is targeting GET requests they could simply provide crafted link directly to your site instead of going through their own. If anyone is aware of attack vectors I'm overlooking I'd love to hear it.

UPDATE: One caveat I do see in doing this is errors and attacks being silently swallowed up and you never hearing about them. I've added a comment to the code suggesting to log the error.

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new GracefullyHandleHttpAntiForgeryExceptionAttribute());
    }
}

public class GracefullyHandleHttpAntiForgeryExceptionAttribute : HandleErrorAttribute
{
    public GracefullyHandleHttpAntiForgeryExceptionAttribute()
    {
        this.ExceptionType = typeof(HttpAntiForgeryException);
    }

    public override void OnException(ExceptionContext filterContext)
    {
        if (this.ExceptionType.IsAssignableFrom(filterContext.Exception.GetType()))
        {
            // Consider logging these errors, you don't want some
            // problem in your code silently failing and it's worth
            // tracking potential attacks.

            filterContext.ExceptionHandled = true;
            filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.RawUrl);
        }
    }
}

Here's a couple scenarios where this might provide a better experience for your users:

  1. Jane Doe has an account at example.com. Hacker Joe emails Jane Doe a link to example.comev.il, and Jane follows it and is presented a form that she fills out. Joe's form POSTs to a resource at example.com that can only be accessed if Jane is logged in. Since Jane is logged in the POST is authorized, but the [ValidateAntiForgeryToken] attribute steps in and rightly throws an exception. Normally Jane is presented an error page (hopefully not a YSOD mind you :-) and is confused. Although not a terrible experience, things could be better. If instead of erroring in this way, a GET request were issued to the current URL, Jane would be presented that page (assuming it can handle GETs), and move on with her life.
  2. Jane Doe opens two browser windows to the same site in rapid succession. Each browser window will get its own anti-XSRF token but only one will be valid. If she were to submit a form from the window with the invalid token she'll get an error page. Using this helper would simply refresh the form and although the data she entered would be gone, she would be able to immediately start filling in the form again. With a little extra work you could display a comforting message to the user that appears on the refreshed page.
3
votes

Getting an invalid token is an exceptional scenario. As such, an exception is thrown. I would not expect an application to respond in a reasonable way to what is more or less an attack.

For more on exception handling see: http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx

1
votes

You're right, it's due to security concern. AntiForgeryToken has two parts, client part(cookie) and server part. When a HttpRequest reaches server, the server will check the cookie and retrieve the token value and compare with server part.

In a typical XSRF scenario, such as

Mallory: Hello Alice! Look here:
   <img src="http://bank.example.com/withdraw?account=Alice&amount=1000000&for=Mallory">

when the victim click the image her request will be treated as a invalid request as there's no client side token and exception was throw to protect victim.