CSRF attacks are one of the many security issues that web developers must defend against. Fortunately, ASP.Net MVC makes it easy to defend against CSRF attacks. Simply slap on [ValidateAntiForgeryToken] to every POST action and include @Html.AntiForgeryToken() in every form, and your forms will be secure against CSRF.
However, it is easy to forget to apply [ValidateAntiForgeryToken] to every action. To prevent such mistakes, you can create a unit test that loops through all of your controller actions and makes sure that every [HttpPost] action also has [ValidateAntiForgeryToken].
Since there may be some POST actions that should not be protected against CSRF, you’ll probably also want a marker attribute to tell the test to ignore some actions.
This can be implemented like this:
First, define the marker attribute in the MVC web project. This attribute can be applied to a single action, or to a controller to allow every action in the controller.
///<summary>Indicates that an action or controller deliberately
/// allows CSRF attacks.</summary>
///<remarks>All [HttpPost] actions must have
/// [ValidateAntiForgeryToken]; any deliberately unprotected
/// actions must be marked with this attribute.
/// This rule is enforced by a unit test.</remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class AllowCsrfAttacksAttribute : Attribute { }
Then, add the following unit test:
[TestMethod]
public void CheckForCsrfProtection() {
var controllers = typeof(MvcApplication).Assembly.GetTypes().Where(typeof(IController).IsAssignableFrom);
foreach (var type in controllers.Where(t => !t.IsDefined(typeof(AllowCsrfAttacksAttribute), true))) {
var postActions = type.GetMethods()
.Where(m => !m.ContainsGenericParameters)
.Where(m => !m.IsDefined(typeof(ChildActionOnlyAttribute), true))
.Where(m => !m.IsDefined(typeof(NonActionAttribute), true))
.Where(m => !m.GetParameters().Any(p => p.IsOut || p.ParameterType.IsByRef))
.Where(m => m.IsDefined(typeof(HttpPostAttribute), true));
foreach (var action in postActions) {
//CSRF XOR AntiForgery
Assert.IsTrue(action.IsDefined(typeof(AllowCsrfAttacksAttribute), true) != action.IsDefined(typeof(ValidateAntiForgeryTokenAttribute), true),
action.Name + " is [HttpPost] but not [ValidateAntiForgeryToken]");
}
}
}
typeof(MvcApplication) must be any type in the assembly that contains your controllers. If your controllers are defined in multiple assemblies, you’ll need to include those assemblies too.