Sécurité des sessions en PHP

Publié le 13 novembre 2012 - Developpement Web. Tags : , ,

Plus le temps passe, plus je suis sidéré par le niveau de sécurité proche d’une passoire de certaines applications. En cause, plus ou moins toujours les mêmes facteurs, toujours des erreurs basiques faciles à éviter.  Du coup, pour palier à ma carence de découvertes du moment, je me suis dit qu’il serait intéressant de compléter mon article de 2009 concernant la sécurité PHP/mysql (mise à jour programmée d’ailleurs) et de vous parler de la gestion des sessions.

Ici, pas question de vous expliquer la création d’un espace membre, le but est de vous montrer les mesures de sécurité basiques à mettre en œuvre lors de l’utilisation de ces « sessions ».

Contrairement à ce qu’on pourrait penser, créer un espace membre sur son site n’est pas compliqué, PHP fournissant tous les outils nécessaires pour cela.

Une session, c’est quoi exactement ? Pour faire simple, il s’agit d’un espace de stockage d’informations côté serveur (à la différence d’un Cookie, où les informations sont stockées chez le visiteur). En gros PHP attribue à chaque visiteur un identifiant de session, et stocke les informations liées dans un tableau via la superglobale $_SESSION[] qu’on initialise par la fonction session_start() (en la plaçant en haut des pages concernées, probablement toutes…). Les sessions sont particulièrement utiles dans le cadre du développement d’un espace membre.

Connexion à un espace membre

Générez un nouvel identifiant de session

Lorsqu’un visiteur s’identifie, vous vérifiez que la combinaison « nom d’utilisateur » / « mot de passe » est correcte en comparant les informations saisies dans un formulaire avec ce que vous avez stocké en base de données. Si les informations saisies sont correctes, alors l’utilisateur devient un membre connecté. Pour changer ce statut, on ajoute généralement un élément à la session de l’utilisateur avec une valeur spécifique, ex :

$_SESSION['logged'] = 1;

L’erreur classique est ici d’oublier de changer changer l’identifiant de session. Il est en effet nécessaire de faire appel de la fonction session_regenerate_id()  lors d’une connexion réussie. Cela a pour effet de modifier l’identifiant de session tout en conservant les informations contenues dans le tableau associatif. En clair, cela permet de bloquer les attaques par fixation de session. Voici donc ce que vous devriez avoir:

session_start();
if(connexion_ok() == TRUE) { //fonction fictive....
$_SESSION['logged'] = 1;
session_regenerate_id();
}

Il convient également d’utiliser cette fonction avant d’enregistrer/mettre à jour des informations sensibles (le cas échant, il est même possible de demander une nouvelle authentification pour plus de sécurité).

Connexions persistantes

Les sessions ayant une durée de vie limité, on utilise généralement un cookie pour permettre de garder des connexions ouvertes sur plusieurs jours. Un erreur typique est de sauvegarder le couple login / mot de passe (même crypté!) directement dans le cookie. Si l’idée est compréhensible (il suffit de lire les informations du cookie pour effectuer une requête rapide sur la table contenant la liste des membres), elle est une faille de sécurité très importante. Non seulement les cookies peuvent être lus par une personne malveillante sur l’ordinateur d’une autre personne, mais ils peuvent aussi être manipulés en local.

Plutôt que de stocker un mot de passe, on va donc générer un jeton aléatoire dans le cookie, et enregistrer les informations dans une table spécifique de la base de données, en y ajoutant une durée de vie limitée. De cette façon, il suffit de comparer les valeurs du cookie et les valeurs contenues dans cette table pour accepter une connexion,  tout en protégeant les informations sensibles et en évitant les manipulations locales . Idéalement, il est aussi préférable de crypter le nom d’utilisateur.

Pour ce faire, ces quelques lignes devraient suffire :

$jeton = md5(uniqid(rand(), TRUE)); //création d'un jeton
$utilisateur = md5('grain_de_sel'.md5($utilisateur)); //On hash le nom d'utilisateur
$vie = time() + 60 * 60 * 24* 7; //durée de vie (ici, 7 jours)
setcookie('nom_cookie', "$utilisateur:$jeton", $vie); //on crée le cookie

Lorsqu’un utilisateur présente un cookie de connexion persistante, il suffit de vérifier les informations contenues dans le cookie en les comparant à notre table spécifique, afin d’autoriser ou non la connexion. A chaque connexion réussie on renouvelle le jeton, et on envoie le nouveau cookie correspondant.

En agissant de la sorte, on évite de divulguer le nom d’utilisateur (ce qui limite la probabilité de réussite d’une attaque de brute force, puisque c’est le couple nom d’utilisateur/mot de passe que l’attaquant devra trouver, pas simplement le mot de passe), on protège le mot de passe (sachant qu’un mot de passe simple hasché en md5() est facilement dé-codable), et on limite aussi dans le même temps les éventuelles manipulations locales (la durée de vie de la session est stockée en base, prolonger artificiellement la durée de vie du cookie n’empêcherait pas la session d’expirer)

Vérification d’identité systématique

Un moyen simple d’ajouter une couche de protection supplémentaire à vos session est d’enregistrer l’IP utilisée par le membre (et le navigateur, le cas échéant) à la connexion. Ainsi, sur chaque chaque page il vous sera possible de comparer les informations fournies. En cas de doute, terminez la session.

En imaginant que vous ayez créé une function GetUserIP() pour obtenir l’IP d’un visiteur, il vous suffit de comparer la valeur trouvé à la valeur de la session :

if($_SESSION['ip_membre'] !== GetUserIP()) {
//destruction de session
session_destroy();
unset($_SESSION);
}

Comme vous avez pu voir tout ceci est assez basique, mais cela n’est reste pas moins efficace. Retenez donc qu’en agissant de la sorte vous limitez fortement les risques liés à l’utilisation des sessions au sein d’un espace membre.

nb. Petit rappel: les mots de passe doivent être cryptés dans votre base de données. A ce titre, je vous conseille l’utilisation de phpass (méthode bcrypt), évoqué sur l’article dédié à la sécurisation des données en PHP.


Articles sur ce thème :