PHP Sessioni e autenticazione: prevenire il fixation della sessione

by theArchitect
SHARE
PHP Sessioni e autenticazione: prevenire il fixation della sessione
© Guida-HTML5.it

Introduzione

Quando si parla di sessioni e autenticazione in PHP, molti sviluppatori si concentrano sul login, sulla password e sul controllo degli accessi. C’è però un rischio spesso sottovalutato: il session fixation. Questo attacco avviene quando un aggressore riesce a far usare alla vittima un identificatore di sessione già noto, per poi riutilizzarlo dopo l’autenticazione e impersonare l’utente.

In pratica, se l’ID di sessione non viene rigenerato nel momento giusto, un malintenzionato potrebbe “agganciarsi” alla sessione dell’utente. È un problema molto concreto nelle applicazioni che gestiscono login tramite cookie e sessioni server-side.

In questo tutorial vedremo come proteggere il login dalla session fixation usando PHP in modo corretto, con un esempio completo e facilmente adattabile a un progetto reale.

Codice completo

<?php
declare(strict_types=1);

// Impostazioni di sicurezza per la sessione
ini_set(´session.use_only_cookies´, ´1´);
ini_set(´session.use_strict_mode´, ´1´);

session_set_cookie_params([
    ´lifetime´ => 0,
    ´path´ => ´/´,
    ´domain´ => ´´,
    ´secure´ => isset($_SERVER[´HTTPS´]), // true in HTTPS
    ´httponly´ => true,
    ´samesite´ => ´Lax´,
]);

session_start();

// Simulazione di un "database" utenti
$users = [
    ´mario´ => [
        ´id´ => 1,
        ´username´ => ´mario´,
        ´password_hash´ => password_hash(´Password123!´, PASSWORD_DEFAULT),
        ´role´ => ´user´,
    ],
];

// Logout
if (isset($_GET[´logout´])) {
    $_SESSION = [];

    if (ini_get(´session.use_cookies´)) {
        $params = session_get_cookie_params();
        setcookie(session_name(), ´´, time() - 3600,
            $params[´path´],
            $params[´domain´],
            $params[´secure´],
            $params[´httponly´]
        );
    }

    session_destroy();
    header(´Location: ´ . $_SERVER[´PHP_SELF´]);
    exit;
}

// Login
$error = null;

if ($_SERVER[´REQUEST_METHOD´] === ´POST´) {
    $username = trim($_POST[´username´] ?? ´´);
    $password = $_POST[´password´] ?? ´´;

    if ($username === ´´ || $password === ´´) {
        $error = ´Compila tutti i campi.´;
    } elseif (!isset($users[$username])) {
        $error = ´Credenziali non valide.´;
    } elseif (!password_verify($password, $users[$username][´password_hash´])) {
        $error = ´Credenziali non valide.´;
    } else {
        // Punto chiave: rigenerare l´ID sessione dopo l´autenticazione
        session_regenerate_id(true);

        $_SESSION[´user´] = [
            ´id´ => $users[$username][´id´],
            ´username´ => $users[$username][´username´],
            ´role´ => $users[$username][´role´],
            ´login_time´ => time(),
        ];

        header(´Location: ´ . $_SERVER[´PHP_SELF´]);
        exit;
    }
}

$isLoggedIn = isset($_SESSION[´user´]);
?>

<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login sicuro</title>
</head>
<body>
    <h1>Area riservata</h1>

    <?php if ($isLoggedIn): ?>
        <p>Benvenuto, <strong><?= htmlspecialchars($_SESSION[´user´][´username´]) ?></strong>.</p>
        <p>Ruolo: <strong><?= htmlspecialchars($_SESSION[´user´][´role´]) ?></strong></p>
        <p><a href="?logout=1">Logout</a></p>
    <?php else: ?>
        <h2>Login</h2>

        <?php if ($error): ?>
            <p style="color:red;"><?= htmlspecialchars($error) ?></p>
        <?php endif; ?>

        <form method="post" action="">
            <p>
                <label>Username</label><br>
                <input type="text" name="username" autocomplete="username">
            </p>
            <p>
                <label>Password</label><br>
                <input type="password" name="password" autocomplete="current-password">
            </p>
            <p>
                <button type="submit">Accedi</button>
            </p>
        </form>
    <?php endif; ?>
