Crazy Horse
asked on
Checking CSRF token within a function
I know I have asked a few questions already about CSRF and there is a great article on it below, but I am struggling to implement in my current scenario.
https://www.experts-exchan ge.com/art icles/2880 2/Improved -Form-Toke ns-to-Guar d-Against- CSRF-and-S creen-Scra pers.html
I want to try it in it's simpler form first as this article is a bit advanced for me.
I am using a nice example posted by Ray as a base:
I already have a function used for registering users and there is some validation. Here is a piece of it.. ($link is used if validation passes and details are entered into the database)
I then have the 2 functions as per Ray's example (I just changed the way the token is generated using openssl_random_pseudo_byte s)
The form:
I want to incorporate the check_form_token function into my register function but when I try the tokens never match and when I echo the session out and the hidden value, they are always different from each other.
Like I said, I know that people have helped me with this before but I think it was in different circumstances.
https://www.experts-exchan
I want to try it in it's simpler form first as this article is a bit advanced for me.
I am using a nice example posted by Ray as a base:
<?php // demo/form_token.php
/**
* Demonstrate the use of a form token
*/
error_reporting(E_ALL);
// FUNCTION TO CREATE AN IDENTITY IN THE FORM
function make_form_token()
{
$token = md5('SALT' . rand(1000,9999) . time());
$_SESSION['form_token'] = $token;
return $token;
}
// FUNCTION TO EVALUATE THE IDENTITY IN THE FORM
function check_form_token()
{
$token = !empty($_SESSION['form_token']) ? $_SESSION['form_token'] : 'X';
$input = !empty($_POST['form_token']) ? $_POST['form_token'] : 'Y';
if ($input == $token) return TRUE;
return FALSE;
}
// THE FORM TOKEN DEPENDS ON THE PHP SESSION
session_start();
if (!empty($_POST))
{
echo "The form token is ";
if ( check_form_token() )
{
echo "valid.";
}
else
{
echo "<b>not</b> valid.";
}
echo "<br>Refresh this screen to resend the data and you can see a form token error.";
}
// PUT UP A FORM TO ILLUSTRATE THE USE OF THE TOKEN
$token = make_form_token();
$html = <<<EOF
<form method="post">
Click GO to test the current the form token.
<input type="hidden" name="form_token" size="40" value="$token" />
<input type="submit" value="Go!" />
</form>
EOF;
echo $html;
I already have a function used for registering users and there is some validation. Here is a piece of it.. ($link is used if validation passes and details are entered into the database)
function user_register($link) {
$message = "";
if(isset($_POST['register'])) {
if(empty($_POST['address1'])) {
$message .= "Please enter your address<br />";
}
if(empty($_POST['region'])) {
$message .= "Please select your region<br/>";
}
if(filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) === false){
$message .= "Email address invalid<br />";
}
I then have the 2 functions as per Ray's example (I just changed the way the token is generated using openssl_random_pseudo_byte
function make_form_token() {
$token = base64_encode(openssl_random_pseudo_bytes(32));
$_SESSION['form_token'] = $token;
return $token;
} // end CSRF token generation
function check_form_token()
{
$token = !empty($_SESSION['form_token']) ? $_SESSION['form_token'] : 'X';
$input = !empty($_POST['form_token']) ? $_POST['form_token'] : 'Y';
if ($input == $token) return TRUE;
return FALSE;
} // end check_form_token
The form:
$token = make_form_token();
<input type="hidden" name="form_token" value="<?php echo $token ?>">
I want to incorporate the check_form_token function into my register function but when I try the tokens never match and when I echo the session out and the hidden value, they are always different from each other.
Like I said, I know that people have helped me with this before but I think it was in different circumstances.
It sounds as if though you are calling user_register($link) after calling make_form_token(). You need to call it before.
if( !empty($_POST) )
{
...
user_register($link);
...
}
...
$token = make_form_token();
...
ASKER
I think I understand what you are saying but just don't know how to do it.
I have a functions.php file and it was like this:
If I swap those around and put the register function before the token generation, it still doesn't work.
I have a functions.php file and it was like this:
function make_form_token() {
$token = base64_encode(openssl_random_pseudo_bytes(32));
$_SESSION['form_token'] = $token;
return $token;
} // end CSRF token generation
function user_register($link) {
$message = "";
if(isset($_POST['register'])) {
if($_SESSION['form_token'] !== $_POST['form_token']) {
echo "Tokens don't match";
}
// more here
If I swap those around and put the register function before the token generation, it still doesn't work.
>> If I swap those around
That will not work because what you have there are function definitions. They do nothing unless you actually call them. What I am saying is that you need to call make_form_token() after you call user_register($link). Look at my previous post -- notice that if there is data POSTed, then the if clause is true and make_form_token() ends up being called last. Why? Because make_form_token() will generate a new token, so you must call it last.
That will not work because what you have there are function definitions. They do nothing unless you actually call them. What I am saying is that you need to call make_form_token() after you call user_register($link). Look at my previous post -- notice that if there is data POSTed, then the if clause is true and make_form_token() ends up being called last. Why? Because make_form_token() will generate a new token, so you must call it last.
ASKER
Currently, I am doing that. Here is the page that calls them:
<div class="col-md-6">
<?php user_register($link); ?>
<h3 class="dark-grey">
Registration
</h3>
<form method="post">
<div class="form-group">
<label> First Name </label>
<input type="text" name="first_name" class="form-control"> </div>
<div class="form-group">
<label> Last Name </label>
<input type="text" name="last_name" class="form-control"> </div>
<div class="form-group">
<label> Contact Number 1 </label>
<input type="number" name="contact_no" class="form-control" placeholder="eg: 0868587186"> </div>
<div class="form-group">
<label> Contact Number 2 </label>
<input type="number" name="contact_no2" class="form-control"> </div>
<div class="form-group">
<label> Address Line 1 </label>
<input type="text" name="address1" class="form-control"> </div>
<div class="form-group">
<label> Address Line 2 </label>
<input type="text" name="address2" class="form-control"> </div>
<div class="form-group">
<label> Address Line 3 </label>
<input type="text" name="address3" class="form-control"> </div>
<div class="form-group">
<label> Region </label>
<select name="region" class="form-control sm-margin-bottom-10">
<option value="">Select Region</option>
<?php location_dropdown($link); ?>
</select>
</div>
<div class="form-group">
<label> Email </label>
<input type="email" name="email" class="form-control"> </div>
<div class="form-group">
<label> Password <a href="#" data-toggle="tooltip" data-placement="right" title="Password must be at least 8 characters and must contain an uppercase & lowercase letter, a number and a special character. "><span class="fa fa-info-circle"></span></a></label>
<input type="password" name="password" class="form-control" id=""> </div>
<div class="form-group">
<label> Repeat Password </label>
<input type="password" name="pass_confirm" class="form-control" id="" value=""> </div>
<div class="col-sm-6">
<input type="checkbox" name="terms" value="Yes" class="checkbox pull-left" /> I agree to the terms and conditions </div>
<input type="text" name="somefield" style="display:none" />
<input type="submit" class="btn btn-danger btn-raised ripple-effect" name="register" value="Register">
<?php $token = make_form_token(); ?>
<input type="hidden" name="form_token" value="<?php echo $token ?>">
</form>
</div>
Try updating your token generator to:
Then, on THAT registration page change <?php $token = make_form_token(); ?> to:
Lastly, in user_register(), change:
function make_form_token($token_name='form_token') {
$token = base64_encode(openssl_random_pseudo_bytes(32));
// use $token_name as the session key
$_SESSION[$token_name] = $token;
return $token;
} // end CSRF token generation
Then, on THAT registration page change <?php $token = make_form_token(); ?> to:
<?php $token = make_form_token('register_token'); ?>
Lastly, in user_register(), change:
if($_SESSION['form_token'] !== $_POST['form_token'])
to:if($_SESSION['register_token'] !== $_POST['form_token'])
ASKER
Thanks. I did everything you said but I am still getting my defined error "Tokens don't match" ?
if(isset($_POST['register'])) {
if($_SESSION['register_token'] !== $_POST['form_token']) {
echo "Tokens don't match";
}
On post ID: 41970367, does the file that contain that code start with session_start(); ?
if($_SESSION['register_token'] !== $_POST['form_token'])
{
// Update your echo statement as shown below. What values do you see for $_SESSION['register_token'] and $_POST['form_token'] ?
echo "Tokens don't match. <br>SESSION=", var_export($_SESSION,true), '<br>POST=', var_export($_POST, true);
}
ASKER
It has:
which contains:
require_once 'functions/functions.php';
which contains:
session_start();
Be sure to add error_reporting(E_ALL) to the logical top of your scripts. It can help catch a multitude of errors!
ASKER
SESSION=array ( 'register_token' => 'RXtIhNO9b6j2vqsov/Qzbu/Xe 0mZvuERNhF cub2Tt7o=' , )
POST=array ( 'first_name' => '', 'last_name' => '', 'contact_no' => '', 'contact_no2' => '', 'address1' => '', 'address2' => '', 'address3' => '', 'region' => '', 'email' => '', 'password' => '', 'pass_confirm' => '', 'somefield' => '', 'register' => 'Register', 'form_token' => 'pLufqBprtsDhVOW5+Tk20zFBF Hn8IJwiHPt +WA72owg=' , )
POST=array ( 'first_name' => '', 'last_name' => '', 'contact_no' => '', 'contact_no2' => '', 'address1' => '', 'address2' => '', 'address3' => '', 'region' => '', 'email' => '', 'password' => '', 'pass_confirm' => '', 'somefield' => '', 'register' => 'Register', 'form_token' => 'pLufqBprtsDhVOW5+Tk20zFBF
ASKER
Thanks, Ray. I keep forgetting to add that. I have inserted it now but no error is displayed when I submit the form, even with the error reporting code in.
ASKER
Should the token generation be a separate function or should the token generation code be inside the user_register($link) function i.e.: have all the code in one function instead of two? Not sure if that would make a difference.
Do you have any ajax requests to that file upon page load (or perhaps upon some field change or form submission)?
ASKER
The page fades in and out after clicking submit. I didn't create the theme, it is one purchased so all the styling and javascript is already built in. I am just adding php to it. So, I don't really know.
ASKER
All my other php works 100% so I don't think there is any Ajax breaking anything.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
>> Should the token generation be a separate function
Yes
>> The page fades in and out after clicking submit
Open your page via Chrome, then right click > Inspect > Network
Click the "Preserve log" checkbox. Then fillout your form and submit. Assuming your file is named "register.php", you should be seeing only one request to "register.php". If you see other requests (POST or GET) to "register.php", then those other requests are regenerating the token.
>>All my other php works 100%
Do all the other php scripts use the token as well?
Yes
>> The page fades in and out after clicking submit
Open your page via Chrome, then right click > Inspect > Network
Click the "Preserve log" checkbox. Then fillout your form and submit. Assuming your file is named "register.php", you should be seeing only one request to "register.php". If you see other requests (POST or GET) to "register.php", then those other requests are regenerating the token.
>>All my other php works 100%
Do all the other php scripts use the token as well?
I think hielo has it right, and maybe has found the issue. In Chrome, if you use "view source" it submits the form again. That's probably a source of error in all kinds of applications, and I wish they didn't do that, but it is what it is, so we have to be aware.
Here's the expected sequence
Client makes GET request to acquire the HTML page with the form
Server response creates the token, stores it in session and in HTML form
Server presents the form to the client
Client submits the form via a POST request
Server finds token information in $_POST, compares it to $_SESSION
... and it's either good or bad.
Here's the expected sequence
Client makes GET request to acquire the HTML page with the form
Server response creates the token, stores it in session and in HTML form
Server presents the form to the client
Client submits the form via a POST request
Server finds token information in $_POST, compares it to $_SESSION
... and it's either good or bad.
ASKER
@ hielo,
I don't really understand this:
The name and initiator column show different things.
The name column starts with register.php and then has a bunch of images, jQuery.min.js, bootstrap.min.js etc.
The initiator column pretty much only has register.php but I see there is also jQuery.min.js
Does that help?
I don't really understand this:
If you see other requests (POST or GET) to "register.php", then those other requests are regenerating the token.
The name and initiator column show different things.
The name column starts with register.php and then has a bunch of images, jQuery.min.js, bootstrap.min.js etc.
The initiator column pretty much only has register.php but I see there is also jQuery.min.js
Does that help?
ASKER
To answer your question, no, I don't have this token script used anywhere else. This is the first form I am trying it on but I do want to use it on all my forms.
ASKER
I'll have to pick this up again tomorrow afternoon. Sadly, I have to be up really early for work tomorrow so I had better get some rest!
ASKER
Okay, so I started from scratch with a blank page and did this:
This seems to work as expected. I did exactly the same thing on my register.php page and it stops working whereby the tokens never match. Now what do I do?
<?php
error_reporting(E_ALL);
require_once 'functions/functions.php';
if(isset($_POST['form_token'])) {
if($_POST['form_token'] == $_SESSION['token']) {
echo "tokens match";
} else {
echo "Session = " . $_SESSION['token'] . " & POST = " . $_POST['form_token'];
}
}
$token = base64_encode(openssl_random_pseudo_bytes(32));
$_SESSION['token'] = $token;
?>
<html>
<head>
</head>
<body>
<form method="post">
<input type="hidden" name="form_token" value="<?php echo $token ?>">
<input type="submit" name="submit" value="Submit">
</form>
</body>
</html>
This seems to work as expected. I did exactly the same thing on my register.php page and it stops working whereby the tokens never match. Now what do I do?
did exactly the same thing on my register.php pageShort answer: You probably did not do exactly the same thing!
Maybe show us the code that is failing, please? This is such a well-understood design pattern that we will probably be able to find the issue right away.
ASKER
Is it not happening because of what hielo suggested?
Anyway, here is the code that I think is exactly the same :P
Click the "Preserve log" checkbox. Then fillout your form and submit. Assuming your file is named "register.php", you should be seeing only one request to "register.php". If you see other requests (POST or GET) to "register.php", then those other requests are regenerating the token.
Anyway, here is the code that I think is exactly the same :P
<?php
error_reporting(E_ALL);
require_once 'functions/functions.php';
if(isset($_POST['form_token'])) {
if($_POST['form_token'] == $_SESSION['token']) {
echo "tokens match";
} else {
echo "Session = " . $_SESSION['token'] . " & POST = " . $_POST['form_token'];
}
}
$token = base64_encode(openssl_random_pseudo_bytes(32));
$_SESSION['token'] = $token;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title> My site</title>
<meta name="generator" content="#" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/themify-icons.css" rel="stylesheet">
<link href="css/font-awesome.css" rel="stylesheet">
<link href="owl.carousel/assets/owl.carousel.css" rel="stylesheet">
<link href="css/animate.min.css" rel="stylesheet">
<link href="css/animsition.css" rel="stylesheet">
<link href="css/plugins.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js">
</script>
<![endif]-->
<link rel="shortcut icon" href="#">
<link rel="apple-touch-icon" href="#">
<link rel="apple-touch-icon" sizes="72x72" href="#">
<link rel="apple-touch-icon" sizes="114x114" href="#"> </head>
<body>
<div class="site-wrapper animsition" data-animsition-in="fade-in" data-animsition-out="fade-out">
<?php include("header.php"); ?>
<!-- /.search form -->
<section id="page" class="container mTop-30 mBtm-50">
<div class="row">
<div class="col-sm-12">
<div class="panel-body frameLR bg-white shadow space-sm">
<div class="col-md-6">
<?php //user_register($link); ?>
<h3 class="dark-grey">
Registration
</h3>
<form method="post">
<div class="form-group">
<label> First Name </label>
<input type="text" name="first_name" class="form-control"> </div>
<div class="form-group">
<label> Last Name </label>
<input type="text" name="last_name" class="form-control"> </div>
<div class="form-group">
<label> Contact Number 1 </label>
<input type="number" name="contact_no" class="form-control" placeholder="eg: 0868587186"> </div>
<div class="form-group">
<label> Contact Number 2 </label>
<input type="number" name="contact_no2" class="form-control"> </div>
<div class="form-group">
<label> Address Line 1 </label>
<input type="text" name="address1" class="form-control"> </div>
<div class="form-group">
<label> Address Line 2 </label>
<input type="text" name="address2" class="form-control"> </div>
<div class="form-group">
<label> Address Line 3 </label>
<input type="text" name="address3" class="form-control"> </div>
<div class="form-group">
<label> Region </label>
<select name="region" class="form-control sm-margin-bottom-10">
<option value="">Select Region</option>
<?php location_dropdown($link); ?>
</select>
</div>
<div class="form-group">
<label> Email </label>
<input type="email" name="email" class="form-control"> </div>
<div class="form-group">
<label> Password <a href="#" data-toggle="tooltip" data-placement="right" title="Password must be at least 8 characters and must contain an uppercase & lowercase letter, a number and a special character. "><span class="fa fa-info-circle"></span></a></label>
<input type="password" name="password" class="form-control" id=""> </div>
<div class="form-group">
<label> Repeat Password </label>
<input type="password" name="pass_confirm" class="form-control" id="" value=""> </div>
<div class="col-sm-6">
<input type="checkbox" name="terms" value="Yes" class="checkbox pull-left" /> I agree to the terms and conditions </div>
<input type="text" name="somefield" style="display:none" />
<input type="submit" class="btn btn-danger btn-raised ripple-effect" name="register" value="Register">
<input type="hidden" name="form_token" value="<?php echo $token ?>">
</form>
</div>
<div class="mBtm-20 visible-xs"> </div>
<div class="col-md-6">
<h3 class="dark-grey">
Terms and Conditions
</h3>
<p> By clicking on "Register" you agree to The Company's' Terms and Conditions </p>
</div>
</div>
<!-- /inner wrap -->
</div>
</div>
</section>
<!-- /#page end -->
<?php include("footer.php"); ?>
<!-- /.CTA -->
</div>
<!-- /animitsion -->
<!-- JS files -->
<script src="js/jquery.min.js">
</script>
<script src="js/themescripts.js">
</script>
<script src="js/bootstrap.min.js">
</script>
<script src="js/jquery.animsition.min.js">
</script>
<script src="owl.carousel/owl.carousel.js">
</script>
<script src="js/jquery.flexslider-min.js">
</script>
<script src="js/plugins.js">
</script>
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet">
<script>
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
});
</script>
</body>
</html>
I think we need to see what is in functions/functions.php
ASKER
functions.php has LOADS of functions in it. I don't want to post the whole thing. But, I don't see that it can be that because in this post above in my working example, I am also requiring the functions.php file and everything works. Which leads me to believe that the functions file isn't the problem?
https://www.experts-exchan ge.com/que stions/289 96436/Chec king-CSRF- token-with in-a-funct ion.html?a nchor=a419 72084¬i ficationFo llowed=182 751708#a41 971981
https://www.experts-exchan
ASKER
The link doesn't seem to take me where it's meant to. Look at my post with ID: 41971981
I have required functions.php without issue.
I have required functions.php without issue.
ASKER
So, this is weird. If I submit the form once then it fails. If I submit again I get the message "tokens match". It seems it is working in reverse or something?
By submit again I don't mean refreshing the browser, just clicking on the submit button again.
By submit again I don't mean refreshing the browser, just clicking on the submit button again.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Ah, excellent idea.
I found what is breaking it, it would seem. But I don't know why?? It is this code in the <head>
I added it to the test page and it broke it. I removed it from the register page and it fixed it!
I found what is breaking it, it would seem. But I don't know why?? It is this code in the <head>
<link rel="shortcut icon" href="#">
<link rel="apple-touch-icon" href="#">
<link rel="apple-touch-icon" sizes="72x72" href="#">
<link rel="apple-touch-icon" sizes="114x114" href="#">
I added it to the test page and it broke it. I removed it from the register page and it fixed it!
The <link rel= tag breaks it? I've never seen that happen.
Just a note for going forward... When you have hidden data in a data-dependent problem like this one (session data does not match post data), the first thing to do is visualize the data. I tried changing the <input type="hidden" to <input type="text" so I could look at the value in the form. And I added var_dump($_SESSION) to the footer of the page. That let me see that the information was getting into the session and form in a way that matched.
Just a note for going forward... When you have hidden data in a data-dependent problem like this one (session data does not match post data), the first thing to do is visualize the data. I tried changing the <input type="hidden" to <input type="text" so I could look at the value in the form. And I added var_dump($_SESSION) to the footer of the page. That let me see that the information was getting into the session and form in a way that matched.
ASKER
Yeah, it's super weird. It must be that breaking it because as soon as I added it to my test page it broke! And like I said, removing it from the register.php fixed that page and it works now. I'm stumped. Anyway, thanks for the good advice in your previous post.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Look at the browser behaviour as it pertains to <form>. You have <form method="post"></form>, and when you submit, it goes to register.php! Since you did not specify action="register.php", the browser sets the default <form action> to the current page -- register.php in your case. The same is happening for <link> -- it assumes that href="register.php". I don't know why you are including those <link> tags without a valid href, but if you really need them due to css, what you can do is:
create a fake_file.php which contains nothing and update your <link> tags to point to it. The reason it is breaking seems to be that the browser does make at least one request to register.php and this ends up recreating another token.
If you don't want to create the fake file, another option is to add a querystring to the href of the <link>, and at the very start of the file, see if you can detect it. If so, just quit:
create a fake_file.php which contains nothing and update your <link> tags to point to it. The reason it is breaking seems to be that the browser does make at least one request to register.php and this ends up recreating another token.
If you don't want to create the fake file, another option is to add a querystring to the href of the <link>, and at the very start of the file, see if you can detect it. If so, just quit:
<link rel="shortcut icon" href="?css=1#">
Then on the server:<?php
//register.php
if( array_key_exists('css',$_GET) && intval($_GET['css'])===1)
{
exit;
}
error_reporting(E_ALL);
require_once 'functions/functions.php';
?>
ASKER
Thanks to both of you for all your help, it is most appreciated!
ASKER
Open in new window
I get identical tokens.
zWvSVY8C02bgj1uZyDXaoaeo5E
zWvSVY8C02bgj1uZyDXaoaeo5E
If I add this to my register function:
Open in new window
I get:
SESSION: zWvSVY8C02bgj1uZyDXaoaeo5E
POST: R4zoz+gzJ5RrQU7Aa/zwUFwR2+
The POST R4zoz+gzJ5RrQU7Aa/zwUFwR2+