Blog

Mitigating Cross-Site Request Forgery (CSRF) Attacks

Posted by Matthew Presson & J. R. Myers

small_securecodingfundamentals-703050-517859-edited

Cross-Site Request Forgery, also known as CSRF and XSRF, is a type of attack that has been around for years and we know how to prevent it.  Yet we still find this vulnerability in hundreds of assessments and penetration tests every year.

In this post, we want to make developers aware of the vulnerability and its significance, show how a hacker can perform an attack against a sample application, and (most importantly) explain how you can protect an application from this style of attack by using controls built-in to the ASP.NET MVC framework.

A Real-life Example

While performing an application assessment for a client in the healthcare industry, we found an overwhelming number of pages that were vulnerable to cross-site request forgery. One of these pages stood out – the Update Profile page.

The page allowed a user to update the email address associated with their account. We constructed a CSRF proof-of-concept exploit that allowed us to change the email address of any logged-in user that visited the page.

Changing the email address is the first step to gaining control of the account, as the account’s email address is used in other account management processes such as Forgot Password. After a user visited the “Update Profile” exploit page and we updated the email address to one that we controlled, we could then initiate the Forgot Password process.  The reset information was sent to us and we could then reset the password, gaining complete control of the user account.  

The stakes were high for this client; this application held healthcare data for millions of people. If the application were compromised, the company might suffer reputational damage, hefty fines, and additional costs from crisis measures such as providing identity theft protection for compromised customers. Worse, the compromised information could be used in other types of attacks such as social engineering and phishing or even sold on the black market.

Attack Background

This is a type of attack that takes advantage of the way that all web browsers, application servers, and application frameworks function. Every time you make a request to a web site, your browser checks to see if it has any valid cookies in its cookie store for that site. If it does, it automatically appends these cookies to the request before sending it to the server. This is useful for many reasons, but probably the biggest benefit is that it helps customers stay logged in to the systems by automatically sending the person's session cookie/token with each request.

This process happens regardless of the type of request – GET, POST, PUT, DELETE, etc. It happens behind the scenes and is hidden from the user. The process happens even if the request is sent to a different domain (in essence, this is how tracking cookies work, but that is really a topic for another blog post).

This means that once you are logged in to a site your session token is automatically sent to the server along with the request to identify you every time your browser loads an image, every time it requests a piece of JavaScript code, every time you click a link, and every time you submit a form.

In the majority of cases, this is the only thing that identifies you to the application and the only thing that identifies the request as being legitimate. As a result, as long as a valid session token exists as part of the request, the application framework must assume that the request was sent by the logged in person and is legitimate. The goal of a CSRF attack is to abuse this assumption and to get the victim to send requests to the application that they did not intend, often to the benefit of the attacker.

How the Attack Works

Remember – session tokens are automatically appended to every request to the application. Therefore, to pull off a CSRF attack, an attacker simply has to get a victim's browser to make a request to the server. This is actually pretty simple because, in most cases, these attacks are used as part of a phishing campaign. The attacker sends out a bunch of emails to unsuspecting victims to lure them into visiting a web page which the attacker controls. Hidden on this page are HTML elements such as JavaScript blocks and hidden forms which, when rendered in the victim's browser, will make requests to the target application. If the victim happens to be logged in to the application at the time they visit the attacker's site, their session token will automatically be appended to these requests, and the application will perform whatever action the attacker requested. (We know what you’re thinking – who’s dumb enough to click on a phishing campaign email? More people than you might think.)

The sample HTML file below demonstrates just how simple it is for an attacker to get a victim's browser to automatically make a request.

Attacker's code: 


<html>

     <body onload="document.getElementById('attackForm').submit()">
          <form id="attackForm" name="attackForm"
action="http://app/Account/ChangePassword">
                <input type="hidden" name="NewPassword" id="NewPassword" value="csrfRul3z!"/>
                <input type="hidden" name="ConfirmPassword" id="ConfirmPassword"
value="csrfRul3z!"/>
          </form>

          <!-- Flashy content here to make the victim think the site is legitimate -->
     </body>
</html> 


When a victim accesses this page, the JavaScript in the "onLoad" function of the "body" tag will be executed, auto-submitting the form hidden within the page. In this case, the form submits to the Account Controller's ChangePassword action. In our example, the Action does not require the person's current password (a separate vulnerability that should be corrected). If the victim happened to be logged in to the target application, their password has now been changed to a value known by the attacker, and their account is compromised.

When thinking about this type of attack, remember that the only thing an attacker has to know to perform this type of attack is the URL that processes the target request and the names of the parameters that are required. This may sound complicated, but the information can easily be gathered by right clicking on the target page and viewing the page's HTML source. Once it is duplicated and modified on the attacker's site, the only thing left is to lure the victim to it.

Prevention

