Skip to main content

Sécurité de la gestion des sessions : bonnes pratiques pour protéger les sessions utilisateur

Écrit par
blog feature pypi spoof

11 juillet 2023

0 minutes de lecture

La sécurité de la gestion des sessions est un élément essentiel du développement d’applications web. Elle protège les sessions utilisateur et empêche les accès non autorisés. La gestion des sessions garantit la confidentialité, l’intégrité et la disponibilité des données sensibles des utilisateurs. Elle protège également leur vie privée en général, ce qui est essentiel pour préserver leur confiance dans une application.

En gérant les sessions de manière sécurisée, nous mettons en place des processus qui détruisent les jetons de session lorsque les utilisateurs se déconnectent ou que leur session prend fin. Cette approche empêche tout accès non autorisé par une personne qui aurait écouté le trafic réseau ou chercherait à usurper l’identité de l’utilisateur.

Dans cet article pratique, nous verrons comment mettre en œuvre une gestion sécurisée des sessions et quelles sont les bonnes pratiques à suivre.

Comprendre la sécurité de la gestion des sessions

Pour comprendre l’importance de la gestion des sessions au niveau du domaine, nous allons développer une application Node.js simple qui met en œuvre des fonctionnalités élémentaires de connexion, de déconnexion et de gestion des sessions.

Prérequis

Pour suivre ce tutoriel, vous aurez besoin des éléments suivants :

Notez que cette démonstration utilise le système d’exploitation Windows et les commandes correspondantes.

Pour découvrir le projet dans son ensemble, consultez le code complet.

Mettre en œuvre des sessions dans Node.js

Pour commencer, ouvrez l’invite de commandes (CMD), puis utilisez le code ci-dessous pour créer un dossier nommé sessions, y accéder et initialiser l’application :

mkdir session 
cd sessions
npm init -y

L’exécution des commandes ci-dessus génère automatiquement un fichier package.json qui répertorie toutes les dépendances installées dans le projet.

Ensuite, nous devons installer quelques packages pour mettre en œuvre les objectifs définis. Il s’agit de express, un framework permettant de créer des serveurs web, et d’express-session, un framework HTTP côté serveur nécessaire à la création d’un middleware de session. Utilisez le code ci-dessous pour installer les deux packages :

npm i express express-session

Ouvrez ensuite le projet dans Visual Studio Code.

Pour accueillir la logique de notre application, nous devons créer un fichier index.js. Dans CMD, utilisez la commande touch index.js pour créer un fichier index.js. Mettez ensuite à jour le fichier index.js à l’aide du code ci-dessous :

const express = require('express'); 
const session = require('express-session');

const  app = express();
const port = 3001;

// Session Middlewares, and Login and Logout Routes go here
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true
}));

// URL-encoded middleware
app.use(express.urlencoded({ extended: true }));

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

L’extrait de code ci-dessus contient le code de démarrage d’une application Node.js. Il configure le middleware de session de l’application Express dans notre projet.

Le middleware URL-encoded traite les requêtes entrantes contenant des données de formulaire encodées dans l’URL dans toute application Express.js.

Ajoutez ensuite le code suivant sous les middlewares du fichier index.js afin de gérer les fonctionnalités de connexion et de déconnexion d’un utilisateur en session :

// Routes
app.get('/', (req, res) => {
  if (req.session.username) {
	res.send(`Welcome to the page, ${req.session.username}! <a href="/logout">Logout</a>`);
  } else {
	res.send('Welcome, Please <a href="/login">login</a>.');
  }
  console.log(req.session)
});

app.get('/login', (req, res) => {
  if (req.session.username) {
	res.redirect('/');
  } else {
	res.send(`
  	  <form method="post" action="/login">
<input type="text" name="username" placeholder="Username" required>
    		<br /> <br />
    		<input type="text" name="password" placeholder="Password" required>
    		<br /> <br />
    		<button type="submit">Login</button>
  	  </form>
	`);
  }
});

// validate your username and password here
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  req.session.username = username;
  req.password = password;
  res.redirect('/');
});

