Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP session IDs -- how are they generated? [duplicate]

When I call session_start() or session_regenerate_id(), PHP generates what appears to be a random string for the session ID. What I want to know is, is it just a random sequence of characters, or is it like the uniqid() function?

Because if it's just random characters, couldn't you theoretically run into a conflict? If User A logged in and then User B logged in and, though highly unlikely, User B generated the same session ID, then User B would end up accessing User A's account.

Even if PHP checks to see if a session with the same ID already exists and, if so, regenerates an ID again... I don't think I want a system that EVER produces the same ID twice, even after garbage collection -- maybe I want to store a table of them and check against them for possible hijacking or whatever.

If it isn't unique, how should I go about enforcing uniqueness? I'd rather implement it using PHP configuration than in every script I make. Nice thing about PHP sessions is not worrying about the technical details behind the scenes.

like image 231
M Miller Avatar asked Sep 21 '13 21:09

M Miller


People also ask

Can session ID be duplicated?

Yes, Session. SessionId can be duplicate.

How does PHP generate a session ID?

session_create_id() is used to create new session id for the current session. It returns collision free session id. If session is not active, collision check is omitted. Session ID is created according to php.

How are session IDs generated?

Every time an Internet user visits a specific Web site, a new session ID is assigned. Closing a browser and then reopening and visiting the site again generates a new session ID.

Is PHP session ID unique?

PHP allows us to track each visitor via a unique session ID which can be used to correlate data between connections. This id is a random string sent to the user when a session is created and is stored within the user's browser in a cookie (by default called PHPSESSID).


2 Answers

If you want to know how PHP generates a session ID by default check out the source code on Github. It is certainly not random and is based on a hash (default: md5) of these ingredients (see line 310 of code snippet):

  1. IP address of the client
  2. Current time
  3. PHP Linear Congruence Generator - a pseudo random number generator (PRNG)
  4. OS-specific random source - if the OS has a random source available (e.g. /dev/urandom)

If the OS has a random source available then strength of the generated ID for the purpose of being a session ID is high (/dev/urandom and other OS random sources are (usually) cryptographically secure PRNGs). If however it does not then it is satisfactory.

The goal with session identification generation is to:

  1. minimise the probability of generating two session IDs with the same value
  2. make it very challenging computationally to generate random keys and hit an in use one.

This is achieved by PHP's approach to session generation.

You cannot absolutely guarantee uniqueness, but the probabilities are so low of hitting the same hash twice that it is, generally speaking, not worth worrying about.

like image 67
GordyD Avatar answered Oct 19 '22 10:10

GordyD


Here is the code that generates the id: Session.c

Specifically the php_session_create_id function:

PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
{
    PHP_MD5_CTX md5_context;
    PHP_SHA1_CTX sha1_context;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
    void *hash_context = NULL;
#endif
    unsigned char *digest;
    int digest_len;
    int j;
    char *buf, *outid;
    struct timeval tv;
    zval **array;
    zval **token;
    char *remote_addr = NULL;

    gettimeofday(&tv, NULL);

    if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &array) == SUCCESS &&
        Z_TYPE_PP(array) == IS_ARRAY &&
        zend_hash_find(Z_ARRVAL_PP(array), "REMOTE_ADDR", sizeof("REMOTE_ADDR"), (void **) &token) == SUCCESS
    ) {
        remote_addr = Z_STRVAL_PP(token);
    }

    /* maximum 15+19+19+10 bytes */
    spprintf(&buf, 0, "%.15s%ld%ld%0.8F", remote_addr ? remote_addr : "", tv.tv_sec, (long int)tv.tv_usec, php_combined_lcg(TSRMLS_C) * 10);

    switch (PS(hash_func)) {
        case PS_HASH_FUNC_MD5:
            PHP_MD5Init(&md5_context);
            PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
            digest_len = 16;
            break;
        case PS_HASH_FUNC_SHA1:
            PHP_SHA1Init(&sha1_context);
            PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
            digest_len = 20;
            break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
        case PS_HASH_FUNC_OTHER:
            if (!PS(hash_ops)) {
                php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
                efree(buf);
                return NULL;
            }

            hash_context = emalloc(PS(hash_ops)->context_size);
            PS(hash_ops)->hash_init(hash_context);
            PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
            digest_len = PS(hash_ops)->digest_size;
            break;
#endif /* HAVE_HASH_EXT */
        default:
            php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
            efree(buf);
            return NULL;
    }
    efree(buf);

    if (PS(entropy_length) > 0) {
#ifdef PHP_WIN32
        unsigned char rbuf[2048];
        size_t toread = PS(entropy_length);

        if (php_win32_get_random_bytes(rbuf, MIN(toread, sizeof(rbuf))) == SUCCESS){

            switch (PS(hash_func)) {
                case PS_HASH_FUNC_MD5:
                    PHP_MD5Update(&md5_context, rbuf, toread);
                    break;
                case PS_HASH_FUNC_SHA1:
                    PHP_SHA1Update(&sha1_context, rbuf, toread);
                    break;
# if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
                case PS_HASH_FUNC_OTHER:
                    PS(hash_ops)->hash_update(hash_context, rbuf, toread);
                    break;
# endif /* HAVE_HASH_EXT */
            }
        }
#else
        int fd;

        fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
        if (fd >= 0) {
            unsigned char rbuf[2048];
            int n;
            int to_read = PS(entropy_length);

            while (to_read > 0) {
                n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
                if (n <= 0) break;

                switch (PS(hash_func)) {
                    case PS_HASH_FUNC_MD5:
                        PHP_MD5Update(&md5_context, rbuf, n);
                        break;
                    case PS_HASH_FUNC_SHA1:
                        PHP_SHA1Update(&sha1_context, rbuf, n);
                        break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
                    case PS_HASH_FUNC_OTHER:
                        PS(hash_ops)->hash_update(hash_context, rbuf, n);
                        break;
#endif /* HAVE_HASH_EXT */
                }
                to_read -= n;
            }
            close(fd);
        }
#endif
    }

    digest = emalloc(digest_len + 1);
    switch (PS(hash_func)) {
        case PS_HASH_FUNC_MD5:
            PHP_MD5Final(digest, &md5_context);
            break;
        case PS_HASH_FUNC_SHA1:
            PHP_SHA1Final(digest, &sha1_context);
            break;
#if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
        case PS_HASH_FUNC_OTHER:
            PS(hash_ops)->hash_final(digest, hash_context);
            efree(hash_context);
            break;
#endif /* HAVE_HASH_EXT */
    }

    if (PS(hash_bits_per_character) < 4
            || PS(hash_bits_per_character) > 6) {
        PS(hash_bits_per_character) = 4;

        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now");
    }

    outid = emalloc((size_t)((digest_len + 2) * ((8.0f / PS(hash_bits_per_character)) + 0.5)));
    j = (int) (bin_to_readable((char *)digest, digest_len, outid, (char)PS(hash_bits_per_character)) - outid);
    efree(digest);

    if (newlen) {
        *newlen = j;
    }

    return outid;
}

As you can see the actual id is a hash of a mixture of things, like the time of day. So there is a possibility of running into a conflict, however, it has a very low possibility. So much so, it is not worth worrying about unless you have lots of concurrent users.

However, if you really are worried you can increase the entropy by setting a different hash algorithm session.hash_function

As far as monitoring active sessions, this question covers it well Is it possible to see active sessions using php?

If you are using a single instance of php on a single machine, then it actually has a built in session manager that checks whether an id already exists before assigning it. However, if you are running multiple instances or multiple machines it has no way of knowing what ids have been assigned by other machines.

like image 20
1321941 Avatar answered Oct 19 '22 12:10

1321941