Enhancing ASP.NET MVC Security with AOP
Cross Site Request Forgery (CSRF) is a form of attack against web applications where an attacker can take advantage of how state is preserved in web application between page requests. This can be a persistent vulnerability due to the extended session capabilities of many sites “Keep me logged in” functionality. The attack is accomplished by the attacker either tricking the user into clicking on a malicious link or it can be done with absolutely no user interaction if attacker can take advantage of a Cross Site Scripting (XSS) vulnerability in the web application.
If an application is vulnerable to CSRF then even authenticated controller methods can be activated at will by an attacker and sent whatever data that the attacker wants to send. Consequences of such an attack can include altering of confidential information, making unauthorized purchases using the users identity and locking a user out of their account by resetting their password to one only known by the attacker.
As ASP.NET developers in the Webforms days we were largely immune to this type of attack thanks to the prevalence of ViewState. This is because a side effect of using ViewState is that the hidden ViewState field on a form has a similar effect to an anti-CSRF token, in that it is page specific data that is require to be present and correct on a page postback. Now, with the popularity of ASP.NET MVC and the fact that developers are working closer to the metal with page markup and navigation functionality we must be on guard for CSRF vulnerabilities in our web applications. Luckily starting in ASP.NET MVC 2 Microsoft introduced some anti-forgery technology to mitigate this type of attack. Just decorate the controller methods with the ValidateAntiForgeryToken attribute and make sure that you include an Html.AntiForgeryToken() call in the form in your view. The result is that a hidden form field is rendered in your view with a unique canary value that is also stored encrypted in a cookie. When the controller method is executed the values are compared and if there is a mismatch then the controller method will not execute.
Now we have a security problem (CSRF) and an easy solution (the anti-forgery token) but without developers remembering to always implement the anti-forgery functionality we will still be building insecure applications. Educating ourselves about security risks is a great first step but even when we know about preventing CSRF attacks we don’t always remember when we are deeply engrossed in coding. This is where a tool based solution is entirely appropriate. As humans we are not nearly as detailed obsessed as a computer and so we can take advantage of the computers ability to continually perform boring and repetitive tasks while we focus our efforts on the creativity needed to solve the actual business problems.
We want to make it very obvious to a developer that there is a security flaw in their code and we want to do this as quickly as possible so that it can be fixed easily. The solution I have come up with is to examine the code and fail the build if a potential CSRF flaw is detected. To do this I have leveraged the compile time analysis functionality in PostSharp to detect the potential for a CSRF attack and inform the developer exactly where the problem is by failing the build.
First, we need a vulnerable application to test against. I am using a small ASP.NET MVC demonstration application I wrote that is the website frontend for El-Cheapo Publishing. You can get the full code from my Bitbucket repository. As a publisher, we maintain royalty schedules for our authors and each author of a book has a certain percentage split of the royalty amount. Of course, we know that this is secure because we are using role based security to ensure that only members of the Admin role can make changes to the royalty split data.
As it is written this application is vulnerable to a CSRF attack where I can make someone change royalty distribution percentages using their own credentials. Let’s put ourselves in the shoes of an underpaid and not too ethical author. As an author, I know someone in the accounting department and I want to up my royalties without going through a long and convoluted negotiation with the El-Cheapo brass.
I know the royalty editing page looks like this:
All I need to do is create a small page that exploits the vulnerability in the royalty editing form and get one of my accounting buddies to click on it since they also spend all day logged into the El-Cheapo site with their administrator credentials. This is easy enough to do because everyone loves dancing frogs so I create something like this:
This will display a dancing frog to the user and it includes a form with hidden fields duplicating those on the royalty editing form along with enough Javascript to post this to the El-Cheapo royalty editing form. Once an admin user who has previously logged into the El-Cheapo visits my dancing frog page the form fields will be automatically submitted and my commission rate for this title will be changed from 30 to 99 percent, all under the logged in users identity.
In order to prevent this sort of behavior we need to require all of our developers to use the anti-forgery features of ASP.NET MVC. I am already using PostSharp to keep all of my methods clean and DRY by moving logging, caching, exception handling and other common logic out to aspects. For a video of how I am doing that check out the recording of my presentation at CodepaLOUsa 2012 on InfoQ. To cause build failures when a controller accepts post data and does not use the anti-forgery token I wrote the following aspect:
In this aspect we are performing compile time validation on the targeted methods, which in this case is all methods in types in the WebApplication.Controllers namespace which is accomplished by adding an assembly level attribute to the AssemblyInfo.cs file in my project:
[assembly: CSRF(AttributeTargetTypes = “WebApplication.Controllers.*”)]
After the code is compiled PostSharp runs and further modifies the assembly to add all of my cross cutting functionality such as logging, caching, etc. It will also execute all of the compile time validation routines for the targeted methods.
In this validation we make sure that we are operating on a method that has at least one parameter, that the method has at least one attribute and that one of the attributes is an HttpPost. We then check and make sure that this method does not have an OverrideCSRFValidation attribute (which is also defined in the aspect). The OverrideCSRFValidation is there so that we can consciously bypass the enforcement that a particular controller method has to implement anti-forgery. That way we can opt out of the requirement on a case by base basis but it requires the developer to take action to get a clean compile. If we have not opted out of requiring the anti-forgery token then we make sure that a ValidateAntiForgeryToken attribute is on the method. If there is not an anti-forgery token attribute we fail the build (by returning a false) and output a compiler error message to the developer telling them there is a security error in their code and the location of the error.
In order to give the developer the exact error location in their build output I implemented two different options. The first is using an experimental feature of PostSharp to resolve the message location in the code. This is an opt in setting that is off by default, so you have to go to Tools->Options->PostSharp and turn it on.
The second method of finding the location of the offending method is even more experimental. I am using Project Rolsyn to build a code analysis tool that I call CodeFreud. The first use of this tool is to parse the code in the solution and find the method with the correct signature and then pass that back up so that the compiler error message has the correct filename and location. At this point in time I am using a hack to pass in the location of the solution file to CodeFreud and just putting the full path to the solution in a PostSharp property named “SolutionFile”. This will change in the future as I continue to improve both CodeFreud and this aspect. Stay tuned for updates!
Finally, when I build my application all of the validation logic executes and the result is a failed build:
When I build I now see all of the controller methods that could be vulnerable to a CSRF attack and the compilation fails. I can now go through and either secure them by using the anti-forgery token or exempt them from the validation by decorating them with the OverrideCSRFValidation attribute.
With this code you now have the tools you need to automatically enforce a secure coding technique in your projects. All of the code used here is licensed under an MIT license and while it is sample code and should not be used directly for production applications I hope it gives you insight and inspiration to increase the security of your applications.
I welcome your feedback, either on this blog or on Twitter. Stay tuned for more articles on application security, Aspect Orientated Programming and whatever other topics strike my fancy!
- My AOP talk at CodepaLOUsa is up on InfoQ
- Finding Bad Controllers With AOP