Search code examples
phpformscsrfcsrf-protection

CSRF for internal $_SERVER["REQUEST_METHOD"] form


Trying to implement CSRF token for the first time ever ...

I have an internal $_SERVER["REQUEST_METHOD"] form ...

Most of the stuff online talks about having it done via _$POST php form ...

So ... after reading all that, I've reasoned that since I have it as in internal part, that I should set the token there ...

So this is what I did ... but clearly I'm misunderstanding something here because it's not working ... not just misunderstanding ... I'm also not sure what I'm supposed to do ... so ... if you could explain what I'm supposed to do so I can set it correctly ... I would really appreciate it

<?php
session_start(); //allows use of session variables

  $token = md5(uniqid(rand(), TRUE));
  $_SESSION['token'] = $token;
  $_SESSION['token_time'] = time();


if ($_SERVER["REQUEST_METHOD"] == "POST") {

   if (empty($_POST["first-name"])) {
     $firstNameErr = "First name is required";
   } else {
     $first_name = test_input($_POST["first-name"]);
   }

    if (empty($_POST["last-name"])) {
     $lastNameErr = "Last name is required";
   } else {
     $last_name = test_input($_POST["last-name"]);
   }

   if (empty($_POST["email"])) {
     $emailErr = "Email is required";
   } else {
     $email = test_input($_POST["email"]);
   }

   if (empty($_POST["message"])) {
     $messageErr = "Message is required";
   } else {
     $message = test_input($_POST["message"]);
   }


   if(isset($first_name) && isset($last_name) && isset($email) && isset($message) && isset($token))
   {
     $_SESSION['first_name'] = $first_name;
     $_SESSION['last_name'] = $last_name;
     $_SESSION['email'] = $email;
     $_SESSION['message'] = $message;
     $_SESSION['token'] = $token;
     header("Location: SessionsCRSF.php");
   }
}

function test_input($data) {
   $data = trim($data);
   $data = stripslashes($data);
   $data = htmlspecialchars($data);
   return $data;
}
?>

On the same page is the form (placing a snippet, but you get the idea):

<form class="ui form"  method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

  <input type="hidden" name="token" value="<?php echo $token; ?>" />

  <div class="field">
    <label>First Name</label>
    <input name="first-name" id="first-name" placeholder="First Name" type="text" value="<?php if(isset($first_name)) { echo $first_name; }?>">
     <?php if(isset($firstNameErr)) print ('<span class="error">* ' . $firstNameErr . '</span>'); ?>
  </div>

(...)

</form>

But I don't know how to test it via SessionsCRSF.php form that you see listed in the header ...

Here's the snippet of SessionsCRSF.php form since the rest is just PHPMailer stuff:

<?php

session_start();

    if ($_POST['token'] == $_SESSION['token'])
    {
      die('working, yay?'); 
    }


$first_name = $_SESSION['first-name'];
$last_name = $_SESSION['last-name'];
$email = $_SESSION['email'];
$message = nl2br($_SESSION['message']);

require 'PHPMailerAutoload.php';

$mail = new PHPMailer;

EDITED

Ok, so .. this is what I have so far that works:

<?php
session_start(); //allows use of session variables

if ($_SERVER["REQUEST_METHOD"] == "POST" && $_SESSION['token'] == $_POST['token'] && !empty($_SESSION['token'])) {

   if (empty($_POST["first-name"])) {
     $firstNameErr = "First name is required";
   } else {
     $first_name = test_input($_POST["first-name"]);
   }

    if (empty($_POST["last-name"])) {
     $lastNameErr = "Last name is required";
   } else {
     $last_name = test_input($_POST["last-name"]);
   }

   if (empty($_POST["email"])) {
     $emailErr = "Email is required";
   } else {
     $email = test_input($_POST["email"]);
   }

   if (empty($_POST["message"])) {
     $messageErr = "Message is required";
   } else {
     $message = test_input($_POST["message"]);
   }

   if(isset($first_name) && isset($last_name) && isset($email) && isset($message))
   {
     $_SESSION['first_name'] = $first_name;
     $_SESSION['last_name'] = $last_name;
     $_SESSION['email'] = $email;
     $_SESSION['message'] = $message;
     header("Location: contact9SessionsCRSF2.php");
     exit; 
   }
}
else {
  $token = md5(uniqid(rand(), TRUE));
  $_SESSION['token'] = $token;
  $_SESSION['token_time'] = time();
}