How do we prevent these attacks from being successful? The solution is quite simple:  add a random value to the body of each form that processes sensitive data or performs a sensitive transaction. Then on the server, add code to check for the existence of this random value.  If the code is not present or is not what is expected, reject the request as invalid.

Let's look at how to do this with just 2 lines of code using controls built-in to the ASP.NET MVC framework.

Using Razor syntax in your CSHTML file, the original ChangePassword page may look like the following:


@using (Html.BeginForm("ChangePassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))

{

     <h4>Change your password<h4>
     <hr />
     @Html.ValidationSummary("", new { @class = "text-danger" })
     <div class="form-group">
            @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" })
            <div class="col-md-10">
                   @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" })
            </div>
     </div>
     <div class="form-group">
           @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
           <div class="col-md-10">
                   @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
           </div>
     </div>
     <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                   <input type="submit" class="btn btn-default" value="Change Password" />
            </div>
     </div>



To add a random value to this form, we need to make use of the "AntiForgeryToken()" function that is part of the HtmlHelper class. Just modify the CSHTML file as follows:


@using (Html.BeginForm("ChangePassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))

{
     @Html.AntiForgeryToken()
     <h4>Change your password</h4>
     <hr />

<!-- ... the rest of the form ... -->
}


This function performs two critical tasks. First, it generates a long random value and appends it to the form as a hidden input field named "__RequestVerificationToken". Secondly, it adds a cookie to the page's response. The cookie has the same name and value as the hidden input field, and has the necessary security flags set to prevent it from being accessed by JavaScript.

Now that the random value is part of the form, we need to add the code to automatically validate it on the server. To do this, we need to add the "ValidateAntiForgeryToken" attribute to the ChangePassword action that processes the form POST. The following snippet of code demonstrates this:


[HttpPost]

[ValidateAntiForgeryToken]
public IActionResult ChangePassword(ChangePasswordViewModel model)
{
     if (!ModelState.IsValid) 
     {
             return View(model);
     }
     // ... the rest of the change password processing logic ...
}


The "ValidateAntiForgeryToken" attribute is the last piece needed to mitigate CSRF attacks. This attribute is an IAuthorizationFilter, which means that it will be executed before the Action method of the Controller class is called. During execution, it performs several checks including:

  • Validating that a cookie AND a request parameter named "__RequestVerificationToken" are present.
  • Validating that the values of those inputs are not null or empty.
  • Validating that the two values match. The value is also cryptographically verified to belong to the specific application and logged in user. If any of these conditions fail, the request is rejected.

Why the Solution Works

At this point, you may be asking "Why does this solution work?"

First, by adding the random "__RequestVerificationToken" parameter to the ChangePassword form, an attacker now has a third value that they must submit as part of the request. While this is not difficult at all, it isn’t quite that simple either. The value of the "__RequestVerificationToken" parameter is cryptographically generated by the .NET framework using the application server's machine key. The value is also tied to the specific application and logged in person. As such, the value is unique to every person, meaning that the attacker cannot log in to the application and copy the value of their "__RequestVerificationToken" into the attack page. This effectively prevents token reuse attacks.

Second, the same value is placed into a cookie in the user's browser. Again, while an attacker can add cookies through JavaScript code, they will not have the proper value for the victim.

As a result, even though an attacker can submit requests to our sample Change Password function, they will all be rejected without a proper value for the "__RequestVerificationToken".

Limitations

You should be aware of the limitations of this solution:

  • The solution requires that cookies be accepted in the browser. While this generally isn’t an issue, this solution will fail if a person has disabled cookies.
  • The solution only works with POST requests. The HtmlHelper class always generates an HTML input field and as a result the token cannot be easily added to links which generate GET requests. That said, you should not be using GET requests to perform state-changing actions, so this should not be an issue either.
  • The solution can be bypassed if any Cross-Site Scripting (XSS) vulnerabilities exist within your application. XSS vulnerabilities can be used to request the page containing the sensitive protected form and parse out the token. At this point, the attacker has the secret value and makes the request as usual. In short, ensure you do not have any XSS vulnerabilities :-).

Summary

Cross-Site Request Forgery is a well-established form of attack that takes advantage of the way the web works to trick victims into performing actions that they didn’t intend. As a result, an attacker may be able to change the victim's password in an application or transfer money from the victim's bank account to the attacker's. Any request (or series of requests) that can be made by a person's browser can be scripted into this kind of attack. Luckily, the solution for mitigating these attacks is quite simple for ASP.NET MVC developers – everything is right there in the framework waiting for you to use it.

In future blog posts, I'll cover how to apply these same techniques to applications that rely on JavaScript and AJAX technologies, as these requests are no more safe than normal forms.

Until next time – be safe and happy hacking/developing.

Helpful Resources

Learn More About Application Security 

Get the tools you need to build secure software. Learn more about our instructor-led training and eLearning

small_securecodingfundamentals-703050

 

Topics: Application Security, Software Security, Software Assurance, Secure Coding Fundamentals