8
\$\begingroup\$

The PHPSESSID is stored in the clients cookie so I don't consider it as secure. Someone might bruteforce it and perform some action (like a Facebook status post) whenever a session was successfully hijacked.

I was wondering how I can protect against attacks like that. Maybe by locking out suspect users? My solution I came up with is a bit different:

<?php

    $lifetime = 1 * 60 * 60; // seconds

    // Rename the PHPSESSID cookie for convenience and set the maximum lifetime
    ini_set('session.name', 'session_id');
    ini_set('session.gc_maxlifetime', $lifetime);
    session_set_cookie_params($lifetime);

    // Create a new session if necessary
    if ( ! isset($_COOKIE['session_id'])) {
        @session_start() or die(); // Don't output anything on invalid cookie forgery attempts

        // Generate a random lowercase alphanumeric string
        $sessionKey = substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', 5)), 0, 32);

        // Save it in the session and as a cookie
        setcookie('session_key', $sessionKey);
        $_SESSION['session_key'] = $sessionKey;

        $_SESSION['foo'] = 'bar'; // Just for testing – see the last echo statement in this script
    }
    else {
        @session_start() or die(); // Don't output anything on invalid cookie forgery attempts

        // Destroy the session completely (including client cookies) if the session keys don't match
        if ($_SESSION['session_key'] !== $_COOKIE['session_key']) {
            session_unset();
            session_destroy();
            setcookie('session_id', null, time() - 1);
            setcookie('session_key', null, time() - 1);
        }
    }

    echo isset($_SESSION['foo']) ? $_SESSION['foo'] : 'baz';

?>

What do you think about it? Can I consider this as secure or are any other steps required? Will it protect me successfully from the described attacks?

\$\endgroup\$
3
\$\begingroup\$

Depending of the attack, your code might be either may be more secure than the traditional use of PHP sessions.


If the attacker gets the session_id cookie from an XSS attack, he also gets access to the session_key one, and your code won't protect sessions from being hijacked. You can avoid this by setting the HttpOnly flag on your cookies, making them not accessible by JavaScript through document.cookie :

  • Using ini_set('session.cookie_httponly', 1); will configure PHP to add the flag on the session_id cookie.
  • Setting the seventh parameter of the setcookie function to true will set the HttpOnly flag on the session_key cookie. More information about all the parameters can be found on the official documentation : http://php.net/manual/function.setcookie.php

Anyway, that won't protect from the man in the middle attacks. PHP has a function named session_regenerate_id which changes the current session id by a new one, making the hijacked cookies obsolete.

All you need to do is calling this function once you've started the session : http://php.net/manual/en/function.session-regenerate-id.php


If the attacked gets the session_id cookie from bruteforce, then your solution is indeed more secure, but you should still have a look to the two settings mentioned above.

Note that substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', 5)), 0, 32); doesn't generate a true random lowercase alphanumeric string since each character won't appear more than 5 times. substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', 32)), 0, 32); will work.


There are also more php.ini settings to add more entropy to PHP sessions ids :

  • The session.hash_function setting changes the hash function used by PHP to generate the id.
  • The session.entropy_file and session.entropy_length settings will tell PHP to use an external file as a source of additional entropy when generating the id.
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ This answers my question. Anyways I'm not quite sure when to call session_regenerate_id. On each request after session_start? (Thats what I understand from your writing) Or maybe only on special events like a login for example? Thank you. :-) \$\endgroup\$ – Martin Jan 2 '15 at 4:39
  • \$\begingroup\$ @Martin: For maximum security, I would call session_regenerate_id each time session_start is called. Note that you must set the first parameter to true to delete the old session id, otherwise PHP will keep the data associated with the old id. \$\endgroup\$ – NPlay Jan 2 '15 at 19:30
  • \$\begingroup\$ Your "fix" fixes a minor problem, while leaving the big problem that str_shuffle is not cryptographically secure making the output easily guessable. \$\endgroup\$ – CodesInChaos Jul 12 '16 at 14:15
3
\$\begingroup\$

To prevent brute forcing of the PHP Session ID you should use configuration rather than rolling your own security.

From OWASP Session Management Cheat Sheet - Session ID Length:

The session ID must be long enough to prevent brute force attacks, where an attacker can go through the whole range of ID values and verify the existence of valid sessions.

The session ID length must be at least 128 bits (16 bytes).

This should be secure enough to prevent brute forcing:

...it will take an attacker at least 292 years to successfully guess a valid session ID

Check your entropy settings within php.ini and that session.entropy_length is set to 16.

Regarding the code itself:

// Generate a random lowercase alphanumeric string
$sessionKey = substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyz', 5)), 0, 32);

str_shuffle does not produce cryptographically secure randomness, so it unsuitable for producing anything security related such as tokens. You should write your own function based on openssl_random_pseudo_bytes or any other cryptographically secure source.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ 292 years to successfully guess a valid session ID for 100,000 ID's and 10,000 guesses/second. Seems pretty secure for now. I just don't like the idea, that it also could take just 1 second when the attacker gets lucky. Having a key + id and require them to match seems so much more secure to me but maybe this is just equally safe and i can't imagine how these really compare. About the str_shuffle thing: I didn't think it would be all that important. The attacker wont know if a session_id exists for the session_key he's brute forcing. \$\endgroup\$ – Martin Jan 2 '15 at 17:06
  • \$\begingroup\$ @Martin: What you're effectively doing is adding a predictable sequence of 32 characters to the session ID. Yes, tricky for an attacker to get right as if he gets it wrong the session is unset and destroyed but as it is predictable maybe not impossible. At least verify your PHP config is secure as suggested and then add this on top (although adding more code rarely increases security as there is more to go wrong). All in all I wouldn't get too hung up on brute forcing and would look at securing my site against other session management attacks such as Session Fixation and Session Hijacking. \$\endgroup\$ – SilverlightFox Jan 2 '15 at 17:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.