Preventing CSRF (Cross-Site Request Forgery) Attack in PHP Tutorial

In this tutorial, you can learn to prevent your site from CSRF or Cross-site Request Forgery Attacks using PHP Language. The tutorial aims to provide students and beginners with a reference for learning to build a secure web application using PHP Language for the server-side script. Here, I will be providing a simple web page script that demonstrates the main Idea for preventing the site from CSRF Attacks.

What is CSRF Attack?

The CSRF or Cross-site Request Forgery is a type of attack that involves a hacker compelling you to take action against a website you are currently logged in to. Failure of protecting the site from CSRF attacks gives an attacker the ability to partially get around the same origin policy, which is meant to stop various websites from interfering with one another. An attacker can cause your client to carry out an unintentional action.

How to Prevent CSRF Attacks in PHP?

We can prevent the CSRF Attack by implementing a CSRF Token validation. The CSRF Token is a generated random token that will be used for validating the form data or the request was exactly submitted from the right form page of the client side. We can set up also the CSRF Token that regenerates every time the form page loads to make it more secure and we can also add an expiration so that the attackers will only have a limited and short time to bypass it. Check out the sample web page that I created and provided below to have a better idea of how you can prevent your site from CSRF Attach using PHP.

Sample Web Page

The scripts below result in a simple web application that contains a sample form that requires the user's basic information and contacts. The form data contains a generated CSRF Token to allow the server API to validate the request. The application also has a sample file script that simulates form requests without the CSRF Token.

Form Page Interface

Here is the PHP file script named index.php. It contains a combined PHP and HTML code which are the PHP Generated CSRF Token stored in SESSION Global Variable and the page/form layout elements.

  1. <?php
  2. session_start();
  3. if(isset($_SESSION['csrf_token']['sample-form']))
  4.         unset($_SESSION['csrf_token']['sample-form']);
  5. $_SESSION['csrf_token']['sample-form']['token'] = password_hash(uniqid(mt_rand(), true),PASSWORD_DEFAULT);
  6. $_SESSION['csrf_token']['sample-form']['expiry'] = time() + 3600 ;
  7. ?>
  8. <!DOCTYPE html>
  9. <html lang="en">
  10.         <meta charset="UTF-8">
  11.         <meta http-equiv="X-UA-Compatible" content="IE=edge">
  12.         <meta name="viewport" content="width=device-width, initial-scale=1.0">
  13.         <title>CSRF Token Protection in PHP</title>
  14.         <link rel="preconnect" href="https://fonts.googleapis.com">
  15.         <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  16.         <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
  17.         <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
  18.         <link rel="stylesheet" href="style.css">
  19.         <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
  20. </head>
  21.         <div class="content-md-lg py-3">
  22.                 <div class="col-lg-8 col-md-10 col-sm-12 col-12 mx-auto">
  23.                         <div class="page-title">CSRF Token Protection in PHP</div>
  24.                         <hr style="margin:auto; width:25px" class="border-light opacity-100">
  25.                 </div>
  26.                 <div class="container-lg">
  27.                         <div class="row py-3 my-5">
  28.                                 <div class="col-lg-5 col-md-5 col-sm-10 col-12 mx-auto">
  29.                                         <!-- Sample Web Form -->
  30.                                         <div class="card rounded-0">
  31.                                                 <div class="card-header rounded-0">
  32.                                                         <div class="card-title fw-bold">Sample Web Form</div>
  33.                                                 </div>
  34.                                                 <div class="card-body rounded-0 py-2">
  35.                                                         <div class="container-fluid">
  36.                                                                 <form action="api.php" method="POST">
  37.                                                                         <input type="hidden" name="token" value="<?= $_SESSION['csrf_token']['sample-form']['token'] ?>">
  38.                                                                         <div class="mb-3">
  39.                                                                                 <label for="fullname" class="form-label">Fullname</label>
  40.                                                                                 <input type="text" id="fullname" name="fullname" class="form-control rounded-0" required="required">
  41.                                                                         </div>
  42.                                                                         <div class="mb-3">
  43.                                                                                 <label for="email" class="form-label">Email</label>
  44.                                                                                 <input type="email" id="email" name="email" class="form-control rounded-0" required="required">
  45.                                                                         </div>
  46.                                                                         <div class="mb-3">
  47.                                                                                 <label for="phone" class="form-label">Phone</label>
  48.                                                                                 <input type="text" id="phone" name="phone" class="form-control rounded-0" required="required">
  49.                                                                         </div>
  50.                                                                         <div class="mb-3">
  51.                                                                                 <div class="mx-auto col-lg-6 col-md-8 col-sm-12">
  52.                                                                                         <button class="btn btn-sm rounded-pill btn-primary w-100">Submit</button>
  53.                                                                                 </div>
  54.                                                                                 <div class="mx-auto col-lg-6 col-md-8 col-sm-12 text-center">
  55.                                                                                         <a href="./insecure-request.php" target="_blank">Simulate Attacker Request</a>
  56.                                                                                 </div>
  57.                                                                         </div>
  58.                                                                 </form>
  59.                                                         </div>
  60.                                                 </div>
  61.                                         </div>
  62.                                         <!-- Sample Web Form -->
  63.                                 </div>
  64.                         </div>
  65.                 </div>
  66.         </div>
  67.         <footer class="footer py-2 position-fixed bg-black text-light bottom-0 w-100">
  68.                 <div class="text-center"><b>This tutorial program is developed by:</b></div>
  69.                 <div class="text-center"><a href="mailto:[email protected]"><b>[email protected]</b></a></div>
  70.         </footer>
  71. </body>
  72. </html>

