There seem to be two driving forces here. What do you want to tell the end user, and what do you not want to tell the attacker. The most damaging piece of information you could disclose may be any timing information relating to the error output.
Then again, in some CSRF attacks the errors can be visible, while in other attacks the errors may be suppressed or hidden. That would make me want to take these actions in a high security site:
Terminate the session, and any "upstream" sessions that may be related to OpenID or federation.
If the user is signed on using multiple tabs, redirect them to a page that informs them:
- This may be a result of a second browsing session / tab that is open.
- Tell the user to o prevent this from happening again, close all browsers and only browse one site at a time ( or use a browser that supports this security natively )
- What happened (bad guys tried to do xxxx to yyy) (Careful to not echo data from the attacker or else this opens you up to XSS)
At first I liked the proposal here to go to an "edit" screen that allows a user to verify the changes that are being attempted. Then I thought about the possibility of click-jacking and how that can remove any benefit of this. Then again, I don't want to enable a unsupported "feature" of my site where external users can pre-fill out a form on someone's behalf. I'll offer a dedicated endpoint for this purpose if it's desired.
Other actions I like so far when handling an error include:
Throttle (Thread.Sleep) connections that encounter any of the following trends
- Errors by IP address source (Careful if the source is a NAT'd user with thousands of machines behind it)
- Errors by Referrer (Careful to prevent a system wide DOS)
- Target Page (Controller/View) (yes this can DOS as well)
- Logged on user (or anonymous user)
If you notice that a combination of those factors is causing many of your CSRF alerts, you can slow down and throttle or deny connections until conditions improve. One metric could be errors / time.
Although most implementations allow for a seed, ASP.NET MVC in particular makes it easy to implement a constant (compiled-in) seed throughout out site. This constant
may encourage attackers to try to guess what your seed is. To prevent this I'm pasting in some ASP.NET MVC code that addresses a few issues
- Enables a dynamic seed
- A centralized place to initiate your logging and exception tracking from all controllers.
- Cryptographic Oracle disclosure prevention by sleeping a fixed amount of time before an error is generated and sent to the client. This is of course inspired by Scott Gu's ASP.NET mitigation, with the added feature of sleeping for a fixed amount of time vs a random amount of time.
To start, look at this blog for the base implementation. Then modify the OnAuthorization
method like this
protected override void OnAuthorization(AuthorizationContext filterContext)
{
TimeSpan maxSleepDuration = new TimeSpan(0, 0, 0, 0, 700); //Errors will always take 700ms
DateTime timeStart = DateTime.UtcNow;
base.OnAuthorization(filterContext);
string httpMethodOverride = filterContext.HttpContext.Request.GetHttpMethodOverride();
if (this._verbs.Verbs.Contains(httpMethodOverride, StringComparer.OrdinalIgnoreCase))
{
try
{
this._validator.OnAuthorization(filterContext);
}
catch (HttpAntiForgeryException e)
{
//todo: Custom actions here? Track the IP? Referrer? Do special actions based on IP?
TimeSpan timespent = DateTime.UtcNow - timeStart;
int sleepDuration = maxSleepDuration.Milliseconds - timespent.Milliseconds;
if (sleepDuration > 0 && sleepDuration != System.Threading.Timeout.Infinite)
System.Threading.Thread.Sleep(sleepDuration);
//todo: Error here, or redirect back to validation screen
throw e;
}
}
}
Lastly, you may want to verify your MachineKey is not set to autogenerate, and ensure rotation of this key in your ITOps schedule.