Cross-Site Request Forgery is an attack in which a user is tricked into performing actions on another site by inadvertently clicking a link or a submitting a form. It often called CSRF, or sometimes XSRF, for short.
It gets its long name from:
CSRF attacks are especially powerful if the target site has previously authenticated the user's browser -- in other words, if a user has already logged into the target site through the same browser that subsequently loads the attacking site. Whenever any request is sent to a website, the browser sends all stored cookies for that website along with the request. If those cookies include something like a "logged_in=true" setting, then the request could be honored by the target server as a request from a currently logged in user. The exploit takes advantage of the fact that the server trusts the user's browser.
Typically attackers use CSRF attacks to make state-changing requests. The request performs some action, but the attacker will not be able to see the results of the request since it is still a request by the user's browser.
The simplest CSRF attack is simply to trick a user into making a GET
request to a specific URL. This can done by putting the URL into a deceptively named link. The link could be put in a blog comment (lots of WordPress exploits have used this technique), a post on a web forum, or in a phishing email.
<a href="http://hackermag.com/best_hacker/vote/48576">View PDF</a>
The link hides its true action, and it may fool some users. But it is not a very sophisticated attack because it still requires the user to click the link to activate the action.
A more sophisticated version places the URL somewhere that obviates the need for user action, such as the image source attribute of an image tag.
<img src="http://hackermag.com/best_hacker/vote/48576" />
When a page is loaded, the browser automatically makes individual requests to retrieve all images in the HTML. When a user views a forum page with the above image in a user post, the browser issues a GET
request to that URL. The user does not need to click any link; the damage is done in the process of the browser interpreting the HTML.
The biggest danger from a CSRF attack comes when the target URL links to a page on a website which requires user authentication and the user's browser still retains their previous authentication state. The attack takes advantage of the browser's authenticated state.
Imagine that a user logs in to their bank website. The bank website sets a cookie in the user's browser to indicate that their access has been authorized. Future requests to any page on the bank's website will include that cookie as proof of authorization.
It works a lot like going to a concert and getting a wristband when you pay or present a ticket. The wristband is what proves your authorized status. Wristband: authorized to be inside. No wristband: not authorized, should not be granted access.
Imagine that when the user is done with their banking, they close the browser window but they do not click "Log out". The user's browser still has the bank authorization cookie -- it's still wearing the wristband.
If an attacker can trick the user into making a request to the bank's website, that request will send the cookie with the authorization and the request be considered authenticated. As far as the bank can tell, this is just another request from the user's browser.
<img src="https://bank.com/transfer?amount=10000&to_account=2468013579" />
If a user loads a page with the above image tag, it will trigger a GET
request to the URL specified in src
. In this scenario, an unauthenticated user will get the bank.com
's login page. But a previously authenticated user could get access if the request is accepted by bank.com
.
There are many actions that a CSRF attack can take. Some of the more common ones are:
The best defense against CSRF attacks which take the form of GET
requests is to disallow GET
requests for key actions, especially actions which "change state" in some way.
It is considered a best practice to only use GET
requests for retrieving data, not for actions which make changes. Instead, use POST
requests (such as form submissions) for actions which make changes. (See also HTTP Request Types (GET vs. POST))
Links and image tag sources are always GET
request. Making all GET
requests harmless removes a major pathway for CSRF attacks.
GET
requests are not the only way to trigger a CSRF attack. An attacker can forge a form. Walking through it step-by-step will help explain how such an attack is structured.
Imagine that an attacker does some research to see what a legitimate bank looks like.
<html>
<head>
<title>Bank Website</title>
</head>
<body>
<form action="http://bank.com/transfer" method="POST" name="bank_form">
<input type="text" name="amount" value="" />
<input type="text" name="to_account" value="" />
<input type="submit" value="submit" />
</form>
</body>
</html>
The attacker can then put that HTML into another page and then trick a user into submitting the form. This would pass any "allow POST requests only" protections which have been put in placeāit is a POST
request. The request would send any authorization cookie, just like a GET
request does.
The attacker only needs to find a way to trick a user into submitting this form. They can do that by using CSS to hide the form, and then using JavaScript to submit the form when the page loads.
<html>
<head>
<title>Fake Form</title>
</head>
<body onload="document.bank_form.submit()">
<form action="http://bank.com/transfer" method="POST" name="bank_form" style="display: none;">
<input type="text" name="amount" value="10000" />
<input type="text" name="to_account" value="2468013579" />
</form>
</body>
</html>
Notice that the body has an onload
attribute which will submit the form immediately and the form has a style
attribute which hides it.
It does not require the user to click "Submit", in fact, there is not even a submit button on the form. Convincing a user to visit this page is enough to trigger the request. And that is easily done, such as with a fake link in a phishing email.
There is one problem with this attack so far. The form will submit and then the user will see the results of the submission. It might be a page on the bank's website which says, "Funds transfer complete". If a user saw that, they would be suspicious and probably call their bank to void the transaction.
An attacker will also need to hide the results. Forms allow sending their results to an iframe
using a target
attribute.
<html>
<head>
<title>Fake Form</title>
</head>
<body onload="document.bank_form.submit()">
<form action="http://bank.com/transfer" method="POST" name="bank_form" style="display: none;" target="hidden_results" >
<input type="text" name="amount" value="10000" />
<input type="text" name="to_account" value="2468013579" />
</form>
<iframe name="hidden_results" style="display: none;"></iframe>
</body>
</html>
Notice the addition of the iframe
tag (with display: none
) and the form
tag has a new target
attribute.
If the attacker hides an iframe
and sends the form results there, the user will have no hint that the CSRF attack has occurred.
One easy defense is to verify that the HTTP referer in the POST
request is originating from the correct domain. Any legitimate form submission should have a referer and that referer should be the expected one for the form page. The referer is spoofable, but it adds another challenge for an attacker to overcome. And each layer of defense adds to a Defense in depth strategy.
<?php
function request_is_same_domain() {
if(!isset($_SERVER['HTTP_REFERER'])) { return false; }
$referer_host = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
return ($referer_host === $_SERVER['HTTP_HOST']);
}
?>
The strongest defense against CSRF attacks is the use of "CSRF Tokens" which is also known as the "synchronizer token pattern".
The synchronizer token pattern can be implemented on forms using the following steps.
<?php
function csrf_token() {
// Requires PHP 7 or later
return bin2hex(random_bytes(64));
}
?>
<?php
function create_csrf_token() {
$token = csrf_token();
$_SESSION['csrf_token'] = $token;
$_SESSION['csrf_token_time'] = time();
return $token;
}
?>
<?php
function csrf_token_tag() {
$token = create_csrf_token();
return '<input type="hidden" name="csrf_token" value="' . $token . '">';
}
?>
In any form:
<?php echo csrf_token_tag(); ?>
<?php
function csrf_token_is_valid() {
if(!isset($_POST['csrf_token'])) { return false; }
if(!isset($_SESSION['csrf_token'])) { return false; }
return ($_POST['csrf_token'] === $_SESSION['csrf_token']);
}
?>
<?php
if(csrf_token_is_valid()) {
// process the form
} else {
// reject the form
}
?>
Generally, the attacker should not be able to see the session token given to the user, nor should they be able to set it. Therefore the attacker cannot predict the correct form token to add to their fake form.
Unless the attacker also can use Cross-Site Scripting (XSS) to gain access to the user's browser cookies. Then they could potentially steal the token information they need to construct a matching form. When putting CSRF protections in place, it is equally important to make sure that XSS protections are in place.
This is a simple CSRF token implementation, and there are more advanced versions. A token could be only valid for a limited time period, such as 5 or 10 minutes. The token generated could be unique for each form on the site. The session token and the form token could be different values which are complimentary. For example, encrypting one string could return the other string.
CSRF tokens are the best protection, but there are a few more which can further strengthen application defenses. Often these defenses are added to the most sensitive actions. These include access-related actions like changing a username, email, password, or security preferences, or high-stakes actions like deleting something significant or transferring money.
For sensitive actions the site could require an addition user action or confirmation in addition to the form submission. For example after the form is submitted, a second "please confirm this action" page could be presented. A CSRF attack would not be able to react to the second page. Some sites add a CAPTCHA image to confirm that there is a user behind an action.
Before changing passwords or security preferences, many sites require a user to re-authenticate by providing their password again. A CSRF attack would not be able to respond to the second request, nor would the attacker know the correct password.