app.get('/logout', (req, res) => {
  req.session.destroy();
  res.redirect('/');
});

Avant de continuer, voyons rapidement ce qui se passe dans le code ci-dessus :

  • La première route, app.get('/'), traite les requêtes adressées à l’URL racine de l’application. Elle souhaite la bienvenue aux utilisateurs dont le nom d’utilisateur est enregistré dans leur session et leur propose un lien pour se déconnecter. Sinon, elle les invite à se connecter.

  • La deuxième route, app.get('/login'), traite les requêtes adressées à l’URL de connexion.

  • La troisième route, app.post('/login'), traite l’envoi du formulaire de connexion.

  • La quatrième route, app.get('/logout'), traite les requêtes GET adressées à l’URL de déconnexion. Elle détruit la session de l’utilisateur et le redirige vers l’URL racine.

Enregistrez toutes les modifications, puis ouvrez votre terminal et exécutez l’application à l’aide de cette commande :

node index.js

Comme nous sommes à l’écoute du port 3001, ouvrez http://localhost:3001/ dans un navigateur. En accédant à cette URL, vous verrez ce simple lien de connexion :

Page web simple affichant le message « Bienvenue ! Veuillez vous connecter. »

Cliquez sur le lien de connexion et saisissez votre nom. Vous accéderez alors à la page d’accueil suivante :

Page web affichant « Bienvenue sur la page, John Doe ! » avec un lien « Se déconnecter ».

Vérifiez maintenant le terminal. Vous y verrez l’objet de session suivant :

Terminal affichant un serveur Node.js à l’écoute sur le port 3001 et un objet de session contenant un cookie et le nom d’utilisateur « John Doe ».

La première propriété est le chemin. Elle représente le chemin valide du cookie de session. Viennent ensuite l’heure d’expiration de la session, la durée de vie du cookie et l’attribut httpOnly défini sur true afin d’empêcher les attaques côté client, comme le cross-site scripting (XSS). L’objet de session affiche également le nom d’utilisateur actuel.

La gestion des sessions et son rôle dans les applications web

Maintenant que nous avons préparé un exemple d’application, voyons comment fonctionne la gestion des sessions et pourquoi elle est si essentielle à leur sécurisation.

Dans les applications web, les interactions entre un utilisateur et une application impliquent généralement des informations sensibles — ici, un mot de passe. La gestion des sessions garantit la sécurité de ces interactions pour les utilisateurs authentifiés et protège les informations fournies pendant leurs sessions. Si cette protection fait défaut, des vulnérabilités liées à la gestion des sessions peuvent apparaître.

Voici quelques vulnérabilités courantes :

  • Détournement de session — Un détournement se produit lorsqu’un attaquant compromet un jeton de session en le volant ou en le devinant, afin d’accéder au serveur sans autorisation au nom de son titulaire. Un attaquant peut s’y prendre de plusieurs façons, notamment en exploitant une faille XSS. Si l’attribut httpOnly de l’application web consultée par l’utilisateur est défini sur false, le risque de détournement de session côté client est très élevé, ce qui ouvre la voie aux attaques XSS.

  • Attaques de l’homme du milieu (MITM) — Il s’agit d’un autre type de détournement de session. Un acteur malveillant peut accéder à une session en interceptant l’identifiant de session pendant les échanges entre l’utilisateur et le serveur. Cela se produit lorsque l’identifiant de session est en texte brut et peut donc être facilement lu par un intercepteur sur le canal de données.

  • Fixation de session — Une fixation de session se produit lorsqu’un attaquant incite l’utilisateur à utiliser un identifiant de session existant pour accéder au serveur, en détournant sa session valide. Cela arrive principalement lorsque l’utilisateur qui accède à la session n’est pas correctement validé à l’aide de l’identifiant de session. Cela peut également se produire lorsqu’un attaquant envoie à la victime un identifiant de session vulnérable dans une URL.

