TW3 — Technologies du Web 3

🔒 ActivitĂ© Pratique : Renforcement de la SĂ©curitĂ© d’un Formulaire de Connexion

Dans cette activité, nous allons prendre un formulaire de connexion de base et y appliquer des bonnes pratiques pour renforcer sa sécurité. L'objectif est de prévenir les attaques courantes comme le XSS, le CSRF, et le brute force, tout en protégeant les données utilisateur.

Objectif de l'activité

Nous prenons un formulaire de connexion de base et y appliquons les bonnes pratiques pour le durcir : prévention des attaques XSS, CSRF et brute force, tout en protégeant les données utilisateur.


🔐 PrĂ©paration du Formulaire de Connexion de Base

Code HTML de départ :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connexion</title>
</head>
<body>
    <form id="loginForm" action="/login" method="POST">
        <label for="username">Nom d'utilisateur :</label>
        <input type="text" id="username" name="username" required>
        
        <label for="password">Mot de passe :</label>
        <input type="password" id="password" name="password" required>
        
        <button type="submit">Se connecter</button>
    </form>
</body>
</html>

Code Node.js de départ :

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.urlencoded({ extended: true }));

app.post('/login', (req, res) => {
    const { username, password } = req.body;
    // Vérification des identifiants (exemple fictif)
    if (username === 'admin' && password === 'password123') {
        res.send('Connexion réussie');
    } else {
        res.status(401).send('Nom d’utilisateur ou mot de passe incorrect');
    }
});

app.listen(3000, () => {
    console.log('Serveur en cours d’exĂ©cution sur http://localhost:3000');
});

Ce formulaire fonctionne, mais il est vulnérable à plusieurs attaques. Nous allons maintenant y ajouter des mesures de sécurité.


🔐 Mesures de SĂ©curisation

🔒 a. Protection contre les Attaques XSS

Vulnérabilité XSS :
Les attaquants peuvent injecter des scripts malveillants dans les champs de formulaire, qui s'exécuteront sur le navigateur de la victime.

Solution :

  • Valider et Ă©chapper toutes les entrĂ©es utilisateur cĂŽtĂ© serveur.
  • Ajouter une Content Security Policy (CSP) pour restreindre les sources de scripts.

Mise en Ɠuvre dans Node.js :

const sanitizeHtml = require('sanitize-html');

app.post('/login', (req, res) => {
    const username = sanitizeHtml(req.body.username);
    const { password } = req.body; // ⚠ ne jamais "sanitizer" un mot de passe : il est comparĂ© tel quel (via hash), pas affichĂ© dans une page HTML

    if (username === 'admin' && password === 'password123') {
        res.send('Connexion réussie');
    } else {
        res.status(401).send('Nom d’utilisateur ou mot de passe incorrect');
    }
});

Ne jamais sanitizer un mot de passe

sanitizeHtml retire ou modifie des caractÚres (<, >, &...) qui peuvent pourtant faire partie d'un mot de passe légitime. L'appliquer au mot de passe le corromprait avant la comparaison. La sanitization HTML protÚge le contenu affiché dans une page ; un mot de passe n'est jamais réinjecté tel quel dans du HTML, il est comparé à un hash stocké (voir bcrypt/argon2 dans les fondamentaux du chapitre).

Ajout d’une CSP dans les headers HTTP :

app.use((req, res, next) => {
    res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'; style-src 'self';"
    );
    next();
});

🔒 b. Protection contre les Attaques CSRF

Vulnérabilité CSRF :
Un attaquant peut forcer un utilisateur authentifiĂ© Ă  envoyer une requĂȘte malveillante (par exemple, en cliquant sur un lien malicieux).

Solution :

  • Utiliser des jetons CSRF pour valider que chaque requĂȘte POST provient de l’utilisateur lĂ©gitime.

csurf est déprécié

Le paquet historique csurf est archivĂ© et non maintenu depuis 2022 (vulnĂ©rabilitĂ©s non corrigĂ©es). Utilisez une alternative maintenue comme csrf-csrf, qui reprend le mĂȘme principe de jeton en double-soumission (cookie + valeur transmise dans le formulaire).

Installation et utilisation de csrf-csrf :


npm install csrf-csrf cookie-parser

Ajout dans l’application :

const cookieParser = require('cookie-parser');
const { doubleCsrf } = require('csrf-csrf');

const { generateToken, doubleCsrfProtection } = doubleCsrf({
    getSecret: () => 'votre_secret_csrf',
    cookieName: 'csrf-token',
});

app.use(cookieParser());

app.get('/login', (req, res) => {
    const csrfToken = generateToken(req, res);
    res.send(`
        <form id="loginForm" action="/login" method="POST">
            <input type="hidden" name="_csrf" value="${csrfToken}">
            <label for="username">Nom d'utilisateur :</label>
            <input type="text" id="username" name="username" required>
            
            <label for="password">Mot de passe :</label>
            <input type="password" id="password" name="password" required>
            
            <button type="submit">Se connecter</button>
        </form>
    `);
});

