Les sessions PHP fonctionnent si bien par défaut qu'on ne les configure jamais, jusqu'au jour où les utilisateurs se plaignent d'être déconnectés au bout de vingt minutes, où un panier disparaît sans raison, ou où un audit signale un identifiant de session non renouvelé après authentification. Le mécanisme est pourtant simple : un identifiant côté client, des données côté serveur, et une poignée de réglages qui déterminent la durée, la portée et la sécurité de l'ensemble. La difficulté vient de ce que deux paramètres différents portent le nom de durée de vie, qu'ils ne mesurent pas la même chose, et que la configuration des sessions PHP se fait souvent en modifiant le mauvais des deux.
Ce qu'est réellement une session PHP
Une session est un mécanisme d'association entre un visiteur et un ensemble de données conservées côté serveur. Le serveur génère un identifiant aléatoire, le transmet au navigateur sous forme de témoin, et le navigateur le renvoie à chaque requête suivante. Le serveur retrouve alors le fichier ou l'enregistrement correspondant et restaure les données. Rien d'autre ne circule : les données restent sur le serveur, et l'identifiant seul voyage. C'est la raison pour laquelle la protection de cet identifiant constitue l'essentiel du sujet de sécurité. Tout le reste, le contenu des données et leur organisation, relève du confort de développement et non de la sécurité.
Le vol de cet identifiant permet d'usurper la session entière, sans avoir besoin du mot de passe. Trois voies existent : l'interception sur un réseau non chiffré, la lecture par un script injecté dans la page, et la fixation, où l'attaquant impose un identifiant qu'il connaît déjà. Chacune se ferme par un réglage précis, et la configuration par défaut n'en ferme qu'une partie, ce qui explique que le sujet revienne dans presque tous les audits. Ce sujet se combine avec la protection contre la falsification de requête, décrite dans notre article sur les jetons CSRF et leur vérification.
Il faut également noter qu'une session n'est pas une authentification. Un visiteur non connecté dispose lui aussi d'une session dès qu'il ajoute un article à un panier ou remplit un formulaire en plusieurs étapes. L'authentification consiste à enregistrer dans la session l'identifiant de l'utilisateur reconnu, et c'est cette information qui doit être protégée avec le plus de soin. La distinction paraît évidente et elle est régulièrement perdue de vue dans les codes où la présence d'une session est prise pour une preuve d'identité. Le contrôle doit toujours porter sur la présence de l'identifiant utilisateur dans la session, jamais sur l'existence de la session elle même.