Les risques associés à des processus de gestion des sessions non sécurisés peuvent être considérables. Grâce aux données sensibles obtenues, des acteurs malveillants peuvent commettre des fraudes, des vols et accéder à des informations privilégiées. Ces atteintes à la sécurité sont extrêmement préjudiciables aux utilisateurs et peuvent nuire gravement à la confiance qu’ils accordent à une application.

Heureusement, il existe plusieurs stratégies pour sécuriser nos processus de gestion des sessions. Les sections suivantes présentent quelques méthodes et montrent comment les mettre en œuvre.

Mettre en œuvre des cookies sécurisés

Les cookies peuvent contribuer à sécuriser la gestion des sessions. Ils peuvent relier les utilisateurs à un objet de session côté serveur. Grâce à son identifiant unique, un cookie regroupe toutes les activités associées à l’utilisateur au fil de ses visites.

Par exemple, lorsqu’un utilisateur accède à une application pour se connecter, cette action crée un objet de session et lui attribue un identifiant de session. Le cookie stocke cet identifiant et l’envoie au navigateur en réponse. Lorsque l’utilisateur envoie une requête, le navigateur transmet le cookie, qui permet de vérifier l’identifiant de session et de récupérer l’objet associé. Le serveur peut ainsi valider l’utilisateur et maintenir sa session sur différentes pages et pour différentes requêtes.

Dans cette optique, les développeurs système, les architectes d’applications et les administrateurs doivent veiller à sécuriser au maximum l’utilisation des cookies.

Une façon de sécuriser les cookies consiste à utiliser l’indicateur Secure, qui n’autorise les requêtes que via HTTPS. Il protège l’utilisateur contre les attaques de l’homme du milieu sur le réseau, car les paquets transmis ne sont pas en texte brut et ne peuvent donc pas être lus.

Outre les attaques MITM, les cookies sont également vulnérables aux attaques XSS, car ils transmettent des paquets de données sur le réseau. Pour prévenir ces attaques, nous pouvons utiliser l’indicateur HttpOnly, qui empêche JavaScript de lire le cookie de session.

Pour mettre en œuvre cet indicateur, nous devons vérifier que la valeur booléenne de l’objet de session HttpOnly est définie sur true.

Ouvrez votre terminal et installez cookie-parser, le middleware qui permet de stocker vos cookies, ainsi que bcrypt, la version vulnérable du package, à l’aide de la commande ci-dessous :

npm i cookie-parser bcrypt@v4.0.1

Dans le fichier index.js, mettez ensuite le code à jour comme suit :

const cookieParser = require('cookie-parser');

// Enable cookie parsing
app.use(cookieParser());

// Set up session middleware
app.use(session({
  secret: '38sisjsk92',
  resave: false,
  saveUninitialized: true,
  cookie: {
	httpOnly: true, 
	secure: false, // Sent only over HTTPS if true
  },   
}));

Pour voir les vulnérabilités détectées dans votre code dans Visual Studio Code, vous devez vérifier que Snyk Code est activé sur votre compte. Vous pouvez le faire depuis l’extension Visual Studio Code ou le site web de Snyk. Regardez l’onglet PROBLEMS dans le terminal de Visual Studio Code : vous verrez que Snyk a signalé plusieurs vulnérabilités dans le code.

Panneau Problèmes de VS Code affichant cinq problèmes de sécurité dans une application Express, dont des secrets codés en dur, une protection CSRF désactivée et des paramètres de cookies.

L’analyse révèle la présence d’un secret codé en dur, la désactivation de la protection contre la falsification de requête intersite (CSRF), la désactivation de l’indicateur Secure et la présence d’un package vulnérable.

Snyk propose plusieurs façons de résoudre ces problèmes en suivant les instructions affichées. Corrigeons-en quelques-uns.

Tout d’abord, Snyk recommande d’utiliser des variables d’environnement pour éviter de coder des valeurs en dur. Pour ce faire, ouvrez votre terminal et exécutez les commandes suivantes :

npm i dotenv
touch .env

La première commande installe le package dotenv pour lire les variables d’environnement définies ; la deuxième crée un fichier d’environnement où stocker ces informations sensibles.

Ajoutez maintenant cette configuration au fichier .env :

SESSION_SECRET=38sisjsk92

Dans le fichier index.js, mettez ensuite à jour le middleware de session avec le code suivant :

require('dotenv').config(); // loads env variables 
app.use(session({
  secret: process.env.SESSION_SECRET,
}));

Ces modifications garantissent que le secret sera lu à partir de la variable d’environnement.

Définissez ensuite l’indicateur Secure sur true.

Comme l’activation de la protection CSRF ajouterait une quantité importante de code à notre application, nous ne l’aborderons pas dans cette démonstration. Pensez toutefois à la mettre en œuvre dans vos propres applications à l’aide du package csrf.

Pour résoudre le problème lié au package vulnérable, consultez la documentation officielle de npm et installez une version à jour ou un package bcryptjs plus sûr et mieux testé.

Après ces modifications, une fois que l’extension Snyk aura analysé votre application, vous constaterez que la plupart des erreurs ont disparu. Si vous exécutez l’application à ce stade, localhost ne fonctionnera pas sans HTTPS et continuera de vous rediriger vers l’URL racine.

En plus d’utiliser le stockage local pour les données des requêtes client, vous pouvez stocker les données de session de votre application dans une base de données. Celle-ci permet de redémarrer le serveur sans perdre les données de session, qui sont associées à un identifiant de session unique. Les utilisateurs peuvent ainsi se connecter sans saisir leurs identifiants à chaque fois. Vous pouvez également utiliser des caches mémoire, comme Redis.

Mettre en œuvre des délais d’expiration des sessions

Un délai d’expiration de session survient lorsque la session d’un utilisateur arrive à échéance, ce qui entraîne sa déconnexion automatique du système. Cela réduit la période pendant laquelle une attaque de session peut avoir lieu et garantit la destruction de la session après un délai donné.

Plusieurs facteurs sont à prendre en compte pour définir la durée avant expiration d’une session :

  • Le type d’application — Déterminez la durée d’expiration des sessions adaptée aux données traitées par votre application.

  • Le comportement des utilisateurs — Veillez à ce que les délais d’expiration ne nuisent pas à l’expérience utilisateur.

Les délais d’expiration des sessions se configurent dans le middleware de session. Mettez le code à jour à l’aide de l’extrait ci-dessous pour définir le délai d’expiration à une heure :

// Set up session middleware
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
	maxAge: 3600000, // Expires after 1 hour (in milliseconds)
	httpOnly: true, // Accessible only via HTTP(S)
	secure: false, // Sent only over HTTPS
  },   
}));

Mettre en œuvre une fonctionnalité de déconnexion adaptée

La fonctionnalité de déconnexion permet aux utilisateurs de mettre explicitement fin à leur session lorsqu’ils ont terminé d’utiliser une application. Elle leur donne le contrôle de la fermeture de leur session via les cookies et leur permet de mieux maîtriser leur sécurité.

Cependant, la fonctionnalité de déconnexion nécessite toujours l’intervention des développeurs. Lorsqu’un utilisateur se déconnecte, la session côté client peut prendre fin, mais le cookie de session côté serveur peut ne pas avoir été entièrement invalidé. Un usurpateur peut alors utiliser ces jetons de session et agir comme s’il était l’utilisateur d’origine.

Pour prévenir cette faille de sécurité, nous devons nous assurer que le jeton et l’identifiant de session de l’utilisateur sont supprimés côté serveur. Implémentez cette fonction dans l’application à l’aide du code ci-dessous, en dernière route du fichier index.js :

app.get('/logout', (req, res) => {
  req.session.destroy(err => {
	if (err) {
  	  console.log(err)
	} else {
  	  res.clearCookie('connect.sid');
  	  res.redirect('/');
	}
  });
});

Ce code appelle destroy() avec une fonction de rappel pour supprimer une session côté serveur lorsqu’un utilisateur se déconnecte. Il appelle également clearCookie() et supprime le connection.sid, une clé associée à l’utilisateur connecté.

Si vous examinez les cookies de l’utilisateur connecté dans le navigateur à l’aide de l’option d’inspection, vous devriez voir un connection.sid, comme ci-dessous :

Outils de développement du navigateur affichant un cookie de session connect.sid sur localhost, sélectionné dans le panneau Cookies.

En mettant en œuvre les étapes ci-dessus, nous pouvons permettre aux utilisateurs du système de se déconnecter sans se soucier de leur sécurité en ligne. L’extension Snyk Code vous permet de consulter toutes les analyses de vulnérabilités effectuées dans notre projet et d’en voir l’état pour chacune d’elles. Vous pouvez ainsi vérifier que l’application ne présente aucun risque avant de la mettre à la disposition des utilisateurs.

Dans cet article, nous avons utilisé Snyk pour analyser notre projet et découvrir comment corriger des problèmes de vulnérabilité tels que les secrets codés en dur, les problèmes liés aux sessions et l’utilisation d’un package vulnérable. Maintenant que nous avons corrigé les vulnérabilités signalées par Snyk, notre système n’en présente plus aucune, comme le montre le rapport ci-dessous :

Tableau de bord Snyk en mode sombre affichant l’absence de vulnérabilités ou de problèmes dans les analyses des logiciels open source, du code, de la configuration et de la qualité du code.

Autres bonnes pratiques pour sécuriser la gestion des sessions

En plus des procédures présentées dans cet article, voici quelques autres bonnes pratiques pour gérer les sessions de manière plus sécurisée :

  • Utilisez des identifiants de session sécurisés — Les identifiants de session sécurisés sont de longues chaînes de caractères uniques qui servent à authentifier les sessions utilisateur. Ces identifiants peuvent être générés automatiquement. Leur caractère aléatoire et imprévisible les rend difficiles à deviner, même pour des attaquants méthodiques recourant à des techniques d’analyse statistique. De plus, leur longueur suffit à résister aux attaques par force brute.

  • Optez pour une gestion des sessions côté serveur — La capture d’identifiants de session côté client peut entraîner une vulnérabilité, même si ces identifiants sont générés de manière unique. Nous pouvons stocker ces identifiants côté serveur et les associer aux utilisateurs de l’application. Avec ce type de stockage des sessions, les identifiants sensibles des utilisateurs sont beaucoup mieux protégés, et ces derniers peuvent utiliser le système en toute liberté, sans craindre une usurpation d’identité.

  • Validez correctement les déconnexions — Lorsqu’un utilisateur se connecte à une instance puis s’en déconnecte, le système doit invalider correctement sa session sur toutes les instances auxquelles il a accédé. Pour y parvenir, il peut être nécessaire de synchroniser les données de session entre les instances, ce qui n’est pas toujours simple. Cette étape reste essentielle, car les instances peuvent servir de point d’entrée à des usurpateurs cherchant à voler les informations des utilisateurs.

Conclusion

Dans cet article, nous avons exploré plusieurs stratégies pour renforcer la sécurité de la gestion des sessions, afin de mieux protéger les visiteurs et de réduire le nombre d’attaques. Une gestion sécurisée des sessions protège les informations personnelles, les données et les comptes, même lorsque les utilisateurs laissent des traces de leur activité.

Elle protège également les utilisateurs lorsqu’ils naviguent sur nos sites Web et utilisent nos applications Web. En appliquant les bonnes pratiques et les stratégies de sécurité présentées ici, nous pouvons aider nos utilisateurs à avoir confiance dans la sécurité de leurs identifiants et de leurs informations confidentielles.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

illustration hero ai
Blog

L’ouragan de l’IA est là

L’IA accélère à la fois la création de logiciels et les cyberattaques. Les dirigeants doivent sécuriser les agents et le code dès leur conception, appliquer des contrôles à l’exécution et valider les défenses de manière indépendante.

feature insights context
Blog

La prévention est-elle essentiellement un problème résolu ?

La prévention dans le code généré par les agents est résolue sur le plan architectural, mais le défi reste de choisir des contrôles qui protègent la sécurité sans ralentir le développement.