121 votes

Comment ajouter correctement un jeton CSRF (cross-site request forgery) en PHP ?

J'essaie d'ajouter une certaine sécurité aux formulaires de mon site web. L'un des formulaires utilise AJAX et l'autre est un simple formulaire "contactez-nous". J'essaie d'ajouter un jeton CSRF. Le problème que je rencontre est que le jeton n'apparaît dans la "valeur" HTML qu'une partie du temps. Le reste du temps, la valeur est vide. Voici le code que j'utilise sur le formulaire AJAX :

PHP :

if (!isset($_SESSION)) {
    session_start();
    $_SESSION['formStarted'] = true;
}

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
}

HTML :

<input type="hidden" name="token" value="<?php echo $token; ?>" />

Des suggestions ?

0 votes

Juste par curiosité, qu'est-ce que token_time est utilisé ?

0 votes

@zerkms Je n'utilise pas actuellement token_time . J'avais l'intention de limiter la durée de validité d'un jeton, mais je n'ai pas encore complètement mis en œuvre le code. Par souci de clarté, je l'ai supprimé de la question ci-dessus.

1 votes

@Ken : l'utilisateur peut donc obtenir le cas où il a ouvert un formulaire, l'a posté et a reçu un jeton invalide ? (puisqu'il a été invalidé)

362voto

Scott Points 389

Pour des raisons de sécurité, ne générez pas vos jetons de cette manière : $token = md5(uniqid(rand(), TRUE));

Essayez ceci :

Générer un jeton CSRF

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

Sidenote : L'un des les projets open source de mon employeur est une initiative de rétroportage random_bytes() y random_int() dans des projets PHP 5. Il est sous licence MIT et disponible sur Github et Composer en tant que paragonie/random_compat .

PHP 5.3+ (ou avec ext-mcrypt)

session_start();
if (empty($_SESSION['token'])) {
    if (function_exists('mcrypt_create_iv')) {
        $_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
        $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
}
$token = $_SESSION['token'];

Vérification du jeton CSRF

Ne vous contentez pas d'utiliser == ou même === , utiliser hash_equals() (PHP 5.6+ uniquement, mais disponible pour les versions antérieures avec l'option hash-compat bibliothèque).

if (!empty($_POST['token'])) {
    if (hash_equals($_SESSION['token'], $_POST['token'])) {
         // Proceed to process the form data
    } else {
         // Log this as a warning and keep an eye on these attempts
    }
}

Aller plus loin avec les jetons par formulaire

Vous pouvez restreindre davantage les jetons à un formulaire particulier à l'aide de la fonction hash_hmac() . HMAC est une fonction de hachage à clé particulière qui peut être utilisée en toute sécurité, même avec des fonctions de hachage plus faibles (par exemple MD5). Toutefois, je recommande d'utiliser plutôt la famille de fonctions de hachage SHA-2.

Tout d'abord, générez un deuxième jeton à utiliser comme clé HMAC, puis utilisez la logique suivante pour le rendre :

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

Et en utilisant une opération congruente lors de la vérification du jeton :

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

Les jetons générés pour un formulaire ne peuvent pas être réutilisés dans un autre contexte sans connaître $_SESSION['second_token'] . Il est important d'utiliser comme clé HMAC un jeton différent de celui que vous venez de déposer sur la page.

Bonus : Approche hybride + intégration de Twig

Toute personne qui utilise le Moteur de modélisation Twig peuvent bénéficier d'une double stratégie simplifiée en ajoutant ce filtre à leur environnement Twig :

$twigEnv->addFunction(
    new \Twig_SimpleFunction(
        'form_token',
        function($lock_to = null) {
            if (empty($_SESSION['token'])) {
                $_SESSION['token'] = bin2hex(random_bytes(32));
            }
            if (empty($_SESSION['token2'])) {
                $_SESSION['token2'] = random_bytes(32);
            }
            if (empty($lock_to)) {
                return $_SESSION['token'];
            }
            return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
        }
    )
);

Avec cette fonction Twig, vous pouvez utiliser les deux jetons d'usage général de la manière suivante :

<input type="hidden" name="token" value="{{ form_token() }}" />

Ou la variante verrouillée :

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twig ne s'occupe que du rendu des modèles ; vous devez toujours valider les jetons correctement. À mon avis, la stratégie Twig offre une plus grande flexibilité et simplicité, tout en maintenant la possibilité d'une sécurité maximale.


Jetons CSRF à usage unique

Si vous avez une exigence de sécurité selon laquelle chaque jeton CSRF ne peut être utilisé qu'une seule fois, la stratégie la plus simple consiste à le régénérer après chaque validation réussie. Cependant, cela invalidera tous les jetons précédents, ce qui ne fait pas bon ménage avec les personnes qui naviguent sur plusieurs onglets à la fois.

Paragon Initiative Enterprises dispose d'une Bibliothèque anti-CSRF pour ces cas particuliers. Il fonctionne exclusivement avec des jetons à usage unique par forme. Lorsque suffisamment de jetons sont stockés dans les données de la session (configuration par défaut : 65535), les jetons les plus anciens non échangés sont éliminés en premier.

27voto

datasage Points 10271

Avertissement de sécurité : md5(uniqid(rand(), TRUE)) n'est pas un moyen sûr de générer des nombres aléatoires. Voir cette réponse pour plus d'informations et une solution qui s'appuie sur un générateur de nombres aléatoires cryptographiquement sécurisé.

Il semble que vous ayez besoin d'un autre élément pour votre "si".

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}

13 votes

Note : Je ne ferais pas confiance à md5(uniqid(rand(), TRUE)); pour les contextes de sécurité.

0 votes

C'est la bonne réponse à la question posée. L'autre réponse avec 300+ upvotes résout un problème que l'OP n'a pas posé.

3voto

Dani Points 13077

La variable $token n'est pas extrait de la session lorsqu'il s'y trouve

0voto

ziyed uddin Points 84

Vous pouvez utiliser la méthode time() avec md5() pour la rendre unique.

if (!isset($_SESSION['token'])) 
{
   $time = time();
   $_SESSION['token'] = md5($time);
   $_SESSION['token_time'] = $time;
}
else
{
   $token = $_SESSION['token'];
}

-1voto

ßãlãjî Points 95

Protection CSRF

TYPES D'UTILISATION DU CRFC

EN FORMULAIRE

<form>
   @csrf
</form>

o

<input type="hidden" name="token" value="{{ form_token() }}" />

META TAG

<meta name="csrf-token" content="{{ csrf_token() }}">

AJAX

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

SESSION

use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $token = $request->session()->token();

    $token = csrf_token();

    // ...
});

PRODUITS INTERMÉDIAIRES

 App\Providers\RouteServiceProvider

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ];
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X