function test_input($data) {
   $data = trim($data);
   $data = stripslashes($data);
   $data = htmlspecialchars($data);
   return $data;
}
?>

Solution

  • You have your page, which contains your $_SESSION value, and you have your INPUT value in the hidden field in your HTML form which are the same value.

    Page 1:
    [SESSION] ... [INPUT]
    

    You then submit the form to the destination address, here Page 2, and then check that your submitted hidden field value is equal to the $_SESSION value, which is not passed by the input form field.

    This input value is found from the $_POST array.

    Page 2: 
    [SESSION] ... [POST]
    

    Specifically to your problem, your submitting your form to the PHP_SELF page (generally PHP_SELF is a bad variable to use and it is not encouraged), which is the same page. BUT, your checking of the data is done on the sessionCRSF.php page, so what you should do is update your form to put:

    <form action='sessionCRSF.php' ... > 
    

    And then at the top of that page your check of the token value should succeed.


    You seem to show some confusion as to SERVER request methods, there are GET/POST/PUT and other ones and if you have an

    I have an internal $_SERVER["REQUEST_METHOD"] form

    what actually is this? If it's a form then it's a GET/POST method, and that's not internal, as far as I am aware - even if it's on the same server it's from one page to another, so the concepts above apply.

    Solution

    From discussion and clarification I can now see the solution:

    In the If statements that handle the form submission, you do not have any handling of the token posted value, $_POST['token']. So you need to add a statement to handle that, in its simplest form, at the point of the code where you set the values for the $_SESSION form data, add a line:

    $_SESSION['posted_token'] = $_POST['token'];
    

    That's all you need, then in the sessionCRSF.php you compare these two values so you then have:

     if ($_SESSION['posted_token'] == $_SESSION['token'])
        {
          die('working, yay?'); 
        }
    

    (You had me confused earlier with reference to "internal" forms etc.!)

    Edit :

    You need to set the original $_SESSION['token'] when the form is generated, NOT when the form is submitted, your code currently creates the $_SESSION['token'] value on every iteration, and because the page self refers it means that the token values -- being random -- will never ever be the same. You need to set

    if (form submitted){
       save submitted POSTED token
    }
    else{
        /// form not submitted
        generate a token and save to session
    } 
    

    Do not do both of these things on the same iteration!!

    Code

    Because sometimes it's easier just to see it....

    Code has been updated to compare the tokens on POST submission and therefore bypassing the entire need for sending the data to another page to be compared.

    <?php
    session_start(); //allows use of session variables
    
    
    if ($_SERVER["REQUEST_METHOD"] == "POST" && 
    $_SESSION['token'] == $_POST['token'] && !empty($_SESSION['token'])) {
    
       if (empty($_POST["first-name"])) {
         $firstNameErr = "First name is required";
       } else {
         $first_name = test_input($_POST["first-name"]);
       }
    
        ...etc...
    
       if(isset($first_name) && isset($last_name) && isset($email) && isset($message))
       {
         $_SESSION['first_name'] = $first_name;
         $_SESSION['last_name'] = $last_name;
         $_SESSION['email'] = $email;
         $_SESSION['message'] = $message;
    
         header("Location: SessionsCRSF.php");
         exit; //ALWAYS end execution after sending location headers. 
       }
    }
    else {
      $token = md5(uniqid(rand(), TRUE));
      $_SESSION['token'] = $token;
      $_SESSION['token_time'] = time();
    }