Skip to main content

Validating Anti-Forgery Tokens over AJAX using MVC 4



In ASP.NET MVC, the HtmlHelper.AntiForgeryToken Method generates a hidden form field (anti-forgery token) that is validated when the form is submitted.

This method is essential in preventing Cross-Site Request Forgery (CSRF) attacks by using the @Html.AntiForgeryToken() helper.

What is Cross-Site Request Forgery you ask? 

Cross-Site Request Forgery (CSRF) is an attack that tricks the victim into loading a page that contains a malicious request. It is malicious in the sense that it inherits the identity and privileges of the victim to perform an undesired function on the victim's behalf, like change the victim's e-mail address, home address, or password, or purchase something. CSRF attacks generally target functions that cause a state change on the server but can also be used to access sensitive data.

For most sites, browsers will automatically include with such requests any credentials associated with the site, such as the user's session cookie, basic auth credentials, IP address, Windows domain credentials, etc. Therefore, if the user is currently authenticated to the site, the site will have no way to distinguish this from a legitimate user request.

In this way, the attacker can make the victim perform actions that they didn't intend to, such as logout, purchase item, change account information, retrieve account information, or any other function provided by the vulnerable website.

Sometimes, it is possible to store the CSRF attack on the vulnerable site itself. Such vulnerabilities are called Stored CSRF flaws. This can be accomplished by simply storing an IMG or IFRAME tag in a field that accepts HTML, or by a more complex cross-site scripting attack. If the attack can store a CSRF attack in the site, the severity of the attack is amplified. In particular, the likelihood is increased because the victim is more likely to view the page containing the attack than some random page on the Internet. The likelihood is also increased because the victim is sure to be authenticated to the site already.

Synonyms: CSRF attacks are also known by a number of other names, including XSRF, "Sea Surf", Session Riding, Cross-Site Reference Forgery, Hostile Linking. Microsoft refers to this type of attack as a One-Click attack in their threat modeling process and many places in their online documentation.

The @Html.AntiForgeryToken() helper

Steven Sanderson's blog post "Prevent Cross-Site Request Forgery (CSRF) using ASP.NET MVC’s AntiForgeryToken() helper" is quite self explanatory on how to use the helper, but lets see how we can achieve this with our ajax requests as well.

Let's start with the JavaScript code. 

The easiest thing to do would be to create a custom ajax function where we can add the required data to pass to the server. Always good to add this layer of abstraction so that you can attach custom error handlers & what not.


 

//=============================================================
// This document contains common utilities that can be used in
// your Application (app).
//=============================================================
(function (app, $) {
app.getAntiForgeryToken = function () {
return $('[name=__RequestVerificationToken]').val();
};

// Create a custom ajax function
app.ajax = function(options) {

// (a): Add the anti forgery tokens
options.headers = options.headers || {};
options.headers.__RequestVerificationToken = app.getAntiForgeryToken();

// (b): Make the ajax call
$.ajax(options);

};
})(this.app = this.app || {}, jQuery);

So instead of calling $.ajax use app.ajax.

On your razor view

You simply add the call  to @Html.AntiForgeryToken(). This should generate in your html a hidden input field that looks like so ..


Your Controller

Decorate the controller with the "ValidateAntiForgeryTokenOnAllPosts" authorize attribute and add a definition for it.

 
[AttributeUsage(AttributeTargets.Class)]
public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
var request = filterContext.HttpContext.Request;

// Only validate POSTs
if (request.HttpMethod == WebRequestMethods.Http.Post)
{
// Ajax POSTs and normal form posts have to be treated differently when it comes
// to validating the AntiForgeryToken
if (request.IsAjaxRequest())
{
var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

var cookieValue = antiForgeryCookie != null
? antiForgeryCookie.Value
: null;

AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
}
else
{
new ValidateAntiForgeryTokenAttribute()
.OnAuthorization(filterContext);
}
}
}
}

Don't forget to annotate your controller like so ..



**Note: This code is courtesy of @Richiban which made my work a whole lot easier. (http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/)

Further Reading (http://www.diaryofaninja.com/blog/2014/01/29/htmlantiforgerytoken-ndash-balancing-security-with-usability)

Comments

  1. how do you intend this to work with ajax begin request though?

    ReplyDelete

Post a comment

Popular posts from this blog

Internet Information Services(IIS) reveals its real or internal IP Address

In the ever changing world of global data communications, inexpensive Internet connections, and fast-paced software development, security is becoming more and more of an issue. Security is now a basic requirement because global computing is inherently insecure.

Keeping that in mind, we recently ran our flagship product through a security audit. It was such a helpful exercise in tying-off any remaining lose ends in our application in terms of application security. 
Based on the security audit report, there was a relatively minor issue that appeared when accessing the /images directory of our application. Turns out that the Location response header of the 301 request returns an Internal IP address. The issue is detailed below.

Issue reportedInternet Information Services (IIS) may reveal its real or internal IP address in the Location header via a request to the /images directory. The value returned whilst pen testing is https://10.0.0.10/images.

The riskInformation regarding internal IP add…

C# Console app that displays twitter feed using Linq To Twitter (using Single User Authorization)

I recently had to add a twitter feed to my existing ASP.NET MVC 4 application. All I had to do was pull the last 10 tweets for a given user. It took me a while (shamefully, 3 hours) to get it working so I thought of writing a simple tutorial that explains how to pull a twitter feed for a console app using LINQ to Twitter.

LINQ to Twitter is an open source 3rd party LINQ Provider for the Twitter micro-blogging service. It uses standard LINQ syntax for queries and includes method calls for changes via the Twitter API
What took me long to figure out was the way twitter has implemented authentication using OAuth. Before you do anything, make sure you read the Learning to use OAuth document.
In my example, I used Single User Authorization. Single User Authorization is designed for scenarios where you'll only ever have one account accessing Twitter. i.e. if your Web site does periodic Twitter updates, regardless of user or you have a server that monitors general information. 
Before we be…

Unit Testing HttpContext.Current.Session in MVC3 .NET

We recently changed some functionality where during the "CREATE" process, we go through a wizard to save application data. This data is saved only to the session in the final step when the user clicks the final submit.

This was easy enough to implement but when I started writing unit tests for my static methods that Add, Update, Delete or Modify the contents of our application data in the session, I got the following error:
System.NullReferenceException: Object reference not set to an instance of an object.

Turns out I had forgotten to setup the HttpContext.
The following "TestInitialise" method fixed my problem :)

[TestInitialize]
public void TestSetup()
{
// We need to setup the Current HTTP Context as follows:

// Step 1: Setup the HTTP Request
var httpRequest = new HttpRequest("", "http://localhost/", "");

// Step 2: Setup the HTTP Response
var httpResponce = new HttpResponse(new StringWriter());

// Step 3: Se…