PHP+JS => Authentification par challange

PHP+JS => Authentification par challange - PHP - Programmation

Marsh Posté le 13-02-2007 à 09:48:58    

Bonjour les gens,
j'ai code un petit script php/js permettant de faire une authentification classique par login/pass mais en m'inspirant du principe d'auth par challange (MD5).
le principe etant de ne pas faire passer les identifiants en clair sur le reseau tout en restant compatible avec des navigateurs ne supportant pas l'https ni les modes d'authentification digest d'http.
Mon petit script marchouille mais n'etant pas un expert en securite (vol de session, sniffing, toussa toussa), je m'en vais vous soumettre ma source a vos zoeils experts  :heink:  
 

Code :
  1. <?php session_start();processForm();InitDigest(); ?>
  2. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  3. <html>
  4. <head>
  5.     <title>Alawalagainabistoufly</title>
  6.     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  7.     <script type="text/javascript" src="md5.js"></script>
  8.     <script type="text/javascript">
  9.     function SubmitDiggest()
  10.     {
  11.         if (document.inputForm.login.value != "" && document.inputForm.password.value != "" )
  12.         {
  13.                 document.digestForm.digLogin.value = MD5(document.inputForm.login.value + document.digestForm.diggest.value);
  14.                 document.digestForm.digPassword.value = MD5(document.inputForm.password.value + document.digestForm.diggest.value);
  15.                 document.digestForm.diggest.disabled = true;
  16.                 document.digestForm.submit.disabled = true;
  17.                 return true;
  18.         }
  19.         else
  20.                 return false;
  21.     }
  22.    </script>
  23. </head>
  24. <body>
  25. <p align="center"><b>Wake Me Up</b></p>
  26. <form action="#" name="inputForm">
  27. <table border="0" align="center">
  28.   <tr>
  29.     <td>login:</td>
  30.     <td><input type="text" name="login" value="" /></td>
  31.   </tr>
  32.   <tr>
  33.     <td>password:</td>
  34.     <td><input type="password" name="password" value="" /></td>
  35.   </tr>
  36. </table>
  37. </form>
  38. <div align="center">
  39. <form action="" method="post" name="digestForm" onsubmit="return SubmitDiggest();">
  40.        <input type="hidden" name="digLogin" value="" />
  41.        <input type="hidden" name="digPassword" value="" />
  42.        <input type="hidden" name="diggest" value="<?php echo GetDigest(); ?>" />
  43.        <input type="submit" name="submit" value="Wake Up" />
  44. </form>
  45. </div>
  46. </body>
  47. </html>
  48. <?php
  49. function processForm()
  50. {
  51.         $login = "login";
  52.         $pass = "password";
  53.         if (isset($_POST['digLogin']) && isset($_POST['digPassword']))
  54.         {
  55.                 if (isset($_SESSION['rand']))  // Je ne suis pas vraiment sur que cette condition soit utile. Eventuellement pour éviter un warning..
  56.                                 if ($_POST['digLogin'] === md5($login.$_SESSION['rand']) && $_POST['digPassword'] === md5($pass.$_SESSION['rand']))
  57.                                 {
  58.                                                 echo "auth successful";
  59.                                 }
  60.                                 else
  61.                                 {
  62.                                                  echo "auth failed";
  63.                                 }
  64.                 ClearDigest();
  65.                 exit;
  66.         }
  67. }
  68. function InitDigest()
  69. {
  70.          $_SESSION['rand'] = md5(rand());
  71. }
  72. function GetDigest()
  73. {
  74.         return $_SESSION['rand'];
  75. }
  76. function ClearDigest()
  77. {
  78.         unset($_SESSION['rand']);
  79. }
  80. ?>

Message cité 1 fois
Message édité par azubal le 14-02-2007 à 10:21:20
Reply

Marsh Posté le 13-02-2007 à 09:48:58   

Reply

Marsh Posté le 13-02-2007 à 09:54:45    

Moi, je le fais en 2 formulaires. L'un qui contient les champs de saisis en clair mais qui n'est pas envoyé car il n'a pas de bouton submit. L'autre, qui n'a que 2 champs cachés plus les 2 boutons submit et reset. C'est lui qui est envoyé au serveur et contient le login et le mdp hashés en md5.
Pourquoi 2 formulaires? Je me dis que si le javascript est désactivé sur le poste client, sans le savoir, l'utilisateur va envoyer en clair son login/mdp puisque ta fonction de hashage ne va pas être exécutée...

Reply

