What prevents a client from picking any arbitrary pair of cookieToken and formToken that was used in the past, and submitting them together in an AJAX call to get it to go through? Isn't that what anti-forgery functions are supposed to prevent? Is this just a lot of stupid overhead that doesn't improve security, or is there a piece of it that I'm missing?
The anti-forgery token is not designed to prevent a Replay Attack. This is where old values are reused to create another request where the aim is to fool the target machine into accepting a valid instruction from the past.
The anti-forgery token is designed to prevent Cross Site Request Forgery attacks.
A simple example is as follows:
- You're logged into
bank.com
on one tab. - You get an email to view a funny video by clicking on a link that redirects you to
evil.com
- The web page on
evil.com
contains a hidden form that is submited by JavaScript tobank.com/make_money_transfer
As you are logged into bank.com
and your cookies are sent by the browser, bank.com
thinks that you made the request and initiates the money transfer without your knowledge, because from the server's point of view, all is well.
The token is designed to prevent this by having something included in the request payload that cannot be auto submitted by a domain that is not the current domain. Due to the Same Origin Policy another domain cannot access the token value and therefore cannot send a legitimate request via a hidden form, or by any other means. The token is unique per log in session so the attacker could not get a valid combination of token and cookie which can be sent to the server.
Looking at the source code for TokenValidator.cs (albeit C# instead of VB.NET) we can see that the ValidateTokens
method checks that the username encoded in the token matches the one of the current HTTP request:
if (!String.Equals(fieldToken.Username, currentUsername, (useCaseSensitiveUsernameComparison) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
{
throw HttpAntiForgeryException.CreateUsernameMismatchException(fieldToken.Username, currentUsername);
}
This is what will stop an attacker grabbing an old version of the form field value and submitting that in their CSRF attack - their encoded username will not match the logged in user of their victim.