Understanding & Preventing CSRF Attacks

What is Cross-Site Request Forgery (CSRF)?

Cross-Site Request Forgery (CSRF) (pronounced sea-surf) is a common attack targeted at web applications.  The attack occurs when a website allows a browser to perform a sensitive action but doesn't validate that the user actually issued that action.   This provides the opportunity for an attacker to submit a request to the target web application unbeknownst to the user.  

Potential target sites include social media, webmail clients, online banking, and network device administration interfaces. 

Key Concepts 

  • While a user is logged into the target site, an attacker can coerce the user’s browser to perform actions on the target website
  • The vulnerability exists in the affected web application, not the victim’s browser or the site hosting the CSRF

 

How does the attack work?

The attacker is exploiting how the target web application manages authentication.  For the exploit to work , the victim must be authenticated against the target site. For instance, let’s say bank.com has online banking that is vulnerable.  If the user visits a page containing a CSRF attack at bank.com but they are not currently logged in, then nothing happens.  If the user has logged into the target site, an attacker can coerce the victim’s browser to perform actions on the target website.

GET Requests

Let's say the bank.com is designed to use GET requests for performing actions such as funds transfers (this is bad design but let's use it for sake of demonstration).    If the attacker is aware of how to craft the exploit URL, then they can use the img element to get the browser silently load the page.

Below is an example of an HTML image element containing an exploit URL:

<img src="http://bank.com/app/transferFunds?amount=500&destination=12345">

This is usually done with one of the following techniques:

  • sending an unsolicited email with HTML content
  • planting an exploit URL or script on pages that are likely to be visited by the victim while they are also doing online banking

The exploit URL can be disguised in a number of ways.

As an ordinary link, encouraging the victim to click it:

<a href="https://bank.com/app/transferFunds?amount=500&destination=12345">
Reset Password
</a>

Or as an invisible fake image:

<img src="https://bank.com/app/transferFunds?amount=500&destination=12345" style="display: none;">

If this image tag were included in the email, the user wouldn't see anything. However, the browser will still submit the request to the target site.

POST Requests

A common misconception is that only allowing HTTP POST requests will prevent CSRF attacks.  The attacker can create a form using HTML or JavaScript and use autosubmit functionality to submit the POST request without requiring the user to click on a submit button.

Here is an example using an HTML form on the attacker’s site

https://attack.com/csrf.html

<body onload="document.frames[0].submit()">
<form action="https://bankcom/app/transferFunds" method="POST">
<input name="amount" value="500">
<input name="account" value="12345">
</form>
</body>

The attacker would use the following iframe element to silently load the auto-submitting form:

<iframe style="display: none;" src="https://attack.com/csrf.html"></iframe>

The diagram below illustrates the request flow for a CSRF attack using POST requests.

 

How can I protect my site against CSRF attacks?

The most common approach is to use randomly generated security tokens called nonces to validate that the request was actually made by the user.

In PHP, we can generate a unique token and insert it into the session array like below:

$_SESSION["csrf"] = uniqid(mt_rand(),TRUE);

We can then use this value when generating a form or link.  

<form action="https://bank.com/app/transferFunds" method="POST">
    <input type="hidden" name="token" value="85457948256f9764b81dbc2.31735476">
    <!-- other form fields -->
    <!-- submit button -->
</form>

When processing the submitted form, we must first check that the token is the same as the one we generated:

$token = isset($_POST['token']) ? $_POST['token'] : '';
$valid = !empty($token) && ($token === $_SESSION['csrf']);

if ($valid)
{
    // Legitimate request 
}
else
{
	// CSRF attempt
}