One of the most common security vulnerabilities on any given website is the Cross-Site Request Forgery (CSRF) attack. It's so common that OWASP has regularly included it in its list of the top ten security vulnerabilities. Luckily for us, Microsoft has made this kind of attack very easy to prevent in ASP.NET MVC via the use of AntiForgeryTokens.
What is a Cross-Site Request Forgery Attack?
Pretend we're doing some online banking. Without logging out of the bank's site or closing our browser, we then end up on a site that looks something like this:
<h1>You Are a Winner!</h1>
<form action="http://mybank.com/api/account/withdraw" method="post">
<input type="hidden" name="Amount" value="1000000" />
<input type="submit" value="Click Me"/>
</form>
Well now that's just about the most obviously dangerous thing I ever saw. This hacker is trying to withdraw a ton of money from my account! But, we're safe, because there's no way in hell we'd ever click that button, right?
We wouldn't (because we are programmers and we're constantly on the lookout for that sort of thing) but your users might. They're not programmers, they don't live and breathe code, and so they don't really have a reason to think anybody would be out to scam them or steal from them.
What if we did click that button? Would it actually attempt to transfer one million dollars out of our account? If there were no other security functions in place, then yes, it absolutely would. But why?
Remember that when we got to the malicious page, we hadn't logged out of the bank's website, which means our browser still had the bank's site's authorization cookie. That cookie would get submitted along with the malicious post, and the site's security would see the cookie and allow the post to proceed.
That is a simple Cross-Site Request Forgery attack; a scammer attempts to trick a regular user into submitting a post to a valid website in the hopes of causing havoc or damage. And the best part for the scammers is there will always be some users that will click that button. Malicious users wouldn't continue to do this if it didn't work.
Now, let's imagine that we're in charge of the security on that banking site, and it's our job to protect our system from illicit transfers and other malicious actions. How would we do that? If our website was written in ASP.NET MVC, we could use AntiForgeryTokens.
Using AntiForgeryTokens
On our cshtml file, we would use AntiForgeryTokens like this (the token must be within a form):
@using(Html.BeginForm("Withdraw", "Account", FormMethod.Post))
{
@Html.AntiForgeryToken();
...
}
Then, on the corresponding POST action, we would decorate with [ValidateAntiForgeryToken]:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Withdraw(int amount)
{
...
}
That's all we have to do! We've now enabled protections against CSRF attacks on this action.
What Do AntiForgeryTokens Actually Do?
Let's take a look at what AntiForgeryToken actually renders to a page.
<input name="__RequestVerificationToken" type="hidden" value="EW4QS1DQk7LRlXhB-wriPaJmsRfbIKF2aTWGklW-SuiQYFKVJhZ564lOZP_KD6_Gh3kkF4MbT4YbfIsjqicKj239d8WH5Tj7IknOLXt-G-iU3fR5OFCn9_Itbhw1">
OK, so that's a hidden <input>
with a bunch of gibberish for the value. That explains why the token must be within a form; it has to be submitted to the server.
Does that value gibberish really mean something? No, not by itself. But what is not immediately obvious is that @Html.AntiForgeryToken is also setting a cookie in the browser with the same name and value as that hidden input.
When the form is submitted, the [ValidateAntiForgeryToken] attribute checks both the form elements and cookies for __RequestVerficationToken and confirms that both exist and that they have the same value. If this is true, the action is allowed to proceed normally; if not, the attribute throws exceptions depending on the kind of error. All of that background functionality is encapsulated in the HtmlHelper extension and the attribute.
If we return to our attack scenario from above, when the user clicks the malicious submit button, our ValidateAntiForgeryToken check would look for the token cookie and not find it, since there's no AntiForgeryToken on the malicious page (even if there were, it wouldn't match). That's how we can prevent CSRF attacks: by having a form field and cookie submit unreproducible values to the server, and only allowing the action to proceed if the values exist and match each other.
Use AntiForgeryTokens On Every POST
"That's nice" you say, "but when should I use these tokens?" My opinion is that you should use it on every POST action. Period. I haven't yet encountered a scenario in which I had a POST action and couldn't use AntiForgeryToken. It's too simple to be ignored and the consequences of it not being included, especially in sensitive data scenarios, are massive.
(You can even use it on jQuery AJAX calls, as this StackOverflow answer demonstrates nicely.)
In short, use AntiForgeryTokens on every POST action to easily prevent Cross-Site Request Forgery attacks.
Happy Coding!