In this tutorial you’ll learn how to implement HTML anti-CSRF tokens in PHP.
Contents
- What is CSRF?
- How to defend against CSRF attacks
- How to create anti-CSRF tokens
- How to verify anti-CSRF tokens
What is CSRF?
CSRF attacks are a dangerous type of web attack against end-users performed through malicious links. Attackers can send CSRF links using emails, social media content, web pages, forums, blog comments, JavaScript and so on.
The link sends the user to a vulnerable website where the user is already logged in. The goal of this link is to make the user perform unwanted operations on the website.
CSRF attacks work only on vulnerable websites. So, if you are building a website, you must make sure to protect it.
In this tutorial you will learn how to implement the most common defense technique: anti-CSRF tokens.
(P.S. If you want to know more about CSRF, take a look at this detailed introduction from my professional Security course.)
How to defend against CSRF attacks
There are a few different ways to protect your website from CSRF attacks.
Including: HTML tokens, JavaScript tokens, Cookie tokens, and HTTP headers.
In this tutorial we are focusing on HTML anti-CSRF tokens, which are the most used defense strategy.
Here’s how anti-CSRF tokens work:
- First, you need to identify the pages where users can send data from. For example, pages containing HTML forms.
- In each of those pages, you need to create a random token. You need to save this token in the user’s Session, and you also need to send it together with the request data (for example, including it into the form as a hidden input).
- When reading the request back, you must check that the request token and the Session token are equal.
This technique effectively stops CSRF attacks because CSRF links cannot include the correct token.
Now, let’s see how to implement this technique.
How to create anti-CSRF tokens
Let’s say that you have a website with registered users.
Users can change their email addresses from the email.php page, which contains the following HTML form:
<form>
New email address: <input type="email" name="email_address"><br>
<input type="submit" value="Send">
</form>
This simple form is vulnerable to CSRF attacks.
An attacker can forge a link to the email.php page with the email_address request parameter set, like this: “email_address=attacker@email.com”.
Assuming that the victim user is authenticated to the website, the link will redirect the user to the email.php page and change its email address to the one chosen by the attacker.
So, how can you protect this form with an anti-CSRF token?
First, in the email.php page where you create the form, you need to create a random token.
You can use the PHP random_bytes() function to generate a random number, and bin2hex() to turn that number into a string:
// Generate a random token.
$token = bin2hex(random_bytes(16));
Then, you need to save this token in the user’s Session and also as an input element of the HTML form:
// Start the Session.
session_start();
// Generate the token.
$token = bin2hex(random_bytes(16));
// Save the token in the user Session.
$_SESSION['CSRF token'] = $token;
// Add the token as form input.
echo
'
<form>
New email address: <input type="email" name="email_address"><br>
<input type="hidden" name="csrf_token" value="' . htmlentities($token, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '">
<input type="submit" value="Send">
</form>
';
If you are exchanging data using JSON, you can include the token directly into the JSON packet.
How to verify anti-CSRF tokens
Everytime the user opens the form page, a new token is created and sent together with the email change request.
Before allowing the email change, you need to make sure that the Session token and the request token are the same.
Here’s how to do that:
// Start the Session.
session_start();
// Read the request variables.
$email_address = $_REQUEST['email_address'];
$csrf_token = $_REQUEST['csrf_token'];
// Check if the submitted token matches the one inside $_SESSION.
if ($csrf_token === $_SESSION['CSRF token']) {
echo 'Token valid. Updating your data.<br>';
updateUserEmail($email_address);
}
else {
echo 'Token invalid. Operation not allowed.<br>';
}
This technique effectively protects your site from CSRF attacks., because attackers cannot forge links with the correct token (which is only known to the user’s browser and the web server).
Note: it is better to use strict comparison to check that the tokens are exactly the same.
Conclusion
In this tutorial you learned how to implement anti-CSRF tokens quickly.
Here are the key concepts to remember:
- You need to protect pages that receive user’s data.
- For each user request, you need to create a random token and save it in the user’s Session and include it in the request data.
- Each user request must include the same token in the request string and in the user’s Session.
Do you have any questions? Leave a comment and I’ll get back to you.
Wouldn’t it be better (more secure) using ‘hash_equals’ for tokens comparison?
Hello Tom, thank you for the interesting question.
hash_equals() is indeed more secure when the user (also a potential attacker) does not know the length of the correct string (such as a password or a keyphrase).
In this specific case, however, the token’s length is not a secret as the token can be easily seen on the source code, and it’s length is always the same.
This may seem a security issue, but it’s not really relevant.
The CSRF protection described in this tutorial is good but basic. There are a few ways to make it more secure, for example by destroying the Session token after a failed attempt (when an invalid token is sent).
In that case, the attacker would have a one-shot try to guess the token which is, basically, impossible to do.
Another protection technique is to check the browser’s signature and/or the IP address, making an attack even less likely.
Making the token’s length variable would not really increase its security.
Moreover, even if variable-length tokens were used, forcing the attacker to have only a single shot to guess it would make timing attacks useless anyway.
Let me know if you have any questions.
Hi Alex,
I am taking your Security course and the Leave a Comment function is not working.
I understand how to use anti-CSRF for a form and and sending the token in a hidden value.
I am having trouble understanding how to use a anti-CRSF for a URL that is sent from one of my pages to another page.
For example–
<a href="update-movie.php?up_id=>Edit
Do I just use urlencode()?
<a href="update-movie.php?up_id=>Edit
Or store the anti-CSRF in a session token?
I do sanitizing at the receiving end with the data from the $_GET.
Or maybe I am missing the point.
Thank you for your clarification.
Larry
Hello Larry,
The procedure is basically the same as with POST forms. The anti-CSRF token goes into the URL like this:
‘href=”update-movie.php?up_id=1&csrf_token=’ . htmlentities($token, ENT_QUOTES | ENT_HTML5, ‘UTF-8’)
At the receiving end the procedure is just like in the post’s example. Just make sure to use $_REQUEST or $_GET.