PHP - Csrf - How to Make It Works in All Tabs

CSRF token collisions with multiple tabs

Assuming that your app is secured with SSL, then there is really no value created by generating new tokens on every page load. It doesn't stop an attacker who has exploited an XSS vulnerability – they'd have access to the freshly generated token anyway.

Remember what a CSRF token defends against: a malicious third-party page blindly trying to post data to your app in hopes that the user is logged in. In this kind of attack, the attacker would never have access to the CSRF token, so changing it frequently does no good.

Do not waste time and resources keeping track of multiple tokens per session. Just generate one at the start and be done.

Codeigniter CSRF protection VS tabs

Background

There is no need to regenerate the CSRF token upon each form submission. There is little security benefit - if the attacker could retrieve the token from your page then they already have won. This will enable your site to run cross-tabs without error.

See this page for some background on the security aspect: Why [you shouldn't] refresh CSRF token per form request?.

CodeIgniter v3

v3 uses a configuration item named csrf_regenerate. Set this to FALSE to prevent regeneration after each request.

CodeIgniter v2

The code CodeIgniter uses is discussed in this post: CSRF Protection in CodeIgniter 2.0: A closer look. The relevant code is below:

function csrf_verify()
{
// If no POST data exists we will set the CSRF cookie
if (count($_POST) == 0)
{
return $this>csrf_set_cookie();
}

// Do the tokens exist in both the _POST and _COOKIE arrays?
if ( ! isset($_POST[$this->csrf_token_name]) OR
! isset($_COOKIE[$this->csrf_cookie_name]) )
{
$this->csrf_show_error();
}

// Do the tokens match?
if ( $_POST[$this->csrf_token_name]
!= $_COOKIE[$this->csrf_cookie_name] )
{
$this->csrf_show_error();
}

// We kill this since we're done and we don't
// want to polute the _POST array
unset($_POST[$this->csrf_token_name]);

// Re-generate CSRF Token and Cookie
unset($_COOKIE[$this->csrf_cookie_name]);
$this->_csrf_set_hash();
$this->csrf_set_cookie();

log_message('debug', "CSRF token verified ");
}

Simply remove the following code from the function:

// Re-generate CSRF Token and Cookie
unset($_COOKIE[$this->csrf_cookie_name]);
$this->_csrf_set_hash();
$this->csrf_set_cookie();

Double Submit Cookies and multiple tabs?

You can create a specific cookie that is generated once per user session. e.g. One named "CSRFCookie". Be sure to set the Secure Flag so this in only sent via HTTPS.

As you are not maintaining server state be aware that this is vulnerable to MiTM attacks as I've covered here:

Even if your site is only accessible over HTTPS and you correctly set the Secure Flag, care must be taken with this approach as an attacker could potentially MiTM any connection from the victim to any HTTP website (if the attacker is suitably placed of course), redirect them to your domain over HTTP, which is also MiTM'd and then set the required cookie value. This would be a Session Fixation attack. To guard against this you could output the cookie value to the header and the hidden form field every time this page is loaded (over HTTPS) rather than reuse any already set cookie value. This is because although a browser can set the Secure Flag, it will still send cookies without the Secure Flag over a HTTPS connection, and the server will not be able to tell whether the Secure Flag was set. (Cookie attributes such as the Secure Flag are only visible when the cookie is set, not when it is read. The only thing the server gets to see is the cookie name and value.)

So if you wish to protect against this and not break your site over multiple tabs, you must keep track of the "CSRFCookie" value server side.

Otherwise, if server state is not stored:

  1. For a secure approach against MiTM, this value must be generated on
    each form load but breaks tabs as the cookie value is changed each
    time.
  2. If you do not wish to protect against MiTM you could just generate the "CSRFCookie" value once, which will allow multiple tabs/back to work.

There may be a solution for (1), which involves checking whether a cookie value exists, and if it already does you increment a number at the end of the name of the cookie value and hidden form field. e.g. "CSRFCookie1", "CSRFCookie2", etc, so each form can have their own cookie. Be aware though that this may build up a huge amount of data that will be submitted with every HTTP request by the browser to your server (even images, .js etc).

Another option is to use HSTS to ensure the browser always redirects any HTTP connection to your server to HTTPS before any request is made. However, there is still a window of opportunity for the HTTP connection to be MiTM'd before any request is made to your server and the HSTS policy set. You could however arrange for your site to be in the HSTS pre-loaded list of the most popular browsers to reduce the risk. This will enable you to use multiple tabs without server state as you simply use the same cookie ("CSRFCookie") and value on every form that is generated if not set or read if already set then never regenerated. Please note that HSTS is not yet supported in IE, but support may be added in IE 12, however see here for Edge browser support.

CSRF tokens - how to implement properly?

You can do either. It depends on the level of security you want.

The OWASP Enterprise Security API (ESAPI) uses the single token per user session method. That is probably a pretty effective method assuming you have no XSS holes and you have reasonably short session timeouts. If you allow sessions to stay alive for days or weeks, then this is not a good approach.

Personally, I do not find it difficult to use a different token for each instance of each form. I store a structure in the user's session with key-value pairs. The key for each item is the ID of the form, the value is another structure that contain the token and an expiry date for that token. Typically I will only allow a token to live for 10-20 minutes, then it expires. For longer forms I may give it a long expiry time.

If you want to be able to support the same form in multiple browser tabs in the same session, then my method becomes a little trickery but could still be easily done by having unique form IDs.

Quick CSRF Token

You have the basic idea down. In essence, the purpose of a CSRF token is to ensure a page cannot, say, include an iFrame which triggers something within your application. As a basic example:

<iframe src="http://example/delete.php?accept=true"></iframe>

Generally, generating one CSRF token per session is OK, but you may want to generate a unique token for each request, and checking that. Doing so will increase the security of your application but is not needed.

A quick reference can be found here: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29

Update:

You wouldn't do it through $_REQUEST per se, you'd create something like this on the form generation page

$form_session_key = bin2hex(random_bytes(32)); // For more options see http://stackoverflow.com/a/31683058/453273
!isset($_SESSION['CSRF_CONTENTS']) ? $_SESSION['CSRF_CONTENTS'] = array() : 0;
$_SESSION['CSRF_CONTENTS'][] = $form_session_key;

//SNIP

<form action="process.php" method="POST">
<input type="text" style="display: none" id="CSRF" contents="<?php echo $form_session_key; ?>" />
</form>

On your processing page

!in_array($_POST['CSRF'], $_SESSION['CSRF_CONTENTS']) ? exit : 0;

The above snippet exits if the CSRF key is not within the CSRF_CONTENTS array, which is generated upon page load.



Related Topics



Leave a reply



Submit