[PHP | MySQL] Protection d'une page via login/mdp

Pour un projet, je dois faire un site internet pour une entreprise. Le site étant fini, j’ai eut une modification de cahier des charges qui n’impose de protéger certaines pages via un login/mot de passe. Le site de développement est hébergé sur ma dedibox, et ira a terme sur un mutualisé 1&1 windows + bdd de base.

Pour bien faire j’ai utilisé une base de donnée MySQL, ça semble nettement mieux qu’un bricolage .htaccess/fichier texte. Après avoir crée un utilisateur MySQL et crée une table “utilisateurs” contenant id/login/mdp correspondant aux utilisateurs du site, j’ai passé les droits de l’utilisateur MySQL en “read only”.

L’accès aux pages protégées se fait par un lien du type “index.php?page=index_page_protegee.php” avec en entête de chaque page protégée un code php qui vérifie si la session est ok :

[code]<?php
if(empty($_SESSION[’_login’])) {
include(‘login.php’);
}

else {
include(‘page_protegee.php’);
?>[/code]

La page de login est un formulaire ou l’on rentre f_login et f_mdp, et passe les données en post a la page verif_login.php :

[code] <?php
session_start();

// Données générales sur le serveur SQL
$host=“localhost”; // Nom du serveur hôte
$username=“utilisateurMySQL”; // Nom d’utilisateur MySQL
$password=“pass”; // Mot de passe MySQL
$db_name=“xxx”; // Nom de la base de donnée MySQL
$tbl_name=“utilisateurs”; // Nom du nom de la table MySQL

// Se conneter au serveur et sélectionner la base de donnée a utiliser
mysql_connect("$host", “$username”, “$password”)or die(“cannot connect”);
mysql_select_db("$db_name")or die(“cannot select DB”);

// Prendre le nom d’utilisateur et le mot de passe rentré dans le formulaire
$f_login=$_POST[‘f_login’];
$f_mdp=$_POST[‘f_mdp’];

$mdp_md5 = md5($f_mdp);

// Protection contre magic quotes
$f_login = stripslashes($f_login);
$mdp_md5 = stripslashes($mdp_md5);
$f_login = mysql_real_escape_string($f_login);
$mdp_md5 = mysql_real_escape_string($mdp_md5);

$sql=“SELECT * FROM $tbl_name WHERE username=’$f_login’ and password=’$mdp_md5’”;
$result=mysql_query($sql);

// Compte le nombre de fois ou le couple login/mot de passe est trouvé dans la table
$count=mysql_num_rows($result);

// Bon si il apparait une seule fois, mauvais sinon
if($count==1){

// Le login/mot de passe ont été validés
$_SESSION[’_login’] = $f_login;
$_SESSION[’_pass’] = $mdp_md5;

echo ‘Connexion ok!’;
}
else {
echo “Mauvais login ou mauvais mot de passe”;
session_unset();
session_destroy();
}
?>[/code]

J’ai md5 le mot de passe, histoire de ne pas avoir un mot de passe en clair dans la bdd. C’est d’une utilité douteuse non ? Vu qu’on peut facilement retrouver le pass a partir du md5 ?

Ensuite il est-ce qu’il y a des points particuliers a vérifier pour se protéger des injections SQL ? Google ne m’a vraiment pas aidé, on tombe surtout sur des site de kikoolol sur des méthodes d’injection, plus ou moins bien fait.

Et dernier point, comment est-ce que je peux me protéger d’un wget sur “page_protegee.php” ? Un htaccess spécifiant localhost seul en lecture suffit ?

En vous remerciant d’avance,

Osh.

pour le stockage des mots de passe, plutot que de stocker le mdp hashé, tu peux:

1 - avoir un “salt” (clef aléatoire alphanumérique) stockée dans un endroit "planqué"
2 - calculer un hash de ($salt . $login . $password) et le stocker.

Comme ça, si ta base est compromise, sans le salt, il est impossible de remonter les mots de passe.

Les injections, je n’ai pas assez travaillé le sujet, donc la parole à quelqu’un d’autre.

Protection aspiration.
Ta (tes) page(s) protégée(s) sont dans un répertoire contenant un .htaccess avec deny from all (donc impossible d’y acceder en http)
Pour la servir, tu fais un file_get_content avec le chemin en dur (/path/to/page_protegee.php)

Sinon, tu includes le bout de code qui vérifie la session dans les pages protégées. Du coup même un aspirateur ne peut pas la lire.

Bon je vais signaler ce qui me choque mais je vais surement oublier des trucs, y a des gens meilleurs que moi en php ici (end of disclaimer)

Ca te semble mais c’est pas forcement vrai. Un fichier htaccess sera toujours mieux qu’un login/pass php mal gaule.

GAH!
NEVER EVER DOING THAT: « index.php?page=index_page_protegee.php »
Pasque bon, tu remplace par « index.php?page=http://perso.pirate.com/always_true.php » et t’es marron (ouais je sais, config php gniagnia on peut l’empecher mais par defaut, ca marche donc c’est MAL)

[quote=« Oshimura, post:1, topic: 51260 »][code]<?php
if(empty($_SESSION[‹ _login ›])) {
include(‹ login.php ›);
}

else {
include(‹ page_protegee.php ›);
?>[/code][/quote]
Il est plus malin de faire un fichier « check_auth.php » qui s’include dans chaque fichier, et qui fera

if(empty($_SESSION['_login'])) { header('Location: login.php'); exit(1); }

Dans la suite, je vire ce qui n’a pas de probleme particulier a ma connaissance (meme si le « or die() » c’est moche)

[quote=« Oshimura, post:1, topic: 51260 »][code] <?php
// Prendre le nom d’utilisateur et le mot de passe rentré dans le formulaire
$f_login=$_POST[‹ f_login ›];
$f_mdp=$_POST[‹ f_mdp ›];

$mdp_md5 = md5($f_mdp);

// Protection contre magic quotes
$f_login = stripslashes($f_login);
$mdp_md5 = stripslashes($mdp_md5);
$f_login = mysql_real_escape_string($f_login);
$mdp_md5 = mysql_real_escape_string($mdp_md5);[/code][/quote]
Oui mais non™
tu utilises une fonction de hash (md5) avant de faire les controles d’usage (stripslash et escape string)???
WRONG!

[quote=« Oshimura, post:1, topic: 51260 »]$sql=« SELECT * FROM $tbl_name WHERE username=‹ $f_login › and password=‹ $mdp_md5 › »;
$result=mysql_query($sql);[/quote]
(cette partie est sujet a controverse mais j’aime bien faire comme ca, perso, ca permet de limiter encore un peu les risques)
WRONG AGAIN!
Ne JAMAIS tester le pass dans sql, NEVER!!
Donc:

$sql="SELECT password FROM $tbl_name WHERE username='$f_login'"; $result=mysql_query($sql);
Et apres tu teste si password est egal a f_mdp en PHP!

Pour la partie sur le comptage des logins, je vois pas l’interet si tu as fait correctement ton ajout d’utilisateur (« le login est deja present »)

[quote date=’ 3/6/2010, 13:05’]// Le login/mot de passe ont été validés
$_SESSION[‹ _login ›] = $f_login;
$_SESSION[‹ _pass ›] = $mdp_md5;[/quote]
GAAAAAAK
Mais mais NO WAY on mets le pass (meme md5) dans une session :smiley:
Generalement on fait $_SESSION[‹ userid ›]=$iduser; et pis c’est tout, pas la peine de multiplier les session.

Ca depend de ce que tu veux faire, mais de mon point de vue ca sert a rien, vu que avec etheral, tu pourras recuperer le pass en clair… Si tu veux faire un truc sioux, tu md5 le pass en javascript (ca reste en local) et tu transmet la cle sur le reseau, ou tu passe par https (ou les deux, mais bon…)

stripslashe et mysql escape truc c’est deja bien. Regarde les see also de php.net pour plus de fonctions sur le sujet.

Hu, WHAT?
quand tu fais un wget sur un fichier PHP, celui ci est execute donc il ne restera plus que le resultat de page_protegee.php et pas le CODE.
Est ce que c’est bien clair pour toi que, quand tu récupère un fichier php par un site web, tu n’obtiens pas le code php mais bien le RESULTAT de l’exécution de ce code?
edit: ah oui, kad avait mieux compris que moi. donc tu mets un include de protection (cf plus haut) et pas ton include(« page protege ») tout moche.

LoneWolf
Cours de sécurité de base en php

Désolé…

Juste une remarque concernant le hashage du mot de passe : non ça ne sert pas à rien… Le sniff réseau n’est absolument pas la raison pour laquelle on hash un mot de passe. Comme tu l’as souligné, le hashage se fait côté serveur. Le hashage en DB sert à plusieurs chose :

  • Protéger les utilisateurs contre un admin peu scrupuleux (j’entend par admin qqun qui a accès à la DB sans être forcément celui qui a codé). Parce qu’il est notoire que les gens utilise un même mot de passe sur plusieurs site différents et qu’il suffit dès lors à l’admin de tester le mdp pour avoir accès à éventuellement facebook etc… Alors certes, le même admin un brin malhonnête peu tenter un reverse du md5, mais en règle général l’admin un peu malhonnête est aussi un admin un peu flemmard. C’est pas un protection 100% mais ça dissuade.
  • Protéger un minimum contre une erreur de code qui fait que le fameux mot de passe est exposé (typiquement un codeur un peu brouillon qui le stock en session et une session qui pour X ou Y raisons s’affiche quelque part, genre une trace de debug qui traine). :smiley:
  • Enfin protéger contre un vol de données (une sauvegarde qui se paume, qqun qui part avec les données etc).

Le MD5 est la méthode la moins bonne pour ce faire, mais ça reste mieux qu’un stockage du mot de passe en clair.

Sinon pour les injections, mysql_real_escape string devrait suffire (stripslashe je sais pas trop en quoi ça protège contre l’injection ?? l’escape string va double slasher les slash présents dans la chaine de toutes manières).

http://phpdebutant.org/article68.php pour une explication claire de pourquoi ta solution d’include est foireuse à la base. :smiley:

Juste pour etre clair, un MD5 ca se reverse pas.

Tu peux faire une attaque par dico et brute force qui va bourriner et qui va essayer de trouver dans une liste de MD5 precalculee les entrees que tu lui donne. C’est en general tres efficaces pour trouver un MD5 qui correspond au MD5 de ton dump de DB. C’est pour ca qu’utiliser un salt est primordial. Tu peux aussi potentiellement trouver un autre mdp qui va generer le meme MD5 mais c’est beaucoup plus dur.

Pour ce qui est SQL, je te conseille de profiter que c’est un ptit truc pour aller mettre ton nez dans des trucs très biens comme pdo.
C’est tout facile, tout légé, tout intégré à php.

[quote=« GloP, post:7, topic: 51260 »]Juste pour etre clair, un MD5 ca se reverse pas.

Tu peux faire une attaque par dico et brute force qui va bourriner et qui va essayer de trouver dans une liste de MD5 precalculee les entrees que tu lui donne. C’est en general tres efficaces pour trouver un MD5 qui correspond au MD5 de ton dump de DB. C’est pour ca qu’utiliser un salt est primordial. Tu peux aussi potentiellement trouver un autre mdp qui va generer le meme MD5 mais c’est beaucoup plus dur.[/quote]
La technique du salt est interessante mais je comprends pas trop comment ca marche la. en gros, on fait md5(salt+pass)=cle a sauvegarder dans la base. Ok
mais le salt est une donnee fixe presente quelque part dans le code PHP non? Le mec qui a acces a la bdd, dans 99% des cas, il a acces aussi au php et donc au salt, donc je pige pas trop l’interet

Mais je pense que j’ai rate un morceau :smiley:

LoneWolf
Missed something :smiley:

[quote=“LoneWolf, post:9, topic: 51260”]La technique du salt est interessante mais je comprends pas trop comment ca marche la. en gros, on fait md5(salt+pass)=cle a sauvegarder dans la base. Ok
mais le salt est une donnee fixe presente quelque part dans le code PHP non? Le mec qui a acces a la bdd, dans 99% des cas, il a acces aussi au php et donc au salt, donc je pige pas trop l’interet[/quote]

Avoir accès a la BDD, c’est quand même plus “facile” (via injection, tout ça, tu fini par afficher un peu tout ce que tu veux) qu’avoir accès au système de fichier du serveur.
Donc là, tu récupère facilement le md5 (+ salt). C’est là que le salt est intéressante car il évite l’utilisation de la force brute pour retrouver ton mot de passe original.

My two cents

@Glop : Aujourd’hui un MD5 ça se bruteforce tellement vite qu’on peut considérer que ça se reverse (même si oui, ce n’est absolument pas le bon terme, my bad). :smiley:

Le salt aide un peu, à condition que le mot de passe et le salt ne soit pas distinguables (parce que bon, si le bruteforce donne « tatalouise12986 » on a vite faite de tester les quelques possibilités)… Mais c’était pas le débat, en gros : mot de passe en clair < mot de passe hashé MD5 < toute autre méthode de hashage.

Lonewolf : il faut que le salt soit une donnée stockée oui, unique pour chaque entrée de la DB, mais pas facilement associable pour quelqu’un qui ne connaît pas le système. Mais clairement, le mec qui a pompé les Data + le code, il a un peu toute les cartes pour remonter les pass, juste une question de temps. Suffit de trouver la méthode d’authentification pour comprendre le salt, et de là le salt n’a plus aucun intérêt on en reviens à un bruteforce bête et con.

[quote=“LoneWolf, post:9, topic: 51260”]mais le salt est une donnee fixe presente quelque part dans le code PHP non? Le mec qui a acces a la bdd, dans 99% des cas, il a acces aussi au php et donc au salt, donc je pige pas trop l’interet

Mais je pense que j’ai rate un morceau :D[/quote]

le gars qui va essayer de trouver le mot de passe à partir du md5 va la plupart du temps utiliser une base existante, dans laquelle vont se trouver les mots du dictionnaire, les mots de passe courant, des noms, des dates, avec les variations courantes (2-3 chiffres a la fin, etc). Mine de rien, ces bases sont très longues à générer, du coup il va etre facile de retrouver “youpi123” à partir de son md5, par contre il y a peu de chance d’y trouver le md5 de “youpi123&-Y0ù;f”, ou alors il faut tout régénérer, même en connaissant le salt ça peut etre très long. Si on ajoute une donnée de l’utilisateur (login, id, e-mail…) dedans en plus, ça veut dire qu’il faudra encore régénérer toute la base pour chaque utilisateur au lieu de se contenter de chercher un seul mdp salé pour tous les utilisateurs.

le but est pas de rendre un mot de passe impossible à trouver, c’est juste de rendre ça suffisamment chiant pour que personne n’essaie. Après, à chaque utilisateur de se trouver un mot de passe qui ne figure pas dans une base de hashs md5 au départ.

Bon et en remettant en perspective,

si un vilain a accès au salt, ça veut dire qu’il a accès au filesystem.
Si il a accès au filesystem, il se contentera d’afficher monfichiersupersecret.php plutot que de reverse le salt+pwd+cequetuveux, non ?

Après, effectivement, ça peut permettre de “casser” l’identité d’un utilisateur sur d’autres sites (en prenant l’hypothèse qu’il utilise le meme couple ailleurs).

Tiens, en y pensant, un truc un peu plus imparable serait d’avoir le hashage a un endroit séparé du serveur (surtout pas accessible avec le ftp du site principal) et réglé pour ne répondre qu’en local.
Appeler cette fonction en lui passant plein de parametres (mais ne pas forcément les utiliser tous lors du hash).
Là ça doit devenir un peu plus poilu à brusquer.

Je suis pas une brute en sécurité et donc je comprends pas ce que tu veux dire par « ça se reverse pas » car si je vous dis:

e2c277bcf2bb2120367172e75b228f77

Il suffit de 1.33 secondes pour savoir ce que ça signifie non? C’est pas ce qu’on entend par un reverse ?

Merci pour la précision.

mmmmmh vu comme ça

mais c’est limite côté obscur, non ? :smiley:

Edit:
Bon en fait c’est un dico en ligne, donc non

[quote=“bdfck, post:15, topic: 51260”]Edit:
Bon en fait c’est un dico en ligne, donc non
[/quote]

Si je comprends bien, si le terme recherché n’existe pas dans la database du site, le “decrypt MD5” ne fonctionnera pas, c’est cela?

Oui. Le but c’est de casser les attaques par dictionnaire en rentrant suffisament de trucs aleatoires dans ta source pour rendre les hash differents de ce qui se trouverait dans un dico pour un mot de passe cree par un humain. C’est la meme chose que de te forcer a choisir un vrai bon mot de passe avec plein de characteres zarbs qui a tres peu de chance de se trouver dans un dico (avec l’avantage en plus de pas creer des entrees de hash similaires sur deux bases de donnees)…

@Boolean : Tu ne peux pas reverse un hash. Un mot de passe qui se reverse c’est un mot de passe encrypté (et non pas hashé).

Pour faire une analogie :

  • Hashage : tu prends un jeu de carte, tu le mélanges en le lançant en l’air et en récupérant les cartes une à une, le mec qui veut entrer doit remettre les cartes dans l’ordre qu’elles avaient AVANT que tu les mélanges. Il n’a aucun autre moyen de trouver le bon ordre que de tous les essayer en espérant tomber sur le bon ordre le plus rapidement possible.

  • Encryption : Le même jeu de carte, tu le réordonne selon une logique prédéfinie (tu peux aussi y ajouter des cartes à intervalle régulier, etc),mais toujours selon une logique. Le mec qui veut entrer devra connaître la logique (la clef) pour remettre le paquet dans le bon ordre.

Donc un hashage ça se bruteforce, une encryption ça se reverse.

Avant propos: je sur-plussoie la première réponse de Lonewolf. J’avais lu le code en vitesse, pensant que Oshimura s’y connaissait un peu plus (j’avais donc pas vérifié les erreurs de débutant genre l’escape après le MD5 ^^).

Personnellement, je préfère SHA au MD5. Le MD5 commence à être un peu faible, ces derniers temps. Il suffit de googler un peu pour trouver des articles qui parlent des failles du MD5. Le salter, ce n’est donc pas suffisant. En plus, ya des sites qui « s’amusent » a calculer des MD5 en brute force (tape voir des séquences MD5 dans google, ca va être fun, tu verras… j’y ai retrouvé quelques un de mes mots de passes).

Et je ne suis pas d’accord avec GloP: j’avais vu un article sur les possibilités de reverser (au moins partiellement) le MD5 suite à une attaque sur la découverte de collisions sur le hash (mais j’arrive plus à mettre la main dessus) qui m’a fait passer tous mes MD5 + salt en SHA + salt.

Par chance PHP est noob friendly avec mysql en ce sens qu’il ne te permet que d’exécuter une seule requête à la fois via mysql_query(). Toutefois cela ne protège pas des injections dans les champs, mais tu semble bien avoir compris la chose en utilisant la fonction d’échappement de mysql (mais pas très bien cf avant propos).

Une autre sécurité serait d’utiliser les prepared queries, mais cela dépend de la version de ton mysql.

Pour cela, le mieux est encode de vérifier dans touts des codes protégés la validité du couple login + password. le htaccess peut être aussi une solution (qui n’est pas valable dans le cas d’une inclusion externe).

Personnellement j’utilise l’objet en PHP5, j’ai pas trop cette problématique: toutes mes pages protégés dérivent d’une page de base qui s’occupe de faire la vérification des crédentials. La solution similaire en itératif est donnée par Lonewolf: une inclusion d’une page check_auth.php sur toutes les pages php et le tour est joué.

[quote=« LoneWolf, post:9, topic: 51260 »]mais le salt est une donnee fixe presente quelque part dans le code PHP non? Le mec qui a acces a la bdd, dans 99% des cas, il a acces aussi au php et donc au salt, donc je pige pas trop l’interet

Mais je pense que j’ai rate un morceau :smiley:

LoneWolf
Missed something :D[/quote]

Non justement le salt ne doit pas que être fixe: s’il est fixe cela veut dire que deux utilisateurs ayant le même mot de passe auront le même hashage, ce qui permet de récupérer le mot de passe originel, mais en deux passes.

Généralement le salt se fait sur des données pseudos aléatoires (par exemple, la date d’inscription de l’utilisateur à un site, ou un nombre aléatoire tiré pour chaque utilisateur) et doit être assez conséquent.

Note: Je note aussi que tu proposes de mettre l’id session dans $_SESSION, autant il faut faire attention à comment sont réglés les sessions: n’oublions pas qu’un identifiant de session est transféré en tant que hashage à chaque requête de page. Capillo-tractons un peu le scénario: imaginons qu’un utilisateur trouve le hashage d’un autre utilisateur: il serait donc identifié en tant que cet autre utilisateur (dépendant de la durée des sessions, mais si des longues sessions sont réglées, cela peut être un vrai problème).

Naturellement il ne faut absolument pas foutre un id de session dans un cookie: là, on s’identifie comme on veut juste en modifiant la valeur de cookie. Je pense donc que Oshimura s’est mélangé les pinceaux: le login + hash mdp aurait bien trouvé sa passe dans un cookie.

humm et tu stocke ca ou? dans la base de données? dans la table user? Je vois pas trop l’intérêt du salt ici vu que le pirate a la cle MD5, le salt et il lui reste plus qu’a trouver le reste…

Ouais mais la on arrive dans des trucs complexe qui me paraissent totalement overkill dans 99% des cas. Au taf, on utilise egroupware qui utilise un moteur de vérification des session de hashage, et c’est juste imbitable. Surtout quand, pour une raison que tu comprends pas, l’auth avec firefox se fait bouler a 100% des cas (a cause de ce mecanisme) alors que ca marche sur IE… (et que le problème est remonte aux devs mais comme ils n’arrivent pas a le simuler, ils ne tentent pas de le regler).

De mon point de vue, la sécurité revient a retarder le moment ou le pirate réussira a passer. Il dépend complètement de l’argent a mettre dedans et des données a proteger. Les exemples données (md5 ou sha, salt, contrôle SYSTEMATIQUE des entrées utilisateurs) me paraissent obligatoire et pas super complique ni couteux a mettre en �?uvre, et c’est déjà une bonne sécurité qui n’existe pas partout. Le nombre de fois que j’ai vu des progs php mettre des trucs complique de md5 et autre bidule comme ca sans faire de contrôle des entrées, mais un truc de fou quoi…

LoneWolf
Plus c’est simple, mieux c’est