session_start();
if(isset($_SESSION['id_user']) && isset($_SESSION['login']) && isset['sel_aleatoire'])) {
} else {
header("Location: http://localhost/mon_site/page_authentification.php");
exit();
}
Je me suis tout d'abord dit que ce n'est pas la peine car ces deux fichiers sont uniquement
if(!isset($_SESSION["login"]))
{exit(header("Location: http://localhost/mon_site/page_authentification.php");
exit();}
//... suite du code
2/ et les failles CSRF qui nécessitent de faire passer une variable (aléatoire et unique) dans le formulaire pour vérifier à réception que les données reçues ont bien été postées depuis le formulaire.
Cette fois-ci la syntaxe est bonne mais pas le code. Il faut comparer les token à une valeur envoyée par la page appelante, c'est leur seule possibilité d'être efficace. Et ils n'ont rien à faire dans un test de vérification d'authentification (à mettre en haut de tes pages protégées) qui se compose d'une simple vérification de variable de session définie lors de l'authentification. Il faut bien distinguer les différentes utilités des deux systèmes de protection : vérification de l'authentification d'une part, et protection des failles CSRF avec les token d'autre part.Effectivement pour le sel aléatoire il fallait écrire $_SESSION['sel_aleatoire'] (erreur d'inattention).
Je t'ai déjà dis qu'on efface $_SESSION["sel"] dès la connexion réussie. Cette variable ne peut donc être utilisée qu'une fois avec la même valeur que celle qui validera l'authentification, et pas deux fois.Dans le cas d'une attaque réseau, sur une place publique par exemple, un pirate récupère l'id-user, le login
et la valeur du sel aléatoire d'un utilisateur.
Il peut donc normalement envoyer sur la session les 3 données qui permettent à l'utilisateur de se connecter,
avec exactement les 3 mêmes valeurs, mais pour lui ça ne marche pas alors que pour l'utilisateur ça marche.
Ca veut donc dire qu'il y a un 4ème facteur qui fait que ça ne marche pas pour lui mais que ça marche pour l'utilisateur.
Tu me dis que ce 4ème facteur vient du grain de sel aléatoire, alors que le pirate et
l'utilisateur utilisent pourtant le même grain de sel aléatoire.
Ce point est compliqué, j'ai du mal à comprendre![]()
Première fonction du sel aléatoire: rendre unique le post pour protéger le mot de passe.Comme je l'ai indiqué le sel aléatoire généré pour l'authentification a deux fonctions distinctes. La première est de rendre unique le post pour protéger le hash du mot de passe,
la seconde est de servir de token pour contrôler que le fichier de destination de la requête ajax ("recuperation_sel.php") reçoit bien des données en provenance du formulaire.
// On compare les deux valeurs et si oui
if($reponse == $compare_bdd)
{
// On efface cette variable de session qui ne sert plus à rien
unset($_SESSION["sel"])
1/// On vérifie le token et les variables, par sécurité et permet d'éviter des requêtes inutiles si on appelait ce fichier directement
if(isset($login,$_SESSION["sel"]) && !empty($token) && $token == $_SESSION["sel"])
Même la valeur du login rentré n'est pas vérifiée puisqu'elle seule sa présence est vérifiée.
Il peut créer une table puisque s'il a piraté le réseau il dispose de toutes les variables sauf le mot de passe. Mais il ne pourra pas créer de table à l'avance à cause du sel aléatoire et non plus se servir d'un dictionnaire. Pour lui c'est très handicapant, et rien ne dit que ton mot de passe n'est pas très long ce qui le rendrait indéchiffrable avant de longues années. Par exemple, pour un administrateur on pourrait imaginer se servir d'une chaine de plusieurs centaines (voir plus) de caractères.Et même s'il pense faire une rainbow table afin de récupérer le mot de passe en clair le grain de sel aléatoire faussera de toute façon tout l'intérêt de la table!
Il me semble que j'ai déjà répondu à cette question suite à une de tes interrogations précédentes. Et d'après toi ?Est-ce que tu le fais simplement car cette variable ne sert plus à rien ou parce que sa présence peut me porter préjudice?
Surtout que le dernier tuto ne fait jamais mention de cette variable. Encore une fois, merci de ne faire référence qu'au dernier tuto ici. Les autres exemples étaient des bouts de code incomplets et tu risque de tout mélanger (la preuve avec cette variable).Je comprends que suite à l'authentification j'enlève mes $_SESSION['sel_aleatoire'] en haut de chacune de mes pages de session
car cela n'a aucune utilité.
Cela fait plusieurs fois que je t'ai montré le code pour vérifier l'authentification. Cela se compose d'une variable de session (login dans mon exemple), les autres sont inutiles mais si tu veux mettre une autre en plus pourquoi pas. En TOUS CAS il ne faut en aucun cas garder le mot de passe en session, haché ou pas (sauf besoin impératif mais qui normalement ne se pose pas). Au passage j'ai de de gros doute sur l'utilité d'un id_user dans ta table puisque normalement "login" doit être unique et doit pouvoir faire office de clé primaire.Autre chose, est-ce qu'à ton avis, dans la foulée, c'est mieux d'effacer aussi la variable du mot de passe
(qui consiste en la concaténation du mot de passe et du sel fixe, cet ensemble hashé, puis lui-même concaténé au sel aléatoire et le tout à nouveau hashé)
et que je déclare en début de session seulement l'id_user et le login vérifiés en haut de chaque page de session?
Ou bien est-ce que tu prônerais plutôt pour le garder, le déclarer en début de session avec l'id_user et le login et vérifier sur chacune de mes pages de session
que j'ai à la fois l'id_user, le login et le mot de passe de l'utilisateur?
<?php
ini_set('session.use_only_cookies', true);
session_start();
header('Content-type: text/html; charset=UTF-8');
// Activer openssl si possible (sur le serveur) pour pouvoir se servir de la fonction 'openssl_random_pseudo_bytes'
function Unique_Sel()
{
return function_exists('openssl_random_pseudo_bytes')? hash("sha512",openssl_random_pseudo_bytes("128", $cstrong)) : hash("sha512",uniqid(rand(), true));
}
// On part du principe que le mot de passe est enregistrer comme ci-dessous en bdd (définir un passe) :
/*
$mot_de_passe = 'b';
// et rentrez ces valeurs en bdd (avec un login correspondant)
echo 'sel = '. $sel = Unique_Sel();
echo '<br>';
echo 'pass = '. hash("sha512",$mot_de_passe.$sel);
*/
// $_SESSION["login"] est définie uniquement si l'authentification est réussie. Si l'on est déjà enregistré normalement on fait un header de redirection vers une page souhaitée par exemple "menu.php"
if(isset($_SESSION["login"]))
{
// Désactivé pour l'exemple.
/*
header('Location: menu.php');
exit;
*/
}
// Teste les retours attendus du formulaire
$reponse = filter_input(INPUT_POST,'reponse', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH);
$login = filter_input(INPUT_POST,'nom', FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
// Définition et récupération du sel aléatoire de session
$_SESSION["sel"] = isset($reponse,$_SESSION["sel"]) ? $_SESSION["sel"] : Unique_Sel();
// Message par défaut
$message = null;
// Initialisation des erreurs
$erreur = null;
// Si le formulaire est envoyé
if (isset($login,$reponse))
{
// Connexion à la base de donnée, ici test sur une bdd en local
// normalement on enregistre ces variables dans un fichier que l'on inclus avec un require
$hostname = "127.0.0.1";
$database = "nom_de_ma_base";
$username = "root";
$password = "";
try
{
// Configuration de PDO et connexion
// récupération mode objet
$pdo_options[PDO::ATTR_DEFAULT_FETCH_MODE] = PDO::FETCH_OBJ;
// mode d'erreurs
$pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
// Indispensable pour ne pas avoir execute qui formate en string avec un tableau en paramètre (incompatible avec la clause limit)
$pdo_options[PDO::ATTR_EMULATE_PREPARES] = false;
//charset reconnu dans la connexion à partir de php 5.3.6 auquel cas on peut supprimer l'option MYSQL_ATTR_INIT_COMMAND ci-dessous
//$pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES utf8";
$connexion = new PDO('mysql:host='.$hostname.';dbname='.$database.';charset=utf8', $username, $password, $pdo_options);
// Sélection du mot de passe correspondant au login (structure pour requête préparée)
$query = "SELECT pass FROM test WHERE login = ?";
// On prépare la requête
$stmt = $connexion->prepare($query);
// On lie la variable avec binParam
$stmt->bindParam(1, $login);
// Et on exécute
$stmt->execute();
// On récupère le tout dans un tableau (ce qui permet ici de compter facilement les résultats)
$result = $stmt->fetchAll();
if (empty($result))
{
// On sait ici que le login est non valide mais pas besoin de renseigner plus présicément les pirates
throw new Exception("Identifiants non valides");
}
// On ne devrait jamais rentrer dans la condition ci-dessous si la table est bien construite avec une clé primaire sur la colonne du login. Cela dit ce contrôle complémentaire ne coûte pas cher...
if (count($result) > 1)
{
throw new Exception("Erreur d'unicité du login");
}
// Récupération du pass (première ligne du tableau $result) qui est égal à : hash("sha512",mot_de_passe.sel_bdd);
$pass = $result[0]->pass;
// On reconstruit la même chaine que celle construite avec javascript pour renseigner le champ "reponse"
$compare_bdd = hash("sha512",$_SESSION["sel"].$pass);
// On compare les deux valeurs et si oui
if($reponse == $compare_bdd)
{
// On efface cette variable de session qui ne sert plus à rien
unset($_SESSION["sel"]);
// On régénère les identifiants de session
session_regenerate_id(true);
// Déclaration de la variable de session témoin de l'enregistrement
$_SESSION["login"] = 1;
// On envoie un message, sauf si on préfère la redirection mise en commentaire juste après
$message = "Vous êtes maintenant connecté !";
// Désactivé pour l'exemple. Normalement on fait un header de redirection vers une page souhaitée par exemple "menu.php"
/*
header('Location: menu.php');
exit;
*/
}
else
{
// On sait ici que le pass est non valide mais pas besoin de renseigner plus présicément les pirates
throw new Exception("Identifiants non valides");
}
}
catch (PDOException $e)// on récupère les erreurs PDO
{
// message en production
$erreur = "Erreur dans la requête d'authentification";
// message complet en développement -> !!!!!!! IMPORTANT mettre la ligne ci-dessous en commmentaires pour la production (pas besoin de renseigner plus précisément les pirates !!!!!!!!
$erreur .= ' : '.$e->getMessage();
}
catch (Exception $e)// puis on récupère les autres erreurs générées avec throw
{
$erreur = $e->getMessage();
}
$message = isset($erreur) ? $erreur : $message;
}
?>
J'ai déjà codé cette partie dans un fichier de traitement de formulaire,
Que pourrais-tu donc faire pour suivre ce conseil ? Bon je t'aide un peu, tu vas ici et tu cliques sur "Download the compressed, production jQuery 1.11.2". Je te laisse imaginer la suite.A noter que par facilité j'utilise jquery et ici avec un lien google. Je conseille plutôt (envers et contre tous ceux qui disent le contraire) de rapatrier jquery sur votre serveur car je conçois mal qu'on puisse se mettre sous la dépendance d'un autre serveur (quand bien même s'appellerait-il google qu'on a vu suffisamment de fois ramer au passage) pour les services importants d'un site.
]<input type="text" name="captcha_response" />
Dans un premier temps, je cherche juste à récupérer sa réponse et à la mettre dans une variable.<input type="text" name="captcha_response" id="captcha_response" onblur="reponseQuestion" />
<!-- ajout d'un id et de l'évènement onblur pour récupérer la réponse lors de la perte du focus.-->
Plus haut, j'écris la fonction reponseQuestion: