[RESOLU] Protection formulaires.

Mammouth du PHP | 558 Messages

11 janv. 2015, 16:03

merci
sirakawa

Eléphant du PHP | 290 Messages

12 janv. 2015, 10:42

Merci beaucoup AB :D

Effectivement, avec tous les liens que tu m'as donné ça change la vie!! :D :D :D
Je vais donc me mettre au travail, ou plutôt à l'étude!!

En attendant, j'aurais une question à te poser (ton dernier message me fait rebondir dessus).
A l'intérieur de ma session à accès réservé j'ai un calendrier du mois courant, qui, via un programme Ajax,
remplace le calendrier du mois courant par celui d'un autre mois lorsque l'utilisateur clique
sur un bouton mois précédent ou un bouton mois suivant.
Je voudrais te demander ce que tu penses 1) de la sécurité du programme Ajax situé sur un fichier externe et écrit entièrement en javascript
et 2) du programme php de réponse du serveur qui ne traite pas avec le SGBD mais qui est entièrement écrit en php.
En effet, tout en haut de chacun de ces deux fichiers je n'ai pas écris quelque chose du genre
(contrairement à toutes les autres pages de la session):
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
en relation avec un calendrier interactif qui ne communique pas avec mon SGBD.
Mais je crois que rajouter le code ci-dessus dans ces deux pages est préférable
pour assurer la sécurité de la totalité de ma session à accès réservé.
Simple question: est-ce que la redirection présentée ci-dessus fonctionne aussi
dans des fichiers qui gèrent l'envoi d'une requête désynchronisée?
Si je rajoute cette partie de code dans chacun des deux fichiers, dois-je remplacer
l'extension js du fichier de mon programme Ajax en .php et du coup dois-je rajouter
des balises de script php et des balises de script javascript pour bien différencier le langage
utilisé dans chaque script?
Comme mon fichier du programme Ajax a une extension en .js je n'ai pas eu besoin
de mettre les balises d'ouverture et de fermeture <script> et </script> mais si maintenant
je change l'extension du fichier...

Je te pose ces questions car elles sont elles-aussi en rapport avec mon message.
Je te donnerai sûremement ma prochaine réponse dans un petit bout de temps
étant donné le boulot que tu m'as donné :D

Eléphant du PHP | 290 Messages

12 janv. 2015, 10:46

En fait, la question sous-jacente et principale est de savoir si la vérification de la présence ou non
du sel aléatoire dans ce code peut faire office de jeton même dans les pages
consacrées à la gestion de requêtes désynchronisées.

ViPHP
AB
ViPHP | 5818 Messages

12 janv. 2015, 23:16

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.

Quand on utilise un formulaire standard (sans processus d'authentification) on utilise de la même manière un nombre aléatoire que l'on passe dans le formulaire pour faire office de "token" et éviter les failles CSRF sur des données sensibles.
(attention dans le tuto j'efface $_SESSION["sel"] une fois l'authentification effectuée, si besoin il faudra te servir d'une autre variable).

C'est pas nécessaire de protéger tes scripts javascript (.js). Que veux tu qu'un pirate en fasse ? Par contre tu peux protéger les fichiers php de destination des requêtes ajax avec un token, mais il faudra modifier le code javascript pour passer le token dans le post. Et c'est pas vraiment nécessaire si le script php ne peut rien faire qui compromettrait ton programme. A priori s'il s'agit simplement de retourner une page de calendrier, bah si un pirate fait des requêtes sur le script php et à condition qu'il connaisse les variables qu'il faut passer pour avoir une réponse, il n'obtiendra en retour qu'une page de calendrier, rien de plus. Reste à savoir si cette page peut contenir des informations confidentielles. Si oui tu peux commencer par faire un contrôle de session et ensuite éventuellement compléter par un token s'il s'agit d'un formulaire qui va changer l'état de ta bdd, ou qui va changer la valeurs de variables sensibles (pour éviter les failles CSRF citées plus haut).
Il faut bien faire la distinction entre les
1/ Failles d'authentification dont il est assez simple de se protéger en vérifiant une variable de session par ex :
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.

Pour les pages php de ton script calendrier tu pourrais faire systématiquement la vérification d'authentification puisque c'est facile à faire.
Les token c'est plus de travail puisqu'il faut les faire passer dans le post (et donc aussi dans les variables javascript s'il s'agit de requêtes ajax) et utile principalement pour les variables sensibles qui peuvent changer l'état de ta bdd. A savoir aussi que les failles CSRF sont quand même beaucoup plus difficiles à exploiter (et à réussir) pour un pirate qu'un défaut de contrôle d'identification qui est une porte grande ouverte. Pour plus d'infos sur la sécurité voir le premier lien que donne la recherche sur les mots clés "oswap french top 10" (c'est un fichier pdf).

Petite remarque le "isset['sel_aleatoire']" dans ton code ne veut rien dire...

Invité
Invité n'ayant pas de compte PHPfrance

13 janv. 2015, 14:14

Merci pour toutes les précisons :D
J'ai rajouté des variables de sessions dans le fichier php de la réponse du serveur pour l'Ajax.

Effectivement pour le sel aléatoire il fallait écrire $_SESSION['sel_aleatoire'] (erreur d'inattention).
A ce propos, si je peux encore te demander un truc qui me trotte dans la tête depuis déjà longtemps...
après je devrais être bien au point.
Je suis d'ailleurs sûr que plein d'autres se posent la même question.

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 #-o

... déduction ...
Il est donc pris en compte le fait que le grain de sel aléatoire "dans les tuyaux"
a été créé ou non dans la session en cours.
Et sauf erreur de ma part, à chaque session est attribué un numéro unique, de sorte que si un intrus
arrive à s'introduire dans la session à accès réservé d'un utilisateur lambda, la session du pirate sera unique à lui aussi.
Autrement dit, le pirate sera sur la session à accès réservé de l'utilisateur mais ne sera jamais sur une de ses sessions (à l'utilisateur).
Et si à la lecture de la variable du grain de sel aléatoire demandé en haut de chaque page de session est associé le numéro
de la session où le grain de sel a été créé, alors on obtient que le grain de sel aléatoire est unique pour une seule session.

Est-ce que c'est comme ça que ça marche?
J'ai peur que mes déductions soient complètement fausses!

ViPHP
AB
ViPHP | 5818 Messages

13 janv. 2015, 22:50

Effectivement pour le sel aléatoire il fallait écrire $_SESSION['sel_aleatoire'] (erreur d'inattention).
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.
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 #-o
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.

Pour le reste si un pirate récupère ton identifiant de session, il peut se faire passer pour toi en partageant le même identifiant de session, et comme il partagera donc ainsi toutes tes variables de session, inutile de les multiplier pour le contrôle de l'authentification. Cela dit ce piratage n'est quand même pas si facile car il faut que le pirate se trouve à proximité pour pirater le réseau. Comme déjà dit plus haut et dans le tuto, seule une connexion ssl permet d'éviter cela et rien d'autre.

Le but du code donné dans le tuto ne peut en aucun cas protéger du vol de session, il faut bien distinguer les problèmes. Dans le code il s'agit de protéger le mot de passe lors de son transfert sur le réseau et aussi de faire en sorte que le post envoyé pour l'authentification ne soit utilisable qu'une seule fois. Et donc en cas de piratage du réseau, les données récupérées ne pourront pas servir directement (il faudrait utiliser la force brute pour retrouver le mot de passe) pour que le pirate puisse se connecter quand il veut et d'où il veut. S'il ne peut pas se servir de ces données il faudra qu'il re pirate le réseau à chaque fois pour voler l'identifiant de session durant une connexion utilisateur et qu'il intervienne durant cette session utilisateur. Si entre temps le visiteur à changé de lieu ou ne se connecte pas, ça sera pas possible.
Le code du tuto permet d'éviter qu'un éventuel piratage du réseau (même une seule fois) ne se transforme en une porte définitivement ouverte pour le pirate, tout en protégeant le mot de passe. C'est le maximum qu'on puisse faire sans ssl.

Après faut pas non plus être trop parano. La plupart des sites actuels, y compris les forum, n'utilisent pas de connexion ssl. Et si le code est bien fait avec un système de droits d'accès, un éventuel piratage de session ne se limitera qu'aux possibilités de l'utilisateur authentifié. Pour les super admin qui ont un maximum de droits on peut leur conseiller de se connecter uniquement depuis chez eux et de protéger au maximum leur liaison wifi ou utiliser une liaison filaire pour réduire considérablement les possibilités de piratage.
Evidemment tous les éléments de la chaine de sécurité interviennent dans la protection. Par exemple plus les mots de passe sont longs, plus ils seront résistants contre les tentatives utilisant la force brute.

Eléphant du PHP | 290 Messages

14 janv. 2015, 19:17

Bonjour,

Je tiens tout d'abord à te remercier pour tout le suivi. C'est effectivement très appréciable
et ça m'aide beaucoup.

J'ai bien sûr lu ton dernier message, puis j'ai relu tous les messages écrits depuis le début, j'ai lu le tuto sur PDO
ainsi que les deux tutos que tu m'as fait connaître (j'ai relu le premier seulement pour m'exercer).
Il me reste encore d'autres choses à voir mais je comprends déjà beaucoup mieux la programmation qu'avant :D

Il ressort que, si je comprends beaucoup mieux l'intérêt du sel aléatoire, il me reste encore des choses non comprises.
Mais il faut dire aussi que comprendre tout l'intérêt d'un sel aléatoire c'est pas évident :roll:

J'ai quelques questions, je mets des petits numéros.

Si je reprends ce que tu m'as dit:
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.
Première fonction du sel aléatoire: rendre unique le post pour protéger le mot de passe.
Effectivement, si une attaque réseau permet à un pirate, sur une place publique par exemple, de voler une session pendant 10 minutes
(avant que l'utilisateur ferme sa session et/ou change de place), il fera beaucoup moins de mal en 10 minutes une seule fois que sur une période et un nombre de fois illimités!!
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!
Tu me dis que juste après l'authentification réussie tu effaces la variable du sel aléatoire, et je le vois d'ailleurs dans le code du tuto:
                // 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/
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?
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é.

2/
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?

Deuxième fonction du sel aléatoire: 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.

Je vois déjà que dans la page recuperation_sel.php, qui communique avec la bdd et récupère le sel fixe de l'utilisateur,
seule la comparaison du token censé être reçu avec le grain de sel aléatoire qui est parti du formulaire permet d'assurer la sécurité de cette page:
// 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.
La vérification se fait juste pour le token.
3/
La sécurité de cette page repose donc entièrement sur le token, c'est bien cela?

4/
En quoi ici le caractère aléatoire du grain de sel aléatoire est-il pertinent?
Je m'exprime, je pourrais très bien envoyer depuis le formulaire une variable avec comme valeur 'nimporte_quoi'
à la place du grain de sel aléatoire pour servir de token.
La valeur est donc fixe, ne bouge pas.
Pourquoi ce serait moins bien qu'un grain de sel aléatoire?
J'ai une petite idée, mais tu me diras toi => car la chaîne de caractères fixe serait visible dans le code source, ce qui n'est pas le cas du grain de sel aléatoire!

Autre point:
On doit vérifier que recuperation_sel.php reçoit bien des données en provenance du formulaire.
On veut donc s'assurer que la personne qui atteint cette page a bien rentré son login dans le formulaire.
5/
Ca a quel intérêt?
De toute façon, n'importe qui peut se rendre sur la page d'authentification, mettre un login au hasard et cliquer
sur envoyer.
la personne aura donc bien un login => if(isset($login
et un sel aléatoire envoyé depuis le formulaire, comparé de l'autre côté avec comme résultat
l'égalité des valeurs puisque c'est le même! => && !empty($token) && $token == $_SESSION["sel"])
Je ne comprends pas l'utilité deu sel aléatoire ici, et c'est pourtant crucial car il
protège à lui seul la page recuperation_sel.php

ViPHP
AB
ViPHP | 5818 Messages

14 janv. 2015, 21:31

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 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.
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?
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 ?
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é.
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).
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?
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.

Les tokens servent pour contrer les "failles csrf" (déjà dit). Rentre cette expression dans un moteur de recherche pour en savoir plus, c'est pour éviter les requêtes forgées (faites en dehors de ton site).

Que voudrais-tu que l'on fasse de plus comme vérification dans la page "recuperation_sel.php" ??
Au passage je te signale que filter_input est une fonction qui filtre. Il faut que tu comprenne toutes les fonctions utilisées (dans un premier temps dans le code php) sinon t'a pas fini de te poser des questions. Tu prends le nom de la fonction tu la colle dans google et tu auras le manuel php très souvent en lien en première ligne.

Je réponds pas à tes questions suivantes, tu devrais trouver par toi même puisque j'ai déjà répondu à chacune d'entre elles. Enfin bon je préfère oublier l'histoire d'un token fixe, tout ça pour ça :evil: (si un token peut être prévu à l'avance il ne sert à rien).

Arrêtes un peu de poser des questions (elles tournent en rond) et bosse le code php pour le comprendre ligne par ligne. Quand tu auras compris tu auras tes réponses (surtout que chaque ligne est commentée).
Et puis rien ne sert de courir, il te faudra plusieurs mois avant de ne plus être débutant. Il faut trouver des réponses aux questions unes à unes, je veux dire bien comprendre la première avant de s'interroger sur la suivante. Avec une lecture globale on peut saisir vaguement le sens, mais dans la réalité d'un code il faut tout saisir avec précision et savoir pourquoi on le fait :wink:

Après c'est normal que tu rames. Le tuto n'est pas prévu pour des débutants contrairement à celui-ci qui propose la base d'une authentification (mais sans sel). Normalement tu aurais du commencer par ce premier, puis acquérir de l'expérience - en faisant des exercices de code fonctionnel dans des pages séparées et pas seulement une lecture - puis t'intéresser aux problèmes de sécurités, puis étudier le dernier tuto avec sel. Tu peux peut-être éviter la première étape, pas les suivantes :wink:

Eléphant du PHP | 290 Messages

15 janv. 2015, 14:54

Salut,

J'avais déjà relu tout le tuto en détail et m'étais au préalable renseigné sur certaines fonctions php
qui m'étaient nouvelles, donc j'avais bien en tête, par exemple,
le sens de filter_input.
Je garde bien en tête que le temps que vous me consacrez est précieux pour vous et pour moi,
et ce n'est bien entendu pas dans mon intention de vous demander des choses que je peux vérifier moi-même
(c'est aussi pour cela que j'ai pris soin de relire tous les messages écrits depuis le début, pour
éviter toute répétition de votre part dans la mesure du possible).

Maintenant, mon problème concerne la récupération du grain de sel fixe.
Tout le reste fonctionne merveilleusement bien!
Ton programme Ajax pour aller chercher le grain de sel fixe à l'air plus sécurisé que le miens,
et il part de la fonction javascript ce qui n'était pas non plus le cas du miens.

J'entends très bien ta recommandation que j'utilise le code du tuto "difficile"
pour terminer ma programmation, et j'entends également que dans cet élan
je ne cherche pas coûte que coûte à tout vouloir comprendre tout de suite.
Je suis OK. Si je termine ma programmation, c'est mission accomplie.
Je laisse donc mes quelques autres interrogations sur le sel aléatoire de côté...

Mais j'ai des difficultés pour terminer cette programmation:

A.
Si je veux utiliser la librairie, je suis donc dépendant de Google.
Si demain il y a un problème au sein de la librairie pour x raison
(le code est devenu obsolète par exemple), je suis complètement coincé.
Et si des utilisateurs me font savoir qu'ils n'arrivent pas à se connecter
je ne serai pas à même de réparer une librairie dont je ne connais pas le code,
ce qui est d'autant plus embêtant "s'il faut faire vite" (ce point est très important).

C'est pour cela que je ne peux m'empêcher de penser si je n'aurais pas intérêt à garder
mon programme Ajax qui par ailleurs marche, et à l'adapter pour qu'il fonctionne à partir de la fonction
js avec un grain de sel aléatoire faisant office de token.

B.
Maintenant, car l'idée c'est quand même de travailler à partir du tuto "difficile",
je vais de toute façon adapter ce tuto à mon site, et non adapter mon site au tuto.
L'idée est de ne pratiquement rien changer de la partie en js,
mais je ne vais pas reprendre la partie suivante:
<?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,
et comme je suis très satisfait de cette partie de mon travail
je ne vais pas tout rechanger.

Je reprends également tel quel tout ton programme sur l'Ajax,
ce qui fait qu'au final je reprends pratiquement tout ce qu'il y a dans ton tuto.

Je risque d'avoir peut-être des soucis d'adaptation du tuto sur mon site,
mais ça je t'en parlerai après dans le cas seulement où je me retrouve complètement bloqué.
Il se peut donc, et je l'espère, que j'y arrive tout seul.

ViPHP
AB
ViPHP | 5818 Messages

15 janv. 2015, 21:53

A/
A ton avis que veut dire :
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.
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.

B/
Si tu ne reprends pas le code php, il faudra faire de toutes façon l'équivalent. Eventuellement tu peux passer à mysqli mais pour le reste la logique doit rester inchangée si tu veux les mêmes fonctionnalités. Et je ne te demande pas d'adapter ton site au tuto, je te demande d'adapter la page de connexion de ton site, et uniquement celle-ci, au tuto. Ou inversement si tu préfère, mais en changeant simplement les css et la partie html par exemple car ce que je veux dire est que tous les éléments de programmation du tuto (nom des champs du formulaire, code php, requête ajax) sont indissociables pour un fonctionnement correct.

Pour les autres pages il suffira de protéger celles qui le nécessitent avec la ligne de code qui vérifie l'existence de la variable de session définie lors de l'authentification. C'est tout.

Tu cherche vraiment des complications inutiles en voulant mélanger ton ancien code et celui-ci. Quel intérêt y-a-t-il puisque celui-ci est complet ? Au pire s"il te manque des champs dans le formulaire il te suffit de les rajouter ce sera infiniment plus simple.

Après, que tu sois satisfait de ton précédent code n'est pas un argument, sauf s'il possède exactement les mêmes fonctionnalités que celles souhaitées. Pour passer certains paliers il vaut parfois mieux tout remettre à plat, car la modifications de certaines lignes ça et là n'est pas suffisante ou obligerait à des codes fastidieux/périlleux/bancaux.

J'ai fais un tuto pour montrer comment passer le palier d'une authentification avec sel côté client et côté serveur. Libre à toi de l'utiliser intelligemment, ou pas. Si t'as pas le temps, laisses tomber et reviens éventuellement sur le problème quand tu auras le temps.

Evites de penser que tu va pouvoir prendre une fonction par ici, une autre par là et secouer le tout pour obtenir un résultat. Il faut maîtriser le code sur le bout des doigts pour faire cela et c'est assez pointu quand il s'agit de sécurité. D'une part tu n'en as pas encore les capacités mais surtout et c'est le plus important, je ne vois strictement aucun intérêt : encore une fois il ne s'agit que du code d'authentification (donc pour la seule page d'authentification) et il est complet.

Eléphant du PHP | 290 Messages

15 janv. 2015, 23:34

En fait, c'est toi qui avait raison!
Je reconnais avoir été un peu têtu :mrgreen:

C'est bien mieux de tout remettre à plat comme tu dis, j'ai tout à gagner!

Du coup, je me suis remis à bosser en utilisant le tuto à 100%.
J'ai en ce moment un soucis pour récupérer le sel fixe via l'Ajax. Il est vide!!
Comme tu m'as conseillé, je m'aide de plusieurs alert et aussi de document.write()
pour comprendre où ça cloche et où ça marche.
A mon avis je devrais trouver la solution la semaine prochaine.

Mais maintenant je te dis juste où j'en suis, je ne pose pas de questions :mrgreen:

ViPHP
AB
ViPHP | 5818 Messages

16 janv. 2015, 01:32

Oui, comme tu l'a compris, l'entêtement n'est pas toujours rentable surtout sur un sujet sensible qu'on ne connaît pas bien. Peu de tutos proposent un code complet et fonctionnel, alors se serait dommage de ne pas en profiter. Rien ne t'empêcheras de comprendre les lignes unes à unes plus tard. Mais en attendant tu seras parti sur de bonne bases qui te permettront d'évoluer plus facilement par la suite.

La procédure "standard" est de partir du tuto installé dans une page vierge, de mettre les bons paramètres de connexion dans les deux fichiers php, sans oublier de créer la table sql pour rendre l'ensemble fonctionnel. Tu l'as déjà fais au moins une fois donc tu sauras le refaire si besoin.

Ensuite quand tu modifies le code tu fais des tests à chaque changement un peu risqué pour vérifier le comportement. Fais des sauvegardes du fichier sous différents noms pour chaque étape un peu importante ça t'éviteras de perdre trop de travail en cas de problème. Ensuite il te suffira de te demander "à quelle étape de mes sauvegardes (et donc à cause de quels changements) mon sel n'est-il plus récupéré ?". Et ainsi de suite pour les autres problèmes qui surviendront :)

Eléphant du PHP | 290 Messages

19 janv. 2015, 19:22

Merci, la méthodologie est excellente!! :D
Du coup, lorsque les identifiants sont corrects j'arrive maintenant à obtenir le message: Vous êtes maintenant connecté !
avec la page et la table de mon site!

J'ai par ailleurs remarqué que le sel fixe de la table du tuto est hashé (oui, je remarque ça seulement maintenant!).
J'ai trouvé ça curieux car chez moi par contre, le sel fixe de l'utilisateur X est en clair.
J'ai appris que dans une bdd j'ai le hash de l'ensemble mot de passe + sel fixe contaténé et à côté le sel fixe en clair.
Toi, non. Est-ce mieux comme tu fais?
A moins que tu aies juste mis un sel fixe qui est en réalité n'est pas hashé, mais paraît hashé de part sa longueur et sa combinaison...

ViPHP
AB
ViPHP | 5818 Messages

19 janv. 2015, 21:28

Dans l'absolu ça sert à rien que le sel soit hasché, l'important est qu'il soit unique et non devinable.

Je m'en sert pour formater la chaine de caractères qui constitue le sel avec une longueur de valeur fixe et aussi parce que j'utilise "openssl_random_pseudo_bytes" qui sinon renvoie des caractères barbares. C'est une façon habituelle de faire.

Eléphant du PHP | 290 Messages

21 janv. 2015, 14:09

Merci.

J'ai fait le plus dur maintenant!!
Mais il me reste à adapter le captcha. C'est pas du gâteau!! :(
Je comprends très bien le code en php, constitué grosso modo de 3 tableaux associatifs imbriqués les uns dans les autres
et d'une fonction array_rand.
Mais c'est le javascript qui me fait des misères!

J'ai un bouton qui permet à l'utilisateur d'écrire la réponse de mon captcha question/reponse:
]<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.
Je m'y prends ainsi:
<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:
[javascript]
<script type="text/javascript">
function reponseQuestion(reponseCaptcha){
var reponseCaptcha = document.getElementById("captcha_response").value;
return reponseCaptcha;
}
</script>
[/javascript]

Hors, une alert reponseCaptcha dans ou en dehors de la fonction ne donne rien.
J'ai fait d'autres tests avec alert qui me laissent dire que la fonction n'est tout simplement pas lue.

Est-ce que tu peux m'aider en m'expliquant pourquoi?

On dirait que le navigateur ne fait pas de lien entre ce qu'il y a dans mon input et ma fonction.

J'ai un peu honte de poser cette question car je sais que c'est du javascript très rudimentaire :oops: