We use ASP.NET MVC's default Antiforgery technique.
Recently a security company did a scan of a form and made note that they could use the same _RequestVerificationToken
combination (cookie + hidden field) multiple times.
Or how they put it: "The CSRF token in the body is validated on server side but is not revoked after use even though the server
generates a new CSRF token."
After reading the documentation and multiple articles on the implementation of Antiforgery, it is my understanding that this is indeed possible as long as the session user matches the user in the tokens.
Part of their recommendation: "Such tokens should, at a minimum, be unique per user session" In my understanding this is already the case, except for anonymous users, correct?
My questions: Is this a security issue? How much of a risk is it? Is there a library that makes sure tokens are not reusable/invalidated.
If not, including an extra random token in session that will be reset on every request sounds like it would solve the issue.
Anti-Forgery TokensOne token is sent as a cookie. The other is placed in a hidden form field. The tokens are generated randomly so that an adversary cannot guess the values. When the client submits the form, it must send both tokens back to the server.
Require antiforgery validationThe ValidateAntiForgeryToken attribute requires a token for requests to the action methods it marks, including HTTP GET requests. If the ValidateAntiForgeryToken attribute is applied across the app's controllers, it can be overridden with the IgnoreAntiforgeryToken attribute.
Error: The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the <machineKey> configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.
The basic purpose of ValidateAntiForgeryToken attribute is to prevent cross-site request forgery attacks. A cross-site request forgery is an attack in which a harmful script element, malicious command, or code is sent from the browser of a trusted user.
The customer eventually agreed that the Antiforgery implementation of ASP.NET is sufficient. Just for fun I wanted to extend Antiforgery to meet the invalidation requirement.
The Antiforgery library has one extensibility point: IAntiforgeryAdditionalDataProvider
(Core) and IAntiForgeryAdditionalDataProvider
(pre-Core)
In ASP.NET MVC (pre-Core) you can set this on startup.
using System.Web;
using System.Web.Helpers;
// ...
namespace AntiForgeryStrategiesPreCore
{
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
// ...
AntiForgeryConfig.AdditionalDataProvider = new MyAdditionalDataProvider();
}
}
}
For ASP.NET Core you need to register your IAntiforgeryAdditionalDataProvider
as a service. If you don't it will use the DefaultAntiforgeryAdditionalDataProvider
(source) which does nothing.
using System;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AntiForgeryStrategiesCore
{
// ...
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAntiforgeryAdditionalDataProvider, SingleTokenAntiforgeryAdditionalDataProvider>();
// ...
}
}
// ...
}
Now you can add additional data to your Antiforgery token which will be encypted into your cookie and and form field. Here's a MVC Core example that holds on to a single token in Session, and removes it after usage.
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
namespace AntiForgeryStrategiesCore
{
public class SingleTokenAntiforgeryAdditionalDataProvider : IAntiforgeryAdditionalDataProvider
{
private const string TokenKey = "SingleTokenKey";
public string GetAdditionalData(HttpContext context)
{
var token = TokenGenerator.GetRandomToken();
context.Session.SetString(TokenKey, token);
return token;
}
public bool ValidateAdditionalData(HttpContext context, string additionalData)
{
var token = context.Session.GetString(TokenKey);
context.Session.Remove(TokenKey);
return token == additionalData;
}
}
}
This isn't recommended because when you open multiple tabs with multiple forms, only one of the forms will have the valid token in session, and the other will fail. That's why I made one that holds on to multiple tokens. You can find that AdditionalDataProvider and others on GitHub (Time based, Queue based).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With