app.post('/login', doubleCsrfProtection, (req, res) => {
    const { username, password } = req.body;
    if (username === 'admin' && password === 'password123') {
        res.send('Connexion réussie');
    } else {
        res.status(401).send('Nom d’utilisateur ou mot de passe incorrect');
    }
});

🔐 c. Limitation des Attaques par Force Brute

Vulnérabilité :
Les attaquants peuvent essayer plusieurs combinaisons de mots de passe jusqu’à en trouver un correct.

Solution :

  • Limiter le nombre de tentatives de connexion.
  • ImplĂ©menter un dĂ©lai ou un blocage temporaire aprĂšs plusieurs Ă©checs.

Installation et utilisation de express-rate-limit :


npm install express-rate-limit

Ajout dans l’application :

const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // Limite Ă  5 tentatives
    message: 'Trop de tentatives de connexion. Réessayez plus tard.',
});

app.post('/login', loginLimiter, (req, res) => {
    const { username, password } = req.body;
    if (username === 'admin' && password === 'password123') {
        res.send('Connexion réussie');
    } else {
        res.status(401).send('Nom d’utilisateur ou mot de passe incorrect');
    }
});

đŸȘ d. Utilisation de Cookies SĂ©curisĂ©s

Vulnérabilité :
Les cookies peuvent ĂȘtre volĂ©s via des attaques XSS ou envoyĂ©s sur des connexions non sĂ©curisĂ©es.

Solution :

  • Ajouter les options HttpOnly, Secure, et SameSite aux cookies de session.

Exemple :

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

app.use(session({
    secret: 'votre_secret',
    resave: false,
    saveUninitialized: true,
    cookie: {
        httpOnly: true,
        secure: false, // Passez Ă  true si vous utilisez HTTPS
        sameSite: 'strict',
    },
}));

🔐 RĂ©sultat Final

Avec ces améliorations, notre formulaire de connexion est désormais protégé contre :

  1. XSS : Validation des entrĂ©es et ajout d’une CSP.
  2. CSRF : Implémentation de jetons CSRF.
  3. Attaques par force brute : Limitation des tentatives de connexion.
  4. Vol de session : Cookies sécurisés.

app.js complet (toutes les mesures combinées) :

const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const sanitizeHtml = require('sanitize-html');
const { doubleCsrf } = require('csrf-csrf');
const rateLimit = require('express-rate-limit');
const session = require('express-session');

const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
    secret: 'votre_secret',
    resave: false,
    saveUninitialized: false,
    cookie: { httpOnly: true, secure: false, sameSite: 'strict' },
}));

app.use((req, res, next) => {
    res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self'; style-src 'self';"
    );
    next();
});

const { generateToken, doubleCsrfProtection } = doubleCsrf({
    getSecret: () => 'votre_secret_csrf',
    cookieName: 'csrf-token',
});

const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5,
    message: 'Trop de tentatives de connexion. Réessayez plus tard.',
});

app.get('/login', (req, res) => {
    const csrfToken = generateToken(req, res);
    res.send(`
        <form id="loginForm" action="/login" method="POST">
            <input type="hidden" name="_csrf" value="${csrfToken}">
            <label for="username">Nom d'utilisateur :</label>
            <input type="text" id="username" name="username" required>

            <label for="password">Mot de passe :</label>
            <input type="password" id="password" name="password" required>

            <button type="submit">Se connecter</button>
        </form>
    `);
});

app.post('/login', loginLimiter, doubleCsrfProtection, (req, res) => {
    const username = sanitizeHtml(req.body.username);
    const { password } = req.body;

    if (username === 'admin' && password === 'password123') {
        res.send('Connexion réussie');
    } else {
        res.status(401).send('Nom d’utilisateur ou mot de passe incorrect');
    }
});

app.listen(3000, () => {
    console.log('Serveur en cours d’exĂ©cution sur http://localhost:3000');
});

Tester l’application :

  1. Lancez le serveur :
    node app.js
  2. Accédez à http://localhost:3000/login pour tester le formulaire sécurisé.
  3. Effectuez des tests avec des attaques simulĂ©es (par exemple, en injectant des scripts ou en envoyant plusieurs requĂȘtes incorrectes).

Ces bonnes pratiques renforcent significativement la sĂ©curitĂ© des systĂšmes d’authentification tout en maintenant une expĂ©rience utilisateur fluide.

Vérification rapide

Quiz du chapitre

2 questions
0 / 2 répondue0%
  1. 1Dans l'activité de renforcement du formulaire de connexion, quelle mesure est mise en place pour contrer les attaques par force brute ?
  2. 2Quel package est utilisé dans cette activité pour nettoyer les entrées utilisateur et se protéger contre les injections XSS ?
Répondez à toutes les questions pour valider.

Authentification

Terminez le quiz du chapitre pour le marquer comme complété.

Tableau de bord

On this page