CSRF et CORS : des failles banales aux conséquences catastrophiques

Le rapport PortSwigger Web Security 2024 est sans appel : les attaques Cross-Site Request Forgery (CSRF) représentent 36 % des failles web identifiées. L'OWASP Top 10 2021 place les failles d'injection (dont les injections via requêtes falsifiées) en troisième position des risques les plus critiques pour la sécurité des applications web. Ces chiffres ne reflètent que les failles documentées — la majorité des CSRF ne sont jamais détectées.

Une attaque CSRF réussie permet à un attaquant de faire effectuer à votre utilisateur authentifié n'importe quelle action sur votre site — virement bancaire, modification d'email, suppression de données — sans que l'utilisateur ne s'en aperçoive. Si votre application gère de l'argent, des données personnelles ou des actions irréversibles sans protection CSRF, vous êtes exposé.

Chez ADRD Consulting, la protection CSRF est non négociable sur tout formulaire POST. Voici notre implémentation complète, testée sur RecruteX, MailGuard et QualiTrack.

Comprendre CSRF en 2 minutes

Imaginez un formulaire de virement sur votre banque en ligne :

<form action="https://mabanque.fr/virement" method="POST">
  <input name="montant" value="500">
  <input name="destinataire" value="IBAN_MIEN">
</form>

Un attaquant crée une page piège avec ce formulaire caché, qui s'envoie automatiquement. Si vous visitez la page piège pendant que vous êtes connecté à votre banque, le virement s'effectue — votre navigateur envoie automatiquement votre cookie de session. C'est ça, CSRF : exploiter la confiance que le serveur accorde aux requêtes venant de votre navigateur.

Notre implémentation CSRF : hash_equals + expiration 30 minutes

La défense standard est le token CSRF : une valeur aléatoire générée côté serveur, incluse dans le formulaire, vérifiée à la soumission. Un attaquant ne peut pas connaître cette valeur puisqu'il ne peut pas lire vos cookies ni vos pages HTML (politique same-origin).

<?php
// includes/csrf.php — Gestion tokens CSRF ADRD

function csrf_generate(): string {
    if (session_status() === PHP_SESSION_NONE) session_start();

    $token = bin2hex(random_bytes(32));
    $expiry = time() + 1800; // 30 minutes

    $_SESSION['csrf_token'] = $token;
    $_SESSION['csrf_expiry'] = $expiry;

    return $token;
}

function csrf_verify(string $tokenSoumis): bool {
    if (session_status() === PHP_SESSION_NONE) session_start();

    // Token absent ou expiré
    if (empty($_SESSION['csrf_token']) || empty($_SESSION['csrf_expiry'])) {
        return false;
    }

    // Vérification expiration
    if (time() > $_SESSION['csrf_expiry']) {
        unset($_SESSION['csrf_token'], $_SESSION['csrf_expiry']);
        return false;
    }

    // Comparaison timing-safe — JAMAIS ===
    $valide = hash_equals($_SESSION['csrf_token'], $tokenSoumis);

    // Invalider après usage (token à usage unique)
    unset($_SESSION['csrf_token'], $_SESSION['csrf_expiry']);

    return $valide;
}

function csrf_field(): string {
    return '';
}

Utilisation dans un formulaire :

<form method="POST" action="/traitement">
  <?= csrf_field() ?>
  <input type="email" name="email">
  <button type="submit">Envoyer</button>
</form>

Vérification côté serveur :

require_once 'includes/csrf.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!csrf_verify($_POST['csrf_token'] ?? '')) {
        http_response_code(403);
        die('Token CSRF invalide ou expiré.');
    }
    // Traitement du formulaire
}

SameSite cookies : la deuxième ligne de défense

Les cookies SameSite sont devenus le standard moderne de protection CSRF. Avec SameSite=Strict, le navigateur n'envoie jamais le cookie lors de requêtes cross-site — même si un attaquant génère une requête vers votre site depuis sa page piège.

// Configuration session PHP — à mettre en début de chaque script
ini_set('session.cookie_samesite', 'Strict');  // Protection CSRF maximale
ini_set('session.cookie_httponly', 1);          // Protection XSS
ini_set('session.cookie_secure', 1);            // HTTPS uniquement

Attention : SameSite=Strict bloque aussi les liens depuis des emails ou d'autres sites légitimes. Si votre application a des flows de connexion via liens externes, préférez SameSite=Lax (qui protège contre CSRF tout en permettant la navigation normale).

CORS : ne jamais mettre Allow-Origin: *

Le Cross-Origin Resource Sharing (CORS) est souvent mal configuré par excès de simplicité. Access-Control-Allow-Origin: * ouvre votre API à n'importe quelle origine — y compris des sites malveillants. Notre règle ADRD : toujours spécifier explicitement les domaines autorisés.

// Configuration CORS restrictive ADRD
function setCorsHeaders(): void {
    $allowedOrigins = [
        'https://adrd-consulting.com',
        'https://app.adrd-consulting.com',
        'https://recrutex.adrd-consulting.com'
    ];

    $origin = $_SERVER['HTTP_ORIGIN'] ?? '';

    if (in_array($origin, $allowedOrigins, true)) {
        header("Access-Control-Allow-Origin: {$origin}");
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-CSRF-Token');
        header('Vary: Origin'); // Important pour le cache
    }

    if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
        http_response_code(204);
        exit;
    }
}

Checklist ADRD — Protection CSRF/CORS

36 % des failles web sont des CSRF — et ce sont parmi les plus faciles à prévenir avec les bons automatismes. ADRD Consulting peut auditer votre application et implémenter ces protections. Demandez un audit de sécurité.