Tuesday, 26 March 2013

Exam 70-486 Preventing Cross Site Request Forgery In MVC 4

The Problem

As a developer, you may or may not ever think about Cross Site Request Forgery (CSRF) and how it can be a handy channel for hackers to maliciously attack you through the use of other users. So what is it exactly. Well simply put a Cross Site Request Forgery is when a hacker does not have enough access rights to attack your system directly. So they will usually trick users with with the access rights to malicious submit data on their behalf. Usually this kind of attack involves the user having an opened session using either session or persistent cookies.

For example, consider a manager working at a bank. Lets call him Bob. He happens to have some IT skills and knows what a HTML form looks like. He currently has access to bank account information, and had the ability to transfer funds to any account of his choosing from the form illustrated below (seems far fetched, but just go with me on this).

All of a sudden, he was caught photocopying his butt, because lets just say he's nuts, and was then fired. So as a result all access he had to such functionality had been revoked. Knowing how the administration screens worked, he then took a copy of the HTML markup just before he left the building and emailed it to himself. The markup looked something like this.

  1. <form action="http://hostsite.com/Account/Transfer" method="POST">
  2. <fieldset>
  3. <legend>Transfer Details</legend>
  4. <div>
  5. <label for="fromAccountNumber">From Account Number:</label>
  6. <input type="text" id="fromAccountNumber" name="fromAccountNumber" />
  7. </div>
  8. <div>
  9. <label for="toAccountNumber">To Account Number:</label>
  10. <input type="text" id="toAccountNumber" name="toAccountNumber" />
  11. </div>
  12. <div>
  13. <label for="amount">Amount: </label>
  14. <input type="text" id="amount" name="amount" />
  15. </div>
  16. <div>
  17. <input type="submit" value="Submit" />
  18. </div>
  19. </fieldset>
  20. </form>

Keeping in mind that the important factor was the forms action and the input names, he was able to generate his own form that can be hidden from the user on a hack website page hosted at http://hacksite.com. It would contain similar markup noted below.

  1. <form id="hackForm" action="http://hostsite.com/Account/Transfer" method="POST" style="display:none;">
  2. <input type="text" name="fromAccountNumber" value="10000001" />
  3. <input type="text" name="toAccountNumber" value="10000002" />
  4. <input type="text" name="amount" value="1000000" />
  5. </form>
  6. <script>
  7. document.getElementById("hackForm").submit();
  8. </script>