Les deux durées de vie qu'on confond
Le premier paramètre détermine la durée de vie du témoin envoyé au navigateur. Sa valeur par défaut vaut zéro, ce qui signifie que le témoin disparaît à la fermeture du navigateur. C'est ce réglage qui décide si l'utilisateur retrouve sa session après avoir fermé et rouvert son navigateur, et c'est celui que l'on modifie lorsqu'on veut une session persistante. Le deuxième paramètre détermine la durée au bout de laquelle le serveur considère les données de session comme périmées et autorise leur suppression. Sa valeur par défaut est de mille quatre cent quarante secondes, soit vingt quatre minutes. Cette valeur, héritée d'une époque où les sessions servaient surtout à des formulaires courts, ne convient pratiquement à aucun usage moderne.
Ces deux valeurs sont indépendantes et doivent être cohérentes. Un témoin valable un mois avec des données effacées au bout de vingt minutes produit exactement le symptôme le plus courant : l'utilisateur revient, son navigateur envoie bien l'identifiant, et le serveur ne trouve plus rien. Il est alors déconnecté sans explication, et personne ne comprend pourquoi puisque la durée du témoin a bien été allongée. La règle est simple : la durée de conservation côté serveur doit être supérieure ou égale à celle du témoin. Les deux valeurs doivent être posées explicitement dans la configuration de l'application, jamais laissées à celle du serveur, qui peut changer sans préavis.
Le troisième élément, souvent ignoré, est le mécanisme de nettoyage. Les données périmées ne sont pas supprimées à l'expiration mais lors d'un passage de ramassage déclenché aléatoirement à une certaine fraction des requêtes. Une session peut donc survivre bien au delà de sa durée théorique, ou disparaître très exactement à l'échéance selon la fréquentation du site. Sur les systèmes où ce nettoyage est confié à une tâche planifiée du système, modifier la durée dans la configuration de l'application ne change rien, piège classique sur les distributions qui gèrent ce nettoyage elles mêmes. La vérification consiste à regarder si une tâche planifiée du système parcourt le répertoire de sessions, ce qui se voit en une commande.
Les attributs du témoin de session
Le témoin qui transporte l'identifiant doit porter trois attributs, et leur absence constitue le défaut le plus fréquent des configurations laissées par défaut. Le premier interdit la transmission sur une connexion non chiffrée, ce qui ferme la voie de l'interception. Le deuxième interdit la lecture du témoin par le code exécuté dans la page, ce qui ferme la voie du vol par script injecté. Le troisième limite l'envoi du témoin aux requêtes provenant du même site, ce qui ferme une partie de la voie de la falsification de requête. Ces trois protections sont indépendantes et se cumulent, aucune ne remplaçant les deux autres.
Ces trois attributs se déclarent en une seule instruction de configuration, avant le démarrage de la session, et il n'existe aucune raison de s'en priver sur un site servi en connexion sécurisée. Le seul cas demandant réflexion concerne le troisième attribut, dont la valeur stricte peut gêner les parcours où l'utilisateur revient d'un service externe, typiquement après un paiement ou une authentification déléguée. La valeur intermédiaire convient alors, tandis que la valeur stricte reste préférable partout ailleurs, notamment sur les interfaces d'administration. Le comportement doit être testé sur un parcours complet avant d'arrêter la valeur retenue.
La portée du témoin mérite également attention. Un témoin déclaré pour un domaine et tous ses sous domaines devient accessible depuis chacun d'eux, ce qui peut être voulu ou constituer une fuite selon l'architecture. Sur un site hébergeant des contenus tiers sur un sous domaine, cette portée trop large expose la session. La règle prudente consiste à restreindre le témoin au domaine strictement nécessaire, comme nous le rappelons à propos du cookie en informatique. Le nom du témoin mérite également d'être changé, la valeur par défaut annonçant à qui veut l'entendre la technologie employée.
Défauts constatés lors de revues d'applications PHP. Les deux premiers ouvrent des voies d'attaque réelles, les suivants dégradent la fiabilité perçue du site.
La régénération de l'identifiant
Régénérer l'identifiant consiste à en générer un nouveau tout en conservant les données de la session. Cette opération ferme la voie de la fixation, où un attaquant fait ouvrir une session avec un identifiant qu'il a lui même choisi, puis attend que la victime s'authentifie pour utiliser le même identifiant avec des droits acquis. Sans régénération, cette attaque fonctionne parfaitement, et elle ne demande aucun outil sophistiqué. Un simple lien contenant un identifiant de session, envoyé à la victime, suffit lorsque le site accepte de reprendre un identifiant fourni. La régénération est donc obligatoire à un moment précis : immédiatement après une authentification réussie. Cette seule ligne de code, placée au bon endroit, ferme entièrement une famille d'attaques connue depuis vingt ans.
Elle est également recommandée à chaque changement de niveau de privilège, par exemple lorsqu'un utilisateur confirme son mot de passe pour accéder à une zone sensible, et lors de la déconnexion. Certaines équipes régénèrent l'identifiant à intervalle régulier, toutes les quinze minutes par exemple, ce qui réduit la fenêtre d'exploitation d'un identifiant volé. Cette pratique demande une précaution : la suppression immédiate de l'ancien identifiant provoque des déconnexions lorsque plusieurs requêtes partent simultanément, situation banale avec les appels asynchrones d'une page moderne. Le symptôme se manifeste alors surtout chez les utilisateurs les plus actifs, ceux dont les pages déclenchent le plus de requêtes parallèles.
La parade consiste à conserver l'ancien identifiant valide pendant quelques secondes après la régénération, le temps que les requêtes en vol aboutissent. La fonction de régénération accepte un paramètre permettant de ne pas supprimer immédiatement les anciennes données, et un mécanisme de transition marquant l'ancienne session comme obsolète après un court délai règle proprement la question. Sans cette précaution, la régénération périodique produit des déconnexions aléatoires que personne ne parvient à reproduire. C'est une des raisons pour lesquelles la régénération périodique mérite d'être réservée aux applications où elle apporte réellement quelque chose.
| Réglage | Rôle | Valeur recommandée |
|---|---|---|
| Durée de vie du témoin | Persistance côté navigateur | 0 ou selon besoin métier |
| Durée de conservation serveur | Péremption des données | Supérieure à celle du témoin |
| Transmission chiffrée | Empêche l'interception | Activée |
| Lecture par script interdite | Empêche le vol par injection | Activée |
| Restriction au même site | Limite la falsification | Stricte ou intermédiaire |
| Identifiant dans l'adresse | Fuite par référent et journaux | Désactivé |
| Régénération après connexion | Empêche la fixation | Systématique |
Le stockage des sessions
Par défaut, les sessions sont enregistrées dans des fichiers, dans un répertoire temporaire du système. Cette solution fonctionne parfaitement sur un serveur unique et pose néanmoins deux problèmes distincts. Le premier est le partage : sur un hébergement mutualisé, ce répertoire peut être commun à plusieurs sites, ce qui rend théoriquement lisibles les sessions des voisins. La parade consiste à déclarer un répertoire de session propre au site, en dehors de l'arborescence servie publiquement, réglage qui tient en une ligne et qu'il vaut mieux poser systématiquement. Ce répertoire doit être accessible en écriture par le processus du serveur web et par personne d'autre.
Le second problème apparaît dès que plusieurs serveurs se partagent le trafic. Une session écrite sur une machine n'est pas visible depuis l'autre, ce qui produit des déconnexions apparemment aléatoires, en réalité liées à la machine qui a traité la requête. Trois solutions existent : l'affinité de session au niveau du répartiteur, un stockage partagé sur un système de fichiers commun, ou un stockage en mémoire dans un service dédié. La troisième est la plus robuste et la plus rapide, et elle devient rapidement nécessaire au delà d'un serveur. Elle demande en contrepartie de traiter le cas d'indisponibilité du service de stockage, qui rend alors toutes les sessions inaccessibles.
Le stockage en base de données constitue une quatrième voie, plus lente que la mémoire mais qui présente un avantage réel : elle permet d'interroger les sessions actives, de forcer la déconnexion d'un utilisateur, et de conserver une trace des connexions. Sur une application où la gestion des accès compte, cette capacité justifie souvent le surcoût. Elle permet notamment d'afficher à l'utilisateur la liste de ses sessions actives et de lui offrir de les révoquer, fonction devenue attendue. Elle demande en contrepartie une attention au nettoyage, une table de sessions non purgée devenant rapidement volumineuse. Une tâche de nettoyage quotidienne, supprimant les enregistrements périmés, suffit à maintenir cette table dans une taille raisonnable.
Ce qu'on met dans une session, et ce qu'on n'y met pas
Une session doit contenir le strict nécessaire : l'identifiant de l'utilisateur, éventuellement son niveau de droits, et quelques éléments de contexte comme un panier en cours ou une étape de formulaire. Tout ce qui peut être relu depuis la base à chaque requête n'a pas à y figurer, le coût d'une lecture indexée étant négligeable. Le réflexe consistant à y stocker l'objet utilisateur complet produit des sessions volumineuses, lentes à charger, et surtout périmées : une modification des droits en base ne sera pas prise en compte tant que la session existe. Le cas se manifeste lorsqu'un administrateur retire des droits à quelqu'un et constate que la modification reste sans effet pendant des heures.
On n'y met évidemment aucune donnée sensible en clair, mot de passe, numéro de carte, information de santé. Les sessions stockées en fichiers sont lisibles par quiconque accède au serveur, et celles stockées en base apparaissent dans les sauvegardes. Cette règle paraît évidente et elle est régulièrement enfreinte par des développements où l'on conserve temporairement une donnée le temps d'un tunnel en plusieurs étapes , en particulier sur les parcours de paiement écrits dans l'urgence,, sans jamais la retirer ensuite. Un nettoyage explicite à la fin du tunnel, en supprimant les clés concernées, coûte deux lignes et referme cette porte.
La taille des données mérite également attention. Chaque requête charge et réécrit l'intégralité de la session, ce qui rend le coût proportionnel à son volume. Y stocker un catalogue de résultats de recherche ou une image encodée dégrade sensiblement les performances de toutes les pages. La bonne pratique consiste à n'y placer que des identifiants et à recharger les objets correspondants, ce qui est presque toujours plus rapide que de transporter les données elles mêmes. Une session bien tenue dépasse rarement quelques kilooctets, ordre de grandeur qui sert de repère utile.
Déconnexion, expiration et cas limites
La déconnexion doit faire trois choses et pas seulement une : vider les données de la session, détruire la session côté serveur, et supprimer le témoin côté navigateur en le réémettant avec une date d'expiration passée. Une déconnexion qui se contente de vider le tableau de données laisse une session vivante, et une déconnexion qui ne supprime pas le témoin laisse le navigateur envoyer un identifiant obsolète à chaque requête. Ces trois opérations tiennent en cinq lignes et sont rarement toutes présentes. La suppression du témoin doit reprendre exactement les mêmes attributs de domaine et de chemin que lors de sa création, faute de quoi le navigateur en conserve un second, invisible et toujours valide.
L'expiration par inactivité, distincte de la péremption technique, demande une gestion explicite : on enregistre l'horodatage de la dernière activité dans la session et l'on compare à chaque requête. Cette approche donne un contrôle précis, permet d'afficher un avertissement avant l'échéance et de proposer une prolongation. Elle est indispensable sur les applications manipulant des données sensibles, où une durée courte se justifie, et elle doit s'accompagner d'une sauvegarde du travail en cours pour rester supportable. Un formulaire long dont la saisie disparaît à l'expiration transforme une bonne pratique de sécurité en motif d'abandon.
Un dernier cas mérite d'être mentionné : les sessions sur un site accessible à la fois en clair et en connexion sécurisée. Le témoin marqué comme réservé aux connexions chiffrées ne sera pas envoyé sur la version en clair, ce qui produit une session apparemment perdue. La bonne réponse ne consiste pas à retirer l'attribut mais à imposer la connexion sécurisée partout, par une redirection et par la déclaration correspondante, sujet que nous traitons dans notre article sur le HSTS et sa mise en place. Une fois cette déclaration en place, le navigateur n'essaie même plus la version en clair, ce qui referme définitivement le problème. Il reste à vérifier que la déclaration est bien émise sur toutes les réponses, y compris les erreurs et les redirections, un en tête posé uniquement sur les pages servies normalement laissant ouverte la fenêtre qu'il prétend fermer.
💬 0 commentaires
✍️ Laisser un commentaire
Créez un compte gratuit ou connectez-vous pour commenter.