</body>
</html>

Spiegazione

Il punto più importante del codice è questa riga:

session_regenerate_id(true);

Questa chiamata va eseguita dopo che l’utente ha superato il controllo delle credenziali, cioè nel momento esatto in cui la sessione anonima diventa una sessione autenticata. In questo modo PHP genera un nuovo ID di sessione e invalida quello precedente, riducendo drasticamente il rischio che un ID noto possa essere riutilizzato da terzi.

Perché è necessario?

Quando un visitatore entra nel sito, PHP crea una sessione con un suo identificatore. Se il login avviene senza rigenerare l’ID, la sessione pre-login e quella post-login restano legate allo stesso identificatore. Un attaccante che abbia ottenuto quell’ID prima dell’autenticazione potrebbe sfruttarlo dopo.

Impostazioni di sicurezza usate

  • session.use_only_cookies = 1: evita che l’ID di sessione venga passato via URL, modalità molto più rischiosa.
  • session.use_strict_mode = 1: impedisce a PHP di accettare ID di sessione non validi o creati dall’esterno.
  • Cookie con httponly: JavaScript non può leggere il cookie di sessione, riducendo il rischio in caso di XSS.
  • Cookie con secure: il cookie viene inviato solo su HTTPS.
  • SameSite=Lax: aiuta a limitare l’invio del cookie in contesti cross-site non necessari.

Flusso corretto di autenticazione

  • L’utente apre la pagina e ottiene una sessione anonima.
  • Invia username e password.
  • Il server verifica le credenziali con password_verify().
  • Se il login è corretto, PHP rigenera l’ID della sessione.
  • I dati dell’utente vengono salvati in $_SESSION.
  • Da quel momento, l’utente è considerato autenticato.

Logout sicuro

Anche il logout deve essere gestito bene. Non basta svuotare $_SESSION: è buona pratica distruggere la sessione e cancellare il cookie associato. In questo modo si evita che l’ID resti utilizzabile dal browser o da altri contesti.

Best practice

  • Rigenera sempre l’ID dopo il login: è la difesa principale contro il session fixation.
  • Usa HTTPS: senza HTTPS, qualunque meccanismo di sessione è molto più esposto.
  • Non salvare dati sensibili in sessione: conserva solo ciò che serve davvero, come ID utente, username e ruolo.
  • Verifica le credenziali con password_hash e password_verify: non confrontare mai password in chiaro.
  • Imposta cookie sicuri: httponly, secure e samesite dovrebbero essere la norma.
  • Proteggi anche contro XSS: se un attaccante esegue JavaScript nella pagina, può compromettere la sessione indirettamente.
  • Evita di mostrare l’ID di sessione: non stamparlo mai in pagina, log o URL.
  • Usa session.use_strict_mode: è un’impostazione semplice ma molto utile.

Riepilogo

Il session fixation è una vulnerabilità reale e spesso trascurata nei sistemi di autenticazione PHP. La difesa più importante è semplice: rigenerare l’ID di sessione subito dopo l’autenticazione. A questo si aggiungono configurazioni sicure dei cookie, uso obbligatorio delle sessioni via cookie, HTTPS e una gestione corretta del logout.

Se costruisci un sistema login in PHP, pensa alla sessione come a una credenziale temporanea: va protetta, limitata e rinnovata nei momenti giusti. Con poche righe di codice e buone abitudini puoi aumentare molto la sicurezza della tua applicazione.

Approfondisci con risorse ufficiali

  • Manuale PHP - session_regenerate_id(): documentazione ufficiale sulla rigenerazione dell’ID di sessione.
  • Manuale PHP - Session Security: linee guida ufficiali sulla sicurezza delle sessioni.
  • Manuale PHP - session_set_cookie_params(): configurazione dei parametri del cookie di sessione.
  • Manuale PHP - password_hash(): hashing sicuro delle password.
  • Manuale PHP - password_verify(): verifica sicura delle password hashate.

SHARE