API

Here is the PHP file script known as api.php. It is the file script where the form data is being processed or action is being made. The script contains the security check validation with 3 levels. The first level is for checking if there's a valid CSRF token. The second level checks if the request token matches the valid token, and the last level check if the token is already expired.

  1. <?php
  2. //First Level Security Check
  3. if(isset($_SESSION['csrf_token']['sample-form'])){
  4.         //First Level Security Check is Validated
  5.        
  6.         // Valid Token Data
  7.         $valid_token = $_SESSION['csrf_token']['sample-form']['token'];
  8.         $token_expiry = $_SESSION['csrf_token']['sample-form']['expiry'];
  9.  
  10.         // POST Data
  11.         $token = $_POST['token'];
  12.  
  13.         // Escape Form Fields Value
  14.         $fullname = htmlspecialchars($_POST['fullname']);
  15.         $email = htmlspecialchars($_POST['email']);
  16.         $phone = htmlspecialchars($_POST['phone']);
  17.  
  18.         //Second Level Security Check
  19.         if($token === $valid_token){
  20.                 // Second Level Security Check Validated
  21.  
  22.                 //Third Level Security Check
  23.                 if(time() < $token_expiry){
  24.                         // Third Level Security Check Validated
  25.                         print("<h1>Request has passed the validation.</h1>");
  26.                         print("<h3>Submitted Data</h2>");
  27.                         print("<div><b>Fullname</b>: ${fullname}<div>");
  28.                         print("<div><b>Email</b>: ${email}<div>");
  29.                         print("<div><b>Phone</b>: ${phone}<div>");
  30.                 }else{
  31.                         // Failed in Third Level Security
  32.                         print_r("Third Level Security Check Failed: Token has expired. Kindly refresh the form page and retry your submission.");
  33.                 }
  34.         }else{
  35.                 // Failed in Second Level Security
  36.                 print_r("Second Level Security Check Failed: Server does not allow you to proceed to the process/action.");
  37.         }
  38. }else{
  39.         // Failed in First Level Security
  40.         print_r("First Level Security Check Failed: Server does not allow you to proceed to the process/action.");
  41. }
  42.  
  43. print("<br/><a href='./'>Back to Form Page</a>");
  44. ?>

Sample Bypass Script

Here is an example PHP file script known as insecure-request.php. It contains the sample script that bypasses the form request without CSRF Token.

  1. <?php
  2. $ch = curl_init();
  3.  
  4. curl_setopt($ch, CURLOPT_URL,"http://localhost/php-csrf-protection/api.php");
  5. curl_setopt($ch, CURLOPT_POST, 1);
  6. curl_setopt($ch, CURLOPT_POSTFIELDS,
  7.                         "fullname=Malicious Value&email=Malicious Value&phone=Malicious Value");
  8. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  9. $output = curl_exec($ch);
  10. echo($output);
  11. ?>

Snapshots

Here are some of the web page snapshots as the overall result of the scripts I provided above.

Form Page Interface

Preventing CSRF Attack in PHP

Secure Request-Response Message

Preventing CSRF Attack in PHP

Insecure Request-Response Message

Preventing CSRF Attack in PHP

Invalid CSRF Token

Preventing CSRF Attack in PHP

Expired CSRF Token

Preventing CSRF Attack in PHP

DEMO VIDEO

There you go! I have also provided the complete source code zip file of the scripts that I provided above on this site and it is free to download. You can download it by clicking the download button located below this tutorial's content. Feel free to download and modify it to do some experiments.

That's it! I hope Preventing CSRF (Cross-Site Request Forgery) Attack in PHP Tutorial will help you with what you are looking for and will be useful for your current and future PHP Projects.

Explore more on this website for more Tutorials and Free Source Codes.

Happy Coding =)

Comments

Very good information. Thank you for sharing the examples, they make understanding process very simple.

Constructive criticism: the collapsed code areas (syntax highlighting) make page compact but very, very hard to use. Maybe you should try setting for full length?

Cheers and please keep the tutorials flowing:)

Add new comment