web-dev-qa-db-fra.com

PHP ID de session - comment sont-ils générés?

Lorsque j'appelle session_start() ou session_regenerate_id(), PHP génère ce qui semble être une chaîne aléatoire pour l'ID de session. Ce que je veux savoir, c'est c'est juste une séquence aléatoire de caractères, ou est-ce comme la fonction uniqid()?

Parce que s'il ne s'agit que de personnages aléatoires, ne pourriez-vous pas théoriquement rencontrer un conflit? Si l'utilisateur A se connectait, puis l'utilisateur B se connectait et, bien que cela soit très peu probable, l'utilisateur B générait le même ID de session, alors l'utilisateur B finirait par accéder au compte de l'utilisateur A.

Même si PHP vérifie si une session avec le même ID existe déjà et, si c'est le cas, régénère à nouveau un ID ... Je ne pense pas que je veux un système qui produit JAMAIS le même Identifiez-vous deux fois, même après la collecte des ordures - peut-être que je veux stocker une table d'entre eux et vérifier contre eux pour un éventuel détournement ou autre.

Si ce n'est pas unique, comment dois-je procéder pour appliquer l'unicité? Je préfère l'implémenter en utilisant la configuration PHP que dans tous les scripts que je crée. Une bonne chose à propos des sessions PHP ne se soucie pas des détails techniques en arrière-plan).

19
M Miller

Si vous voulez savoir comment PHP génère un ID de session par défaut, consultez le code source sur Github . Ce n'est certainement pas aléatoire et est basé sur un hachage (par défaut: md5) de ces ingrédients (voir la ligne 310 de l'extrait de code):

  1. Adresse IP du client
  2. Heure actuelle
  3. PHP Linear Congruence Generator - un générateur de nombres pseudo aléatoires (PRNG)
  4. Source aléatoire spécifique au système d'exploitation - si le système d'exploitation dispose d'une source aléatoire (par exemple/dev/urandom)

Si le système d'exploitation dispose d'une source aléatoire, la force de l'ID généré dans le but d'être un ID de session est élevée (/dev/urandom et les autres sources aléatoires du système d'exploitation sont (généralement) des PRNG sécurisés cryptographiquement ). Si ce n'est pas le cas, alors c'est satisfaisant.

L'objectif de la génération d'identification de session est de:

  1. minimisez la probabilité de générer deux ID de session avec la même valeur
  2. rendre très difficile le calcul pour générer des clés aléatoires et en frapper une en cours d'utilisation.

Ceci est réalisé par l'approche de PHP à la génération de session.

Vous ne pouvez absolument pas garantir l'unicité , mais les probabilités sont si faibles de frapper deux fois le même hachage que cela ne vaut généralement pas la peine de s'inquiéter.

47
GordyD

Voici le code qui génère l'id: Session.c

Plus précisément, le php_session_create_id fonction:

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

Comme vous pouvez le voir, l'identifiant réel est un hachage d'un mélange de choses, comme l'heure de la journée. Il y a donc une possibilité de se heurter à un conflit, cependant, il a une possibilité très faible. Tant et si bien, cela ne vaut la peine de s'inquiéter que si vous avez beaucoup d'utilisateurs simultanés.

Cependant, si vous êtes vraiment inquiet, vous pouvez augmenter l'entropie en définissant un algorithme de hachage différent session.hash_function

En ce qui concerne le suivi des sessions actives, cette question le couvre bien Est-il possible de voir les sessions actives en utilisant php?

Si vous utilisez une seule instance de php sur une seule machine, il dispose en fait d'un gestionnaire de sessions intégré qui vérifie si un identifiant existe déjà avant de l'attribuer. Cependant, si vous exécutez plusieurs instances ou plusieurs machines, il n'a aucun moyen de savoir quels ID ont été attribués par d'autres machines.

4
1321941