Marsh Posté le 13-02-2007 à 10:02:05    

Ah we, tres tres bonne idee ca.
Je m'en vais modifier mon script de ce pas.
Merci a toi rufo  :jap:
 
Edit : voila, j'ai modifie le script dans mon post ci dessus.


Message édité par azubal le 13-02-2007 à 10:17:29
Reply

Marsh Posté le 13-02-2007 à 11:32:16    

le $_SESSION['rand'], c'est pour éviter un vol de session?

Reply

Marsh Posté le 13-02-2007 à 11:39:09    

si c'est le cas... un session_regenerate_id serait plus approprié ;)

Reply

Marsh Posté le 13-02-2007 à 11:43:43    

azubal a écrit :

navigateurs ne supportant pas l'https ni les modes d'authentification digest d'http


Aux dernières nouvelles même Lynx supporte le HTTPS [:pingouino]
 
Il y a juste googolplex fois plus de chances qu'un navigateur supporte HTTPS que de chances qu'il supporte le javascript, mais ne laisses pas les faits t'arrêter [:pingouiono]


---------------
Stick a parrot in a Call of Duty lobby, and you're gonna get a racist parrot. — Cody
Reply

Marsh Posté le 13-02-2007 à 12:08:40    

mais clair, qui ne supporte pas https?[:roane]


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 13-02-2007 à 12:12:37    

Puis bon, je vois pas tout à fait l'intérêt.[:urd]
Tu ne fais que transformer le md5 du mot de passe en mot de passe, qui circule lui-même en clair sur le réseau...il n'y a plus de différence de sécurité entre ton appli et une appli qui fait passer le mot de passe en clair et le stocke en clair pour la vérification...à part que personne ne saura jamais ce que l'utilisateur avait tapé au départ...[:pingouino]


---------------
Can't buy what I want because it's free -
Reply

Marsh Posté le 13-02-2007 à 17:41:08    

Pour ceux qui ne connaissent pas le principe de l'authentification par challenge il y a de bon sites (google) qui l'expliquent...
Pour ceux qui se demande pourquoi je ne veux pas utiliser https, il y a a cela plusieurs raisons :
- les serveurs qui vont héberger le site ne font pas https (et je n'ai pas acces a la conf).
- certains clients sont des téléphones portables et tous ne supportent pas l'https correctement.
- le 3em interet c'est que je voulais voir si c'était faisable :)
 
@rufo : le $_SESSION['rand'] contient le challenge sauvegardé sur le serveur. Il y a d'autres moyens mais ils me semblent plus contraignant (ou alors je ne vois pas)

Reply

Marsh Posté le 13-02-2007 à 17:57:07    

  • Alors avant tu avais ce schéma :


client qui renseigne son mot de passe -> le navigateur l'envoie au serveur -> serveur qui regarde si le pass correspond.
 
Dans ce schéma, le passe circule en clair, on pourrait le sniffer en se trouvant par exemple sur le meme reseau que toi et si le réseau utilise un hub. En récuperant de cette maniere ce que ton pc émet un pirate récupere facilement ton mot de passe.
 

  • Maintenant tu as sécurisé ton authentification de cette maniere :


client qui renseigne son mot de passe -> javascript qui le hash et l'envoie au serveur -> serveur qui regarde si le hash correspond.
 
Dans ce schéma, le pass ne circule pas en clair car il a été hashé via md5 MAIS ce md5 cicurle en clair lui. On pourrait le sniffer en se trouvant par exemple sur le meme reseau que toi et si le réseau utilise un hub. En récuperant de cette maniere ce que ton pc émet un pirate récupere facilement le hash de ton mot de passe. Et ensuite ? Et bien puisque le serveur attend de la part du client un hash md5 et non pas le pass original ... je te laisse réfléchir sur la sécurité qu'ajoute ton script pour la protection de ton mot de passe ... :)
 
Ton script n'apporte pas un peu plus de sécurité ou même un chouilla de sécurité en plus ! Il apporte ... sctrictement rien :)

Reply

Marsh Posté le 13-02-2007 à 17:57:07   

Reply

Marsh Posté le 13-02-2007 à 18:04:15    

Boué j'ai rien dit ... j'avais po vu la concaténation avec ton identifiant de session PHP. :)

Reply

Marsh Posté le 13-02-2007 à 18:16:25    