Note that the action attribute on the form points directly to that host sites url (http://hostsite.com/Account/Transfer) which happens to be the url that handles the form post for transferring funds. The value attributes of each input element also contains hard coded values, ensuring that when the post is made that the account number he chooses will be involved in the transfer (Poor person who owns account 10000001). Now I know that this probably is more effective to be an ajax post, but just for simplicity I will keep it as a standard form post.

Now lets also consider that Bob has a buddy who still works at the company. Lets just call him Ed. Bob then sends an email to Ed with a link in it. The link might state "Click here to see my funny pictures". But the href redirects him to http://hacksite.com instead.

The form above is then immediately posted via some javascript and the input is then submitted to http://hostsite.com/Account/Transfer. Ed having proper authentication and authorization to access the hostsite.com website is then able to transfer the funds from account "10000001" to account "10000002" without actually intending it.

Given a normal MVC scenario, where a developer has not considered this problem he may write a simple piece of code like this to handle the form post.

  1. [Authorize]
  2. public class AccountController : Controller
  3. {
  4. [HttpPost]
  5. public ActionResult Transfer(string fromAccountNumber, string toAccountNumber, decimal amount)
  6. {
  7. var accountRepository = new AccountRepository();
  8. accountRepository.TranferFunds(fromAccountNumber, toAccountNumber, amount);
  9. return RedirectToAction("Success");
  10. }
  11. }

Given this attack is not an indirect one, and really a forgery trick, (thus the term Request Forgery), a simple Authorize attribute is not sufficient enough to prevent such an attack. Ed will generally have access to perform the post and Bob knows it. Therefore Bob scores himself 1000000 bucks.

The fix

This is relatively easy to implement in MVC. It just requires 2 changes in order to prevent the attack. The first involves making a change to your view by making a call to the AntiForgeryToken method of the HtmlHelper as shown below.

  1. <form action="http://hostsite.com/Account/Transfer" method="POST">
  2. @Html.AntiForgeryToken()
  3. <fieldset>
  4. <legend>Transfer Details</legend>
  5. <div>
  6. <label for="fromAccountNumber">From Account Number:</label>
  7. <input type="text" id="fromAccountNumber" name="fromAccountNumber" />
  8. </div>
  9. <div>
  10. <label for="toAccountNumber">To Account Number:</label>
  11. <input type="text" id="toAccountNumber" name="toAccountNumber" />
  12. </div>
  13. <div>
  14. <label for="amount">Amount: </label>
  15. <input type="text" id="amount" name="amount" />
  16. </div>
  17. <div>
  18. <input type="submit" value="Submit" />
  19. </div>
  20. </fieldset>
  21. </form>

Doing this emits 2 pieces of information to the client's browser. The first is a hidden field with the name of "__RequestVerificationToken". This hidden field contains an encrypted value that is updated on each request to the server, so it never remains consistent. At the same time the helper will also generate a cookie which is "HttpOnly", and therefore not accessible via javascript. This cookie contains the same name as the hidden field and also contains the same value.

The second part involves simply including the ValidateAntiForgeryTokenAttribute on top of your controller or action method.

  1. [Authorize]
  2. public class AccountController : Controller
  3. {
  4. [HttpPost]
  5. [ValidateAntiForgeryTokenAttribute]
  6. public ActionResult Transfer(string fromAccountNumber, string toAccountNumber, decimal amount)
  7. {
  8. var accountRepository = new AccountRepository();
  9. accountRepository.TranferFunds(fromAccountNumber, toAccountNumber, amount);
  10. return RedirectToAction("Success");
  11. }
  12. }

This attribute implements the IAuthorizationFilter interface. This means that by default the controller will attempt to invoke this authorize attribute and ensure the data being posted is valid before it will allow the action to even be invoked in the first place. It's responsibility is to look at both the posted "__RequestVerificationToken" form data and the value from the cookie. If both values match, then it assumes that the information posted was legit and allows processing of the request to continue. If they do not match, or either the hidden field or cookie is missing, then you will receive an error and the data will never be processed.

How does this actually fix the problem?

So going back to the Bob and Ed scenario, how does this prevent Bob from transferring funds through Ed? The answer is pretty straight forward. There is no way ahead of time that Bob can predict the encrypted cookie value, that Ed has on his system. Therefore he could never properly generate the hidden field that matches the cookie. Thus he'll never been able to access the action method and perform the money transfer.

Limitations and Assumptions

Unfortunately the Anti Forgery Token is not a full proof solution. It involves a number of things in which to work.

  • Cookies are required.
  • Only works with POST requests. Though GET should really only be used for reading not updating.
  • The are some Cross Site Scripting tricks that can bypass it. I won't mention them here.

Thanks for reading...

Sunday, 24 March 2013

Exam 70-486 Deferred Validation and Validation Requests in MVC 4

The Problem

Currently out of the box in MVC 3 using ASP.NET 4.0, if a user tries to submit a form that contains fields with HTML markup or even tries to pass HTML markup to query string parameters you will receive a Request Validation error similar to below.

ASP.NET will automatically kick your posted request to the curb and won't even attempt to execute your controller's action methods. The ValidateInput attribute placed on top of controller or action methods was also ignored and no longer became a factor as it once was in ASP.NET 2.0. The reason for this is that the validation of all request input is now evaluated before the application's Begin_Request method is actually called.

To better demonstrate the problem think about the following web request being made to the server.

http://localhost/Home/Index?name=<script>alert('hello world');</script>

As you can see this is a very safe scripting attack but still one in any case. But just assume for the moment that you wanted this to get through to the web server. In ASP.NET 2.0 you could have simply written MVC code like below including the ValidateInput attribute and forcing the action method to never validate.

  1. [ValidateInput(false)]
  2. [HttpGet]
  3. public ActionResult Index(string name)
  4. {
  5. ViewBag.Name = name;
  6. return View();
  7. }

But if you try to run the same code in an MVC application running under the ASP.NET 4.0 runtime, you will receive the error above. Believe it or not Microsoft did this by design because they felt it was important to catch any validation errors early rather than not at all. In ASP.NET 2.0 if you created a custom HTTP Handler or were using a ASMX Web Service, this validation was never being performed at all. So the unexpected functionality change was kinda necessary, (but destroyed the purpose of the ValidateInput attribute in the process, damn you Microsoft!!!). But now all ASP.NET applications are better protected against Cross Site Scripting (XSS) attacks. However there are still times where using HTML markup is actually appropriate for best user experience, such as submitting comments or even blogs as I am right now.

The Fix

So how do we get around this? What is the best approach to take in MVC 3/4? Well there are a number of different ways to address this. The old way to fix this issue in MVC 3 was to revert the requestValidationMode attribute of the httpRuntime element in the web.config file back to using "2.0".

  1. <httpRuntime requestValidationMode="2.0" />

However what this meant you relapsed back to the security vulnerabilities that ASP.NET 2.0 had. Luckily .NET 4.5 has provided 2 new features to fix such a problem. Deferred ("lazy") request validation and the ability to access unvalidated request data. So now you just need to set the runtime attribute to "4.5".

  1. <httpRuntime requestValidationMode="4.5" />

So what is Deferred ("lazy") request validation?

The purpose of Deferred request validation was to delay the validation of any posted information until it was actually attempted to be retrieved from the request object itself. Another words if some posts some malicious code as a query string parameter or as some form data it will be ignored until you try to do something like this.

  1. var comment = Request.Form["comment"];

Or
  1. var comment = Request.QueryString["comment"];

At this point in time the value of comment is then validated and will return a Request Validation Error if the value is deemed malicious.

But wait a minute, why bother letting in malicious information in if you are just going to run into the same problem anyway when you try to access it. Well this is where the ability to access unvalidated data comes into play.

Accessing Unvalidated Data

To retrieve data that has malicious code but without receiving the Request Validation error you can access the data using the Unvalidated extension method included in the System.Web.Helpers namespace located inside of the System.Web.WebPages assembly. You then just simply access it using the code below.

  1. var comment = Request.Unvalidated["comment"];

MVC and Model Binding issues

But now you are probably asking the question, "Does this actually solve issues with Model Binding?". Unfortunately no it does not as Model Binding still continues to look inside of the Forms and Query string collections in order to try and perform the binding. So how do we fix this? Does ValidateInput actually work now?

The answer to the second question is yes, however I strongly recommend to never use it. The problem with ValidateInput is that it allowed all posted data to have validation ignored, even when only one parameter required HTML. Instead in MVC 3 a newly defined AllowHtmlAttribute was created to get around such a problem. You include it on top of model's property so that only that on is ignored.

  1. public class Comment
  2. {
  3. [AllowHtml]
  4. public string Description { get; set; }
  5. public string Title{ get; set; }
  6. }

Doing this means that Description will allow HTML to pass through, however Title will not.

Summary

I guess to take out of all this you just need to remember the following.

  • Ensure that the requestValidationMode on the httpRuntime element in the web.config file is set to 4.5 to ensure you have the maximum XSS support.
  • If fetching unvalidated values from the Request object directly use the Unvalidated extension method from the System.Web.Helpers namespace.
  • To address a property on your model use the AllowHtmlAttribute in replace of the ValidateInput on a controller or action method.

Thanks for reading...