The MVC4 SPA template has a ValidateHttpAntiForgeryTokenAttribute
class with a ValidateRequestHeader
function that parses out 2 halves of a RequestVerificationToken
header constructed by the client when it assembles the data for an AJAX call. The client AJAX code gets its value from a field on the form that combines a form token and a cookie token. I feel like combining these into a single value loses sight of the purpose of an AntiForgery token; they were separate for a reason. Do we gain any security by using anti-forgery tokens in this way?
Server .vbhtml code (Razor-based):
Public Function GetAntiForgeryToken() As String
Dim cookieToken As String = String.Empty
Dim formToken As String = String.Empty
AntiForgery.GetTokens(Nothing, cookieToken, formToken)
Return cookieToken & ":" & formToken
End Function
...
@If User.Identity.IsAuthenticated Then
@<input id="antiForgeryToken" type="hidden" value="@GetAntiForgeryToken()" />
End If
Client AJAX code:
function ajaxRequest(type, url, data, dataType) { // Ajax helper
var options = {
dataType: dataType || "json",
contentType: "application/json",
cache: false,
type: type,
data: data ? ko.toJSON(data) : null
};
var antiForgeryToken = $("#antiForgeryToken").val();
if (antiForgeryToken) {
options.headers = {
'RequestVerificationToken': antiForgeryToken
}
}
return $.ajax(url, options);
}
Server validation code:
Private Sub ValidateRequestHeader(request As HttpRequestMessage)
Dim cookieToken As String = String.Empty
Dim formToken As String = String.Empty
Dim tokenHeaders As IEnumerable(Of String) = Nothing
If request.Headers.TryGetValues("RequestVerificationToken", tokenHeaders) Then
Dim tokenValue As String = tokenHeaders.FirstOrDefault()
If Not String.IsNullOrEmpty(tokenValue) Then
Dim tokens As String() = tokenValue.Split(":"c)
If tokens.Length = 2 Then
cookieToken = tokens(0).Trim()
formToken = tokens(1).Trim()
End If
End If
End If
AntiForgery.Validate(cookieToken, formToken)
End Sub
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:
bank.com
on one tab.evil.com
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: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.