Moarf :(
Certains ne savent pas lire 10 lignes de codes.. :/
Pour afbilou j'explique :
- le serveur envoi au client le formulaire, avec ce formulaire il envoi aussi un md5 géneré aléatoirement (coté serveur c'est stocké au moyen d'une session, coté client c'est stocké dans un champ hidden du formulaire).
- le client concatène le hash avec le login et le pass puis rehash les resultats des concaténations. qu'il renvoie au serveur (en POST).
- le serveur reçoit les deux hashs et va faire la meme operation que le client, c'est a dire concaténer le digest (hash de départ aléatoire) et les login/pass qu'il a en clair. Ensuite il compare ses hashs a lui et ceux qu'il a recu du client.
 
conclusion : impossible en sniffant de trouver ou retrouver les identifiants.
 
Éventuellement dans une attaque de type Man in the middle en reecrivant le javascript de la page mais c'est deja plus complexe comme type d'attaque.
 
Mon interrogation porte sur la session, a savoir : n'y a t il pas un moyen de la voler ? et même si c'etait le cas, serait il quand même possible de s'authentifier ?!

Reply

Marsh Posté le 13-02-2007 à 19:52:12    

Ca change rien, on peut toujours piquer le md5 généré aléatoirement. Il n'y a strictement aucun moyen de sécuriser totalement un truc qui passe par http. Dès le départ où on peut sniffer le réseau, on fait ce qu'on veut. Https sinon rien :(

Reply

Marsh Posté le 13-02-2007 à 19:59:08    

soit c'est moi qui débloque complètement soit vous avez pas compris le principe de l'authentification par challenge...
Et tu en fais quoi du hash une fois que tu l'a récupéré ?!
 
Edit : ca m'a forcé a chercher un peu et je suis tombé sur cet article qui explique clairement les points forts et les faiblesses de ce type d'authentification : http://www.securiteinfo.com/cryptographie/otp.shtml
 
Bilan les attaques possibles sont le hammering (par dico ou non), le spoofing,le man in the middle,l'hijacking et le social ( :D )

Message cité 1 fois
Message édité par azubal le 13-02-2007 à 20:06:54
Reply

Marsh Posté le 13-02-2007 à 20:37:38    

Si j'ai bien compris

 

1 = client connecte sur page serveur Http
2 = serveur Http envoie un challenge stocké dans form hidden et stock $sess ( fin )
3 = client tape mot de passe
4  = javascript hash( challenge + mot de passe)
5 = si hash client = hash serveur et session serveur = session client ok

 

Attaque en 2 + 5 = lire challenge en clair, decrypté le md5 sniffé en 5, enlever challenge et on a le mot de passe

 

Attaque en 5 : sniffé hash(challenge + mot de passe), renvoyer tel quel au serveur et on est identifié.

 

Pour une session PHP initialisée en  GET, puis validée au POST = ca marche

 

Sans session PHP, méthode mauvaise.

 

Pour réussir l'attaque il faudrait donc pour une session client :

 

- avoir la session PHP ( lisible en clair dans la réponse serveur)
- avoir le hash(challenge + mot de passe ) ( clair entre client serveur )

 

Conclusion: l'effort s'est avéré inutile. Le mot de passe a certe été protégé, mais l'authentification est toujours possible ;
Pour progresser dans ce domaine et en Http tu utiliseras un session_handler() modifié ( regeneration et expiration pour disons 20 minutes) et une couche cookie.
Encore une fois tant que la liaison ne sera pas cryptée le système sera toujours faillible.

 

Pour te persuader utilise Http Headers pour firefox.

 


Avant d'essayer de me contredire, fais les test.

  



Message édité par supermofo le 13-02-2007 à 20:46:14
Reply

Marsh Posté le 13-02-2007 à 21:01:58    

@supermofo.  
Alors il faut savoir que le md5 ne crypte pas.. mais hash... Tout ce que tu passe dans une moulinette MD5 ne peut être retrouvé (sauf par attaque par dictionnaire mais c'est long, et puis dans ce cas il suffit de remplacer MD5 par un algo de hash plus robuste).
Quand a ton attaque (5), tu peux sniffer les hashs du login et du pass autant que tu veux étant donné que de toute manière ils vont changer a chaque requête.
Par contre tu a raison sur un point : on peut voler la session (en clair dans le header de réponse du serveur). D'ou ma question de départ... Maintenant dans mon cas particulier cela ne posera pas de problème car cette authentification ne sert que pour la page de vérification qui n'exécute qu'une seule commande, la session ne stock que le challenge et non pas l'état de l'authentification..
 
le but final étant pas non plus de concurrencer ssl hein mais juste d'offrir un niveau de sécurité acceptable...


Message édité par azubal le 13-02-2007 à 21:08:47
Reply

Marsh Posté le 13-02-2007 à 21:11:56    

azubal a écrit :

soit c'est moi qui débloque complètement soit vous avez pas compris le principe de l'authentification par challenge...
Et tu en fais quoi du hash une fois que tu l'a récupéré ?!
 
Edit : ca m'a forcé a chercher un peu et je suis tombé sur cet article qui explique clairement les points forts et les faiblesses de ce type d'authentification : http://www.securiteinfo.com/cryptographie/otp.shtml
 
Bilan les attaques possibles sont le hammering (par dico ou non), le spoofing,le man in the middle,l'hijacking et le social ( :D )


Ah ok, en fait j'avais pas tout pigé :jap:

Reply

Marsh Posté le 14-02-2007 à 03:19:46    

Déjà entendu parler de rainbow tables ?
Ok supposons que j'ai envie de me craquer le slip sur ton code.

 


1ère hypothèse : on a pas le code source devant les yeux ;)

 

=> function getDigest() retourne  0 < int < 32768 lisible dans le code source
=> plusieurs refresh confirme qu'on a bien affaire à rand()
=> ouvrir le code JS
=> MD5(document.inputForm.login.value + document.digestForm.diggest.value);
=> on peut logiquement s'attendre à une comparaison de chaine binaire ( mais non == )
=> faire un script bruteforce qui ( ignore JS ) , se connecte à ta page , lis le token
 et soumet des combinaisons mot de passe / login correctement formatée.
=> si au bout de plusieurs jours on obtient rien alors lancer un bon gros flood de requete HTTP sur le formulaire ou chercher ailleurs

 

2ème hypothèse: on débuggue

 
Code :
  1. if ($_POST['digLogin'] == md5($login.$_SESSION['rand']) && $_POST['digPassword'] == md5($pass.$_SESSION['rand']))
 

Avec une session non initialisée ( $_SESSION['rand'] === null ), c'est à dire sans passer par la partie HTTP GET du formulaire on revient à un vol de session .

 

Le JS devient:

 
Code :
  1. #
  2. <script type="text/javascript">
  3. #
  4.    function SubmitDiggest()
  5. #
  6.    {
  7. #
  8.        if (document.inputForm.login.value != "" && document.inputForm.password.value != "" )
  9. #
  10.        {
  11. #
  12.                document.digestForm.digLogin.value = MD5(document.inputForm.login.value);
  13. #
  14.                document.digestForm.digPassword.value = MD5(document.inputForm.password.value);
  15. #
  16.                document.digestForm.diggest.disabled = true;
  17. #
  18.                document.digestForm.submit.disabled = true;
  19. #
  20.                return true;
  21. #
  22.        }
  23. #
  24.        else
  25. #
  26.                return false;
  27. #
  28.    }
  29. #
  30.   </script>
 

De suite, je m'empresse de créer un compte sur ton site et je confirme.

 

C'est cool mais les humains passent par la partie HTTP GET et on verra uniquement des hash de type md5(rand().$_post['login'])

 

Ok on test les basiques :

Code :
  1. $pass = '[0-9]asdfsqfdsqfdqsghpppfdgkjfkljsdfgj';
  2. $_POST['pass'] = [0-9];
  3. if($pass == $_POST['pass'])  //retourne true
 

Zut c'est craqué :o : un hash peut commencé par un chiffre non ?

  

A supposer que tu renforces ton code:

 

- il reste une possibilité d'injection SQL
- un XSS peu probable ( mais j'imagine qu'un phishing marcherait à merveille ), une envie de changer le code Javascript
- sniffer est toujours possible meme si le temps est très court
- le bruteforce est toujours possible
- un flood pour tuer le serveur ( je sais pas moi 1000 requetes par sec ? )
- si tu en reviens à de telles solutions, il y a une grande possibilité que l'hebergeur soit peut regardant sur certains accès. D'autres possibilités ?

 


Conclusion: L'idée est bonne, la pratique mauvaise .

 

Message cité 1 fois
Message édité par supermofo le 14-02-2007 à 03:41:44
Reply

Marsh Posté le 14-02-2007 à 09:03:51    

supermofo a écrit :

Déjà entendu parler de rainbow tables ?
Ok supposons que j'ai envie de me craquer le slip sur ton code.
 
 
1ère hypothèse : on a pas le code source devant les yeux ;)
 
=> function getDigest() retourne  0 < int < 32768 lisible dans le code source
=> plusieurs refresh confirme qu'on a bien affaire à rand()
=> ouvrir le code JS  
=> MD5(document.inputForm.login.value + document.digestForm.diggest.value);
=> on peut logiquement s'attendre à une comparaison de chaine binaire ( mais non == )
=> faire un script bruteforce qui ( ignore JS ) , se connecte à ta page , lis le token
 et soumet des combinaisons mot de passe / login correctement formatée.
=> si au bout de plusieurs jours on obtient rien alors lancer un bon gros flood de requete HTTP sur le formulaire ou chercher ailleurs
 
2ème hypothèse: on débuggue
 

Code :
  1. if ($_POST['digLogin'] == md5($login.$_SESSION['rand']) && $_POST['digPassword'] == md5($pass.$_SESSION['rand']))


 
Avec une session non initialisée ( $_SESSION['rand'] === null ), c'est à dire sans passer par la partie HTTP GET du formulaire on revient à un vol de session .
 
Le JS devient:
 

Code :
  1. #
  2. <script type="text/javascript">
  3. #
  4.    function SubmitDiggest()
  5. #
  6.    {
  7. #
  8.        if (document.inputForm.login.value != "" && document.inputForm.password.value != "" )
  9. #
  10.        {
  11. #
  12.                document.digestForm.digLogin.value = MD5(document.inputForm.login.value);
  13. #
  14.                document.digestForm.digPassword.value = MD5(document.inputForm.password.value);
  15. #
  16.                document.digestForm.diggest.disabled = true;
  17. #
  18.                document.digestForm.submit.disabled = true;
  19. #
  20.                return true;
  21. #
  22.        }
  23. #
  24.        else
  25. #
  26.                return false;
  27. #
  28.    }
  29. #
  30.   </script>


 
De suite, je m'empresse de créer un compte sur ton site et je confirme.
 
C'est cool mais les humains passent par la partie HTTP GET et on verra uniquement des hash de type md5(rand().$_post['login'])
 
Ok on test les basiques :

Code :
  1. $pass = '[0-9]asdfsqfdsqfdqsghpppfdgkjfkljsdfgj';
  2. $_POST['pass'] = [0-9];
  3. if($pass == $_POST['pass'])  //retourne true


 
Zut c'est craqué :o : un hash peut commencé par un chiffre non ?
 
 
 
A supposer que tu renforces ton code:
 
- il reste une possibilité d'injection SQL
- un XSS peu probable ( mais j'imagine qu'un phishing marcherait à merveille ), une envie de changer le code Javascript  
- sniffer est toujours possible meme si le temps est très court
- le bruteforce est toujours possible
- un flood pour tuer le serveur ( je sais pas moi 1000 requetes par sec ? )
- si tu en reviens à de telles solutions, il y a une grande possibilité que l'hebergeur soit peut regardant sur certains accès. D'autres possibilités ?
 
 
Conclusion: L'idée est bonne, la pratique mauvaise .


 
Y'a ce site : http://md5.rednoize.com/
 
Mais pour avoir testé, ça ne marche que sur de courtes chaînes de caractères...
 
Sinon, y'a aussi la solution de limiter à 3 tentatives de connexion...

Reply

Marsh Posté le 14-02-2007 à 09:33:11    

pour limiter le vol de session, avant chaque formulaire, tu régénére l'id de session, ce qui rendra obsoléte l'ancienne Id.

Reply

Marsh Posté le 14-02-2007 à 10:16:51    

Hum..
Je pense que supermofo n'a pas compris le but de ma question qui était : "il y a t il une faille dans le script d'authentification en lui meme en laissant de coté toutes les techniques d'attaque de barbare (brute force ou dico) ou assez complexe tel que le man in the middle ou l'hijacking car je sais tres bien que ce script ne pourra rien faire contre de tels attaques.
Cela dit il a poussé la réflexion plus loin donc ce n'est pas un mal :jap:  
En tout cas j'ai testé en pratique le vol de session - dans mon script tel que je m'en sers - et ca ne marche pas car la session est regenéré a chaque authentification.
On peut aussi mettre en place d'autres types de protections comme bloquer l'intervale de temps entre le nombre de connexions et le nombre (oui mais on peut passer par un proxy tu va dire?!).
cela dit tu a soulevé un point interressant auquel je n'avais pas pensé: le type de comparaison qui n'est pas strict entre le hash et le login/pass. Je vais donc arranger ca tout de suite (voir la source ci dessus).

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed