web-dev-qa-db-fra.com

Quel est le niveau de sécurité de mon PHP système de connexion?

Je suis nouveau sur PHP et c'est également mon premier système de connexion. Ce serait donc formidable si vous pouviez consulter mon code et voir si vous pouviez détecter les failles de sécurité:

remarque: je désinfecte toutes les entrées d'utilisateur bien que ce ne soit pas montré ici.

S'inscrire:

Étape 1: Je prends le mot de passe que l'utilisateur a choisi et l'exécute via cette fonction:

encrypt($user_chosen_password, $salt);

function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}

Étape 2: Je stocke ensuite le hachage et le sel ($password['hash'] et $password['salt']) dans la table users de la base de données:

id | username | password  | salt       | unrelated info...
-----------------------------------------------------------
1  | bobby    | 809a28377 | 809a28377f | ...
                fd131e5934
                180dc24e15
                bbe5f8be77
                371623ce36
                4d5b851e46

S'identifier:

Étape 1: Je prends le nom d'utilisateur saisi par l'utilisateur et effectue une recherche dans la base de données pour voir si des lignes sont renvoyées. Sur mon site, aucun utilisateur ne peut partager le même nom Afin que le champ nom d'utilisateur ait toujours une valeur unique. Si je reçois une ligne, je récupère le sel pour cet utilisateur.

Étape 2: Ensuite, je lance le mot de passe entré par l'utilisateur via la fonction de chiffrement (comme indiqué précédemment ci-dessus), mais cette fois-ci, je fournis également le sel extrait de la base de données:

encrypt($user_entered_password, $salt);

Étape 3: J'ai maintenant le bon mot de passe pour faire correspondre cette variable: $password['hash']. Donc, j'ai donc une seconde recherche sur la base de données pour voir si le nom d'utilisateur Entré et le mot de passe haché renvoient ensemble une seule ligne. Si tel est le cas, les informations d'identification de l'utilisateur sont correctes.

Étape 4: Afin de connecter l'utilisateur une fois ses informations d'identification transmises, je génère une chaîne unique aléatoire et je la hache:

$random_string = uniqid(Rand(0, 1000000));
$session_key = hash('sha512', $random_string);

J'insère ensuite le $session_key dans la table active_sessions de la base de données:

user_id | key
------------------------------------------------------------
1       | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b

Étape 5:

Je prends la chaîne unique non chiffrée générée à la dernière étape ($random_string) et la définit comme valeur d'un cookie que j'appelle active_session:

setcookie('active_session', $random_string, time()+3600*48, '/');

Étape 6:

Au sommet de mon header.php inclus, il y a cette vérification:

if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) {
   get_userinfo();
}

La fonction get_userinfo() effectue une recherche sur la table users de la base de données et renvoie un tableau associatif stocké dans une session appelée userinfo:

// cette fonction prend d'abord la valeur du cookie active_session et la hache pour obtenir la clé session_key:

hash('sha512', $random_string);

// Ensuite, il effectue une recherche dans la table active_sessions pour voir si un enregistrement de cette variable key existe. Dans ce cas, il récupérera le user_id associé à cet enregistrement et l'utilisera pour effectuer une seconde recherche dans la table users afin d'obtenir la variable userinfo:

    $_SESSION['userinfo'] = array(
        'user_id'           => $row->user_id,
        'username'          => $row->username,
        'dob'               => $row->dob,
        'country'           => $row->country,
        'city'              => $row->city,
        'Zip'               => $row->Zip,
        'email'             => $row->email,
        'avatar'            => $row->avatar,
        'account_status'    => $row->account_status,
        'timestamp'         => $row->timestamp,
    ); 

Si la session userinfo existe, je sais que l'utilisateur est authentifié. S'il n'existe pas mais que le cookie active_session existe, alors cette vérificationau début du fichier header.php créera cette session.

La raison pour laquelle j'utilise un cookie et pas seulement des sessions est de conserver la connexion. Donc, si l'utilisateur ferme le navigateur, la session est peut-être terminée, mais le cookie Existera toujours. Et comme il y a cette vérification en haut de header.php, la session sera recréée et l'utilisateur peut fonctionner comme un utilisateur enregistréin normalement.

Connectez - Out:

Étape 1: La session userinfo et le cookie active_session ne sont pas définis.

Étape 2: L'enregistrement associé de la table active_sessions de la base de données est supprimé.


Notes: Le seul problème que je peux voir (et peut-être qu'il y en a beaucoup d'autres), c'est si l'utilisateur simule ce cookie active_session en le créant lui-même dans son navigateur. Bien sûr, ils doivent définir comme valeur de ce cookie une chaîne qui, une fois cryptée, doit correspondre à un enregistrement de la table active_sessions à partir duquel je vais extraire le user_id pour créer cette session. Je ne suis pas sûr de la probabilité réaliste que cela se produise, pour un utilisateur (peut-être à l'aide d'un programme automatisé) de deviner correctement une chaîne dont il ne sait pas qu'il sera ensuite chiffré par sha512 et comparé à la chaîne de la table active_sessions de la base de données pour obtenir l'ID utilisateur pour construire cette session.

Désolé pour le grand essai mais comme c'est une partie tellement critique de mon site et à cause de mon inexpérience, je voulais juste le faire fonctionner par des développeurs plus expérimentés pour être sûr qu'il soit réellement sécurisé. 

Alors voyez-vous des failles de sécurité sur cette route et comment peut-on l’améliorer?

38
TK123

Vous devez inclure une sorte de délai d'attente ou de basculement pour empêcher les attaques par force brute. Il existe plusieurs façons de procéder, notamment le blocage basé sur IP, les délais d'attente incrémentiels, etc. Aucune d'entre elles ne pourra jamais arrêter un pirate informatique, mais elles peuvent rendre la tâche beaucoup plus difficile.

Un autre point (que vous n'avez pas mentionné, donc je ne connais pas votre plan) concerne les messages d'échec. Rendre les messages d'échec aussi vagues que possible. Fournir un message d'erreur du type "Ce nom d'utilisateur existe, mais les mots de passe ne correspond pas" peut être utile pour l'utilisateur final, mais il tue la fonctionnalité de connexion. Vous venez de convertir une attaque par force brute qui devrait prendre O(n^2) temps à O(n) + O(n). Au lieu d'essayer toutes les permutations d'une table Rainbow (par exemple), le pirate informatique essaie d'abord toutes les valeurs de nom d'utilisateur (avec un mot de passe défini), jusqu'à ce que le message d'échec change. Ensuite, il connaît un utilisateur valide et doit simplement forcer le mot de passe.

Dans le même ordre d'idées, vous devez également vous assurer que le même temps s'écoule lorsqu'un nom d'utilisateur existe et n'existe pas. Vous exécutez des processus supplémentaires lorsqu'un nom d'utilisateur existe réellement. En tant que tel, le temps de réponse serait plus long lorsqu'un nom d'utilisateur existe ou non. Un pirate informatique extrêmement qualifié pourrait chronométrer les requêtes de page pour trouver un nom d'utilisateur valide.

De même, vous devez vous assurer que, en plus de l’expiration des cookies, vous expirez également le tableau des sessions.

Enfin, dans l'appel get_user_info(), vous devez mettre fin à toutes les sessions ouvertes s'il existe plusieurs connexions actives simultanées. Assurez-vous que les sessions sont arrêtées après une période d'inactivité définie (30 minutes, par exemple).

Dans le sens de ce que @Greg Hewgill a mentionné, vous n’avez inclus aucun des éléments suivants:

  • Connexion SSL/cryptée entre serveur-client
  • Autres protocoles de transport que vous utilisez beaucoup pour traiter l’authentification (comme OAuth)

Vous serveur est sécurisé, mais votre algorithme n'est pas sécurisé si quelqu'un peut lire les données échangées (MITM). Vous devez vous assurer que vous ne communiquez que via un protocole crypté. 

28
sethvargo

... exécuter le mot de passe entré par l'utilisateur via la fonction de chiffrement ...

Alors, comment le mot de passe est-il transmis du navigateur au serveur? Vous n'avez pas mentionné la protection contre les attaques de l'homme du milieu.

8
Greg Hewgill

Ce code ...

function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}

...est mauvais. Utilisez la nouvelle API de mot de passe et utilisez-la. Sauf si vous êtes un expert, vous ne devez pas essayer de concevoir votre propre système d'authentification. Ils sont extrêmement difficiles à obtenir .

Afin de connecter l'utilisateur après la transmission de ses informations d'identification, je génère une chaîne unique aléatoire et je la hache:

Juste laissez PHP gérer la gestion de session . Rand() et mt_Rand() sont tous deux des générateurs de nombres aléatoires très peu sécurisés.

4
Scott Arciszewski

Il semble que le code que vous avez créé ne puisse pas être testé via des tests unitaires et d'intégration automatisés. Il est donc difficile de repérer les erreurs susceptibles d’être incluses dans votre implémentation au fil du temps et lors de l’exécution dans un environnement de production.

Cela pose normalement des problèmes de sécurité, car la sécurité d'une exécution correcte et stricte et le traitement sain des données ne sont pas testés/vérifiés.

(C'est juste un autre point de la liste, voyez également la réponse sur la sécurisation de la couche de transport, vous n'avez pas non plus spécifié comment vous protégez vos données de session contre la falsification)

3
hakre

Vous devriez utiliser mt_Rand () au lieu de Rand ()

Voici pourquoi:

1
Lucas Bustamante

En ce qui concerne les mots de passe en php, vous ne devriez pas les chiffrer. Vous devriez les hacher en utilisant la fonction password_hash(), puis lors de la connexion, utilisez password_verify() pour vérifier que le mot de passe via le formulaire html correspond au hachage enregistré dans la base 

0
Akintunde-Rotimi