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.
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é)
0 votes
@zerkms : Merci, mais je suis un peu perdu. Pourriez-vous me donner un exemple ?
2 votes
@Ken : bien sûr. Supposons que le jeton expire à 10h00. Il est maintenant 09h59. L'utilisateur ouvre un formulaire et obtient un jeton (qui est toujours valide). Il remplit ensuite le formulaire pendant 2 minutes et l'envoie. Tant qu'il est 10h01, le jeton est considéré comme invalide et l'utilisateur reçoit une erreur de formulaire.