Solved

CSRF session variables

Posted on 2016-10-21
25
68 Views
Last Modified: 2016-10-29
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:

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";
	}
}

Open in new window


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));

Open in new window


I also have a hidden field in a form:

<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>" />

Open in new window


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
0
Comment
Question by:Black Sulfur
  • 10
  • 9
  • 6
25 Comments
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41854043
I would change the token every time you refresh the page.  Prepare the token for the form, making a new one each time you display a new form.

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
0
 

Author Comment

by:Black Sulfur
ID: 41854134
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.
0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 41854161
Have you considered using a honeypot approach?
0
 

Author Comment

by:Black Sulfur
ID: 41854197
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:

<input name="somename" style="display:none" type="text" />

Open in new window


if(!empty($_POST['somename']) { // some code to abort the submission attempt }

Open in new window

0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 41854286
Yup that is a honeypot

Is your intention to check if the form submitted was one you actually sent to the browser?
0
 

Author Comment

by:Black Sulfur
ID: 41854342
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/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
0
 
LVL 108

Accepted Solution

by:
Ray Paseur earned 450 total points
ID: 41854460
It's not so much that a form token "isn't good" but more like "most of them can be defeated with a little effort."  To defeat a form token, you can't just make a POST request to the action page, you have to read the form page with a GET request, extract the hidden input controls, and formulate the POST request using a combination of the existing hidden controls (to return the form token) and the attack vector in the other form controls.  If a form token discourages all but the most ardent attackers, that's a really good start.

In the article on CAPTCHA we describe a honey-pot, right at the top.  It's an easy way to know that your form was not filled in by a human.  If you separate the honeypot input control from the inline style by using a separate style sheet, you will have another layer of analysis required of the attacker.  Usually it only takes a couple of layers and you're too much trouble for the bad guys.  They will go attack someone else.

Here's my teaching example using a single-use form token.  The token is created just before the form is presented to the client.   If the same token comes back, it's considered OK.  This is a simplified version of mebjas.
<?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;

Open in new window

I've never tried it, but I think a combination of AJAX and a form token might make for a sturdier defense.  In the Captcha article, look for "A Character-based CAPTCHA Test using AJAX to hide the CAPTCHA string."  My line of thinking is that the AJAX script can be called to inject the input control with the form token into the HTML form and into the PHP session.  Since the input control would not be present in the original HTML document, the traditional "screen scraper" attacks would not be able to extract the form token.  Maybe I'll try that and see if I can get it to work.
0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 41854651
@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.
0
 

Author Comment

by:Black Sulfur
ID: 41855101
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.
0
 

Author Comment

by:Black Sulfur
ID: 41855124
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.

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;

Open in new window


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" />

Open in new window

0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855137
... 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.
0
 
LVL 51

Assisted Solution

by:Julian Hansen
Julian Hansen earned 50 total points
ID: 41855139
You should take a look at JWT's.

The trend is to move away from COOKIE based sessions and more to local storage based JWT's - which allow for data to be included with the token and a means to verify the token.

I have not done anything with CSRF and JWT's but there appears to be some movement in this area.
0
Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

 

Author Comment

by:Black Sulfur
ID: 41855144
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?
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855151
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.
<?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>";
    }
}

Open in new window

1
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855157
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.
0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 41855182
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.
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855434
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.
0
 

Author Comment

by:Black Sulfur
ID: 41855446
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?
0
 

Author Comment

by:Black Sulfur
ID: 41855507
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';

Open in new window

0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855542
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...
!empty($_SESSION['form_token']) ? $_SESSION['form_token'] : 'X';

Open in new window

See: http://php.net/manual/en/language.operators.comparison.php

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.
1
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 41855545
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
if (!empty($_SESSION['form_token'])) {
  $token = $_SESSION['form_token'];
}
else {
  $token='X';
}

Open in new window

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.
1
 

Author Comment

by:Black Sulfur
ID: 41855557
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.
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41855611
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!
0
 

Author Comment

by:Black Sulfur
ID: 41856038
Okay, I am going to ask :)
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 41865130
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
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

Developers of all skill levels should learn to use current best practices when developing websites. However many developers, new and old, fall into the trap of using deprecated features because this is what so many tutorials and books tell them to u…
These days socially coordinated efforts have turned into a critical requirement for enterprises.
Learn how to match and substitute tagged data using PHP regular expressions. Demonstrated on Windows 7, but also applies to other operating systems. Demonstrated technique applies to PHP (all versions) and Firefox, but very similar techniques will w…
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now