Crazy Horse
asked on
CSRF session variables
I know I have touched on this already and have had some good answers as well as been advised not to use this method but it is bothering me because I still don't fully understand. I have read a lot today and looked at a lot of tutorials that are similar but I still have some questions.
I have 2 versions of code and not sure which one is best to continue with:
AND
I also have a hidden field in a form:
1. One of the versions changes the token every time you refresh the page. is that good or bad?
2. If I have 3 different pages and on each of those pages there is a different page, could I just copy and paste the code above or would that cause issues?
3. Do I need to make the session variables expire after a certain amount of time or should I unset them at a certain point of time?
Thank you
I have 2 versions of code and not sure which one is best to continue with:
if(empty($_SESSION['csrf_token'])){
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if(!empty($_POST['csrf_token'])) {
if($_SESSION['csrf_token'] === $_POST['csrf_token']){
echo "token verified";
} else {
echo "token invalid";
}
}
AND
if($_SERVER['REQUEST_METHOD'] === 'POST'){
if(!isset($_POST['csrf_token']) || ($_POST['csrf_token'] !== $_SESSION['csrf_token'])){
die("Invalid CSRF token");
} else {
echo "token is valid";
}
}
$_SESSION['csrf_token'] = bin2hex(openssl_random_pseudo_bytes(16));
I also have a hidden field in a form:
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>" />
1. One of the versions changes the token every time you refresh the page. is that good or bad?
2. If I have 3 different pages and on each of those pages there is a different page, could I just copy and paste the code above or would that cause issues?
3. Do I need to make the session variables expire after a certain amount of time or should I unset them at a certain point of time?
Thank you
ASKER
Thanks, Ray. I will check it out. I know you said this isn't a good method to use but I can't seem to find any other ways of doing it with clear examples. 95% of examples out there use the session token method. You did mention CAPTCHA, but to be honest they irritate the heck out of me and I wouldn't want to irritate the heck out of users unless I really had to use it.
Have you considered using a honeypot approach?
ASKER
I use a honeypot for spam but isn't that different?
Just to check we are on the same page, my version of a honeypot is:
Just to check we are on the same page, my version of a honeypot is:
<input name="somename" style="display:none" type="text" />
if(!empty($_POST['somename']) { // some code to abort the submission attempt }
Yup that is a honeypot
Is your intention to check if the form submitted was one you actually sent to the browser?
Is your intention to check if the form submitted was one you actually sent to the browser?
ASKER
I am trying to " prevent the type of attack that occurs when a malicious web site, email, blog, instant message, or program causes a user’s web browser to perform an unwanted action on a trusted site for which the user is currently authenticated." (OWASP on CSRF attacks)
https://www.owasp.org/inde x.php/Cros s-Site_Req uest_Forge ry_(CSRF)_ Prevention _Cheat_She et
https://www.owasp.org/inde
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
@Black Sulfur,
I am familiar with OWASP and what a CSRF is but in your particular case how would this manifest itself. To answer your question it would be helpful to know what it is a malicious site could do on your site that you are trying to protect against.
I am familiar with OWASP and what a CSRF is but in your particular case how would this manifest itself. To answer your question it would be helpful to know what it is a malicious site could do on your site that you are trying to protect against.
ASKER
Oh, sorry Julian, didn't know that is what you meant.
Well, I am just trying to implement security best practices. I only really have a login form and some forms where a user can update their personal details and password as well as add/edit/delete records in the database for event bookings. At this stage there is no online store with financial details or transactions.
So, like I mentioned above, I am just trying to implement it as a security best practice just like preventing SQL injection or XSS or spam etc.
Well, I am just trying to implement security best practices. I only really have a login form and some forms where a user can update their personal details and password as well as add/edit/delete records in the database for event bookings. At this stage there is no online store with financial details or transactions.
So, like I mentioned above, I am just trying to implement it as a security best practice just like preventing SQL injection or XSS or spam etc.
ASKER
I just found an interesting example and it is the first time I have seen the use of 2 tokens. Perhaps you guys would be kind enough to look at it and let me know if it's any better than the others which just use one token.
The form:
if(isset($_POST['submit'])){
if(isset($_POST['_csrfname']) && isset($_POST['_csrfvalue']) && isset($_SESSION[$_POST['_csrfname']]) && $_SESSION[$_POST['_csrfname']] === $_POST['_csrfvalue'] && $_POST['_csrfvalue'] !== '') {
echo "token valid";
} else {
throw new exception("CSRF token validation failed");
}
}
$name = 'token-' . mt_rand();
$token = bin2hex(random_bytes(32));
$_SESSION[$name] = $token;
The form:
<input type="hidden" name="_csrfname" value="<?php echo $name; ?>">
<input type="hidden" name="_csrfvalue" value="<?php echo $token; ?>">
<input type="submit" name="submit" value="submit" />
... any better than the others which just use one token.Short answer: No, it's still a clear-text form token and therefore it can be read and returned by "screen scraper" scripts. Make no mistake, this is far better than nothing, but it's nowhere near as valuable as a CAPTCHA test.
I think of our security measures like the doors of a fire safe. They are rated on the basis of time and temperature. No fire safe is permanent and absolute, but stronger doors mean you have more time to suppress the fire before its contents are incinerated. And it matters what you're trying to protect. If you have bowling scores, or purchase histories, or medical records, or financial details, or nuclear launch codes the appropriate security measures are likely to be different.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Most of the articles I have read and videos I have watched say that you should implement CSRF prevention on ANY form you have on your website. If this is true, would you not annoy the daylights out of your user if every time they wanted to add/edit/delete a record you were first presented with a CAPTCHA?
JSON Web Tokens appear to be conceptually the same as anti-tamper cookies. This is something that PHP should have built into the language, in hindsight. But like many PHP things, simplicity took precedence over security. And the threats are always evolving.
The idea is illustrated by this teaching example. You can install this script and run it to see the behavior. The point of most of these authentication measures is really very simple. Your server wants to share a secret token with your client, and wants your client to return the secret token on each request. Most of our security measures go to the issue of sharing the token with "good" clients and keeping it secret from "bad" clients.
This can get as elaborate as you want -- you can test not only the tokens you've created, but also the time of day, the IP address, the browser identity, etc. These things should remain much the same from one HTTP request to the next. If any of them change, just ask for the client password again. This is how your bank ATM does it, and they are pretty good at protecting our money.
The idea is illustrated by this teaching example. You can install this script and run it to see the behavior. The point of most of these authentication measures is really very simple. Your server wants to share a secret token with your client, and wants your client to return the secret token on each request. Most of our security measures go to the issue of sharing the token with "good" clients and keeping it secret from "bad" clients.
This can get as elaborate as you want -- you can test not only the tokens you've created, but also the time of day, the IP address, the browser identity, etc. These things should remain much the same from one HTTP request to the next. If any of them change, just ask for the client password again. This is how your bank ATM does it, and they are pretty good at protecting our money.
<?php // demo/cookie_safety.php
/**
* Demonstrate how to encode information in a cookie in a way that
* can reduce the risk of cookie tampering. We do this with a
* salted message digest that contains the contents of the cookie.
*
* When the cookie is returned, we use the value to recreate the
* salted message digest. If the new message digest does not match
* the message digest in the cookie, we can discard the cookie.
*
* The cookie will be url-encoded on the browser like this:
* MARY+HAD+A+LITTLE+LAMB%7Ccf783c37f18d007d23483b11759ec181
* It will be url-decoded before it is presented to php.
*
* http://php.net/manual/en/function.setcookie.php
* http://php.net/manual/en/function.md5.php
*/
error_reporting(E_ALL);
ob_start();
echo '<pre>';
// A DATA DELIMITER
$dlm = '|';
// YOUR OWN SECRET CODE IS A SALT FOR THE MESSAGE DIGEST STRING
$secret_code = 'MY SECRET';
// A DATA STRING THAT WE WANT TO STORE (MIGHT BE A DB KEY)
$cookie_value = 'MARY HAD A LITTLE LAMB';
// MAKE A MESSAGE DIGEST FROM THE DATA STRING TOGETHER WITH OUR SECRET CODE
$cookie_code = md5($cookie_value . $secret_code);
// CONSTRUCT THE COOKIE STRING WITH THE CLEAR TEXT AND THE CODED STRING
$safe_cookie_value = $cookie_value . $dlm . $cookie_code;
// SET THE COOKIE LIKE "MARY HAD A LITTLE LAMB|cf783c37f18d007d23483b11759ec181"
setcookie
( 'safe_cookie' // COOKIE NAME
, $safe_cookie_value // COOKIE VALUE
, NULL // COOKIE EXPIRATION
, NULL // COOKIE PATH
, NULL // COOKIE DOMAIN/SUBDOMAIN http://php.net/manual/en/function.setcookie.php#76395
, NULL // COOKIE SECURE (HTTPS)
, NULL // COOKIE HTTP-ONLY
)
;
// HOW TO TEST THE COOKIE
if (isset($_COOKIE["safe_cookie"]))
{
// BREAK THE COOKIE VALUE APART AT THE DELIMITER
$array = explode($dlm, $_COOKIE["safe_cookie"]);
// ENCODE THE DATA STRING TOGETHER WITH YOUR SECRET
$cookie_test = md5($array[0] . $secret_code);
// IF THE MD5 CODES DO NOT MATCH, THE COOKIE IS NO LONGER INTACT
if ($cookie_test == $array[1])
{
echo PHP_EOL . "THE COOKIE {$_COOKIE["safe_cookie"]} IS INTACT";
}
else
{
echo PHP_EOL . "THE COOKIE {$_COOKIE["safe_cookie"]} IS <i>CORRUPT</i>";
}
}
else
{
die('COOKIE IS SET - REFRESH THE BROWSER WINDOW NOW');
}
// SHOW WHAT HAPPENS WITH A CORRUPT COOKIE
$_COOKIE["safe_cookie"] = str_replace('MARY', 'FRED', $_COOKIE["safe_cookie"]);
// TEST THE COOKIE AGAIN
if (isset($_COOKIE["safe_cookie"]))
{
// BREAK THE COOKIE VALUE APART AT THE DELIMITER
$array = explode($dlm, $_COOKIE["safe_cookie"]);
// ENCODE THE DATA STRING TOGETHER WITH OUT SECRET
$cookie_test = md5($array[0] . $secret_code);
// IF THE MD5 CODES DO NOT MATCH, THE COOKIE IS NO LONGER INTACT
if ($cookie_test == $array[1])
{
echo PHP_EOL . "THE COOKIE {$_COOKIE["safe_cookie"]} IS INTACT";
}
else
{
echo PHP_EOL . "THE COOKIE {$_COOKIE["safe_cookie"]} IS <i>CORRUPT</i>";
}
}
annoy the daylights out of your user...It depends on the CAPTCHA you choose. And on what you're trying to protect. There is some science, some art, and a lot of bunko, hokum, hooie, voodoo, and black magic in these choices. And the things that worked a couple of years ago do not necessarily work today in the ever-evolving landscape of online security threats. It's a full-time four year college major.
My sense is "choose something" and don't get hung up on implementation details -- abstract the form security into a separate function or class and code to the interface. Then you can change the concrete implementation if your requirements ever change.
JSON Web Tokens appear to be conceptually the same as anti-tamper cookies.But are also portable across systems unlike cookies. They are encrypted and verifiable and are able to store additional payload outside of the authentication token.
Yes, we've used a similar concept in secure messaging. It was implemented in XML, not JSON (mostly because the idea is so old) and it consisted of a message-text, along with a message-digest. If the message-text did not match the message-digest, the entire message was deemed to be bogus. There were a few other details such as message encryption, but the core concepts seem to be about the same as JWT.
ASKER
Last question about this before I close and issue the points, I have decided to put 2 forms on the user profile page as it is just easier for me to do what I am trying to do like that. Do I need to generate two hidden fields with 2 different random tokens or can I use 1 for both forms as they are on the same page?
ASKER
Also, if you have time, please could you "translate" this as I am not familiar with this style:
$token = !empty($_SESSION['form_token']) ? $_SESSION['form_token'] : 'X';
$input = !empty($_POST['form_token']) ? $_POST['form_token'] : 'Y';
Well, the "two forms" is a loaded question. If you use a single-use form token design, you will need two form tokens and a way to disambiguate the session and request variables so you can know which token goes with which form. If you use a single reusable form token design, you sacrifice some security by allowing the token to be reused. Not sure why you would need two forms, but if it were my work, I would look for a way to use one form, and one action script, even if I had logically disconnected information in two parts.
Now on to the other piece of this...
This is called "ternary operator" notation, so named because it has three parts. It's basically a True/False test. The first part is the expression, to the left of the ?. It is evaluated to either True or False. If True, the second part of the statement is returned (the part between the ? and :). If False, the third part of the statement is returned (the part after the :).
In narrative terms we are saying, look at $_SESSION['form_token'] with the not-empty() function. The exclamation point means negation in PHP. If not-empty() returns True, meaning that the variable is present, return the variable. If not-empty() returns False, meaning the variable is empty or missing, return the letter X. Assign the returned value to the $token variable
Since we do the same sort of thing with the $_POST variable, but return the letter Y into $input we are setting up a comparison that will yield a false result (no matching form token) if either or both of the values are missing.
Now on to the other piece of this...
!empty($_SESSION['form_token']) ? $_SESSION['form_token'] : 'X';
See: http://php.net/manual/en/language.operators.comparison.phpThis is called "ternary operator" notation, so named because it has three parts. It's basically a True/False test. The first part is the expression, to the left of the ?. It is evaluated to either True or False. If True, the second part of the statement is returned (the part between the ? and :). If False, the third part of the statement is returned (the part after the :).
In narrative terms we are saying, look at $_SESSION['form_token'] with the not-empty() function. The exclamation point means negation in PHP. If not-empty() returns True, meaning that the variable is present, return the variable. If not-empty() returns False, meaning the variable is empty or missing, return the letter X. Assign the returned value to the $token variable
Since we do the same sort of thing with the $_POST variable, but return the letter Y into $input we are setting up a comparison that will yield a false result (no matching form token) if either or both of the values are missing.
That is called a Ternary expression. It is a basically a short hand if / else
$x = (expr) ? (if true then this) : (else this);
The first of the above statements can be expanded to
$x = (expr) ? (if true then this) : (else this);
The first of the above statements can be expanded to
if (!empty($_SESSION['form_token'])) {
$token = $_SESSION['form_token'];
}
else {
$token='X';
}
There is a tendency sometimes to overuse Ternary operators and use them inappropriately. However, this is a classic use case - conditional assignment of a variable to a value based on the state of another variable.
ASKER
Thanks guys! I had initially wanted to use 1 form but it got too confusing for me so I split it in 2. I had a user profile edit page where they could edit their email address and password if they wanted to. But if they only wanted to edit their email address and not their password it was getting tricky.
Something like:
If you edit the email address, check the database to see if the new inputted email address exists and return an error if it does because you can't have duplicate email addresses. if the email address = the one you logged in with then don't do anything. If you don't enter a password in the "old password" field then assume you are only editing your email address. If user inputs a value into the "old password" field then you have to enter something into the new password field because it assumes you want to change your password and then also the confirm new password field needs to be filled in. Check the database to make sure that the old password inputted actually is the same as the one in the database for that email address etc.
So, I decided it would be easier to have 2 forms. One for if you want to change your email address and one for if you want to change your password. Made my life way easier.
Something like:
If you edit the email address, check the database to see if the new inputted email address exists and return an error if it does because you can't have duplicate email addresses. if the email address = the one you logged in with then don't do anything. If you don't enter a password in the "old password" field then assume you are only editing your email address. If user inputs a value into the "old password" field then you have to enter something into the new password field because it assumes you want to change your password and then also the confirm new password field needs to be filled in. Check the database to make sure that the old password inputted actually is the same as the one in the database for that email address etc.
So, I decided it would be easier to have 2 forms. One for if you want to change your email address and one for if you want to change your password. Made my life way easier.
Post a question about how to do MySQLi table maintenance for things like email addresses and passwords. I can show you the general design patterns we use. It's easier than you think once you know what lever to pull :-)
Or learn about active record design patterns. It will all make sense soon, I promise!
Or learn about active record design patterns. It will all make sense soon, I promise!
ASKER
Okay, I am going to ask :)
By way of following up, here is the article that I wrote in response to this question.
https://www.experts-exchange.com/articles/28802/Improved-Form-Tokens-to-Guard-Against-CSRF-and-Screen-Scrapers.html
Best to all, ~Ray
https://www.experts-exchange.com/articles/28802/Improved-Form-Tokens-to-Guard-Against-CSRF-and-Screen-Scrapers.html
Best to all, ~Ray
Different "pages" is a complex question. It goes to the nature of the HTTP client/server process. Pages do not really exist - only requests and responses. Read this over, and think about what a page is -- it's a response, and it can only exist in response to one request.
https://www.experts-exchange.com/articles/11271/Understanding-Client-Server-Protocols-and-Web-Applications.html
Session variables expire automatically. Here's how the session works.
https://www.experts-exchange.com/articles/11909/PHP-Sessions-Simpler-Than-You-May-Think.html