OVH Cloud OVH Cloud

question portant sur l'identification

19 réponses
Avatar
Demosthene
Bonjour,

Je me pose une question curieuse :

J'ai une requète SQL d'identification :

... WHERE password = '".md5($_REQUEST['password'])."' and identifiant
= '".$identifiant_filtré."';

Y a-t-il moyen de forger un md5 malicieux qui pourrait détourner ma
requète ?
Faut-il encoder en base64 le md5 pour être certain d'être à l'abri ?

Merci de m'éclairer

Cordialement Démosthène

10 réponses

1 2
Avatar
Demosthene
Demosthene wrote:
Je me réponds :

Y a-t-il moyen de forger un md5 malicieux qui pourrait détourner ma
requète ?


Après réflexion évidente, le MD5 est une suite de caractères
alphanumérique. J'ai confondu avec un developpement ou j'utilise du
3DES. qui lui peut avoir des caractères ambigües pour une requete SQL

Désolé du bruit

Démosthène

Avatar
Patrick Mevzek
J'ai une requète SQL d'identification :

... WHERE password = '".md5($_REQUEST['password'])."' and identifiant
= '".$identifiant_filtré."';

Y a-t-il moyen de forger un md5 malicieux qui pourrait détourner ma
requète ?


Cela dépend de ce que retourne votre fonction md5(). Si c'est le
résultat de base, vous pouvez récupérer n'importe quel octet, alors
vous allez avoir des soucis...
Si c'est la forme hexadécimale, ou base64, alors non.

Par contre, ce n'est pas une bonne pratique que de stocker ses mots de
passe ainsi dans la base, car ils sont trivialement attaquables via une
attaque de force (il existe des dictionnaires de valeurs MD5
précalculées).
D'autre part MD5 est entaché de failles dernièrement, il est donc à
éviter.

Mais quelque soit le digest utilisé, pour faire ce que vous faites, il
faudrait incorporer, outre le mot de passe, un secret (une chaine de
caractères connues uniquement de votre programme), et une valeur
aléatoire (stockée en même temps que votre mot de passe dans la base).

Cela assure qu'un dictionnaire de valeurs précalculées ne sert à
rien, car pour refaire le calcul il faudrait voler votre programme (pour
avoir le secret), en plus de voler la base de données (pour avoir les
hashs et la valeur aléatoire).

On peut encore améliorer les choses évidemment, mais ce sont déjà les
grandes pistes...

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>

Avatar
Iragaël
Non a priori c'est impossible (encore que certains mathématiciens
soient arrivés à des résultats approchants avec des réseaux
monstrueux de machines).

Juste pour chipoter, c'est encore mieux de passer le password en md5
directement dans le navigateur avant le submit histoire d'éviter le
passage en clair sur le réseau. Javascript a toutes les fonctions pour
ça.

Bill
Avatar
Patrick Mevzek
Y a-t-il moyen de forger un md5 malicieux qui pourrait détourner ma
requète ?


Après réflexion évidente, le MD5 est une suite de caractères
alphanumérique.


Non.
L'espace de valeurs est l'ensemble des entiers sur 128 bits, qu'on
représente souvent en hexadécimal. Mais cela peut être n'importe quelle
combinaison, et si on ne passe pas en hexadécimal, on a donc 16 octets de
valeur quelconque (0 à 255).

Bref si votre fonction md5 retourne 16 octets alors vous avez n'importe
quel octet, et pas seulement ceux représentants les caractères
alphanumériques.

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>


Avatar
Patrick Mevzek

Non a priori c'est impossible


Bien sûr que si, comme on peut récupérer une suite de 16 octets
quelconques, l'un de ces octets peut avoir la valeur décimale 39 (soit le
guillemet simple), et dommage pour la requête qui n'utilise pas de
placeholders.
D'autre part, certains SGBDRs ne supportent pas trop les données
«binaires» dans les champs texte, car il faut appliquer plein de régles
d'échappement (ex: PostgreSQL). Il faut alors utiliser un type binaire
(ex: bytea ou varbit()) ou encoder la donnée.

C'est pour ca qu'on manipule souvent la version hexadécimale (sur 32
octets alors) plutôt que la version binaire pure, pas très facile à
manipuler.

(encore que certains mathématiciens
soient arrivés à des résultats approchants avec des réseaux
monstrueux de machines).


C'est plus subtil que ca, on peut sous certaines conditions produire un
deuxième document qui a le même MD5 qu'un document donné.

Juste pour chipoter, c'est encore mieux de passer le password en md5


Non, car ca ne résiste pas plus à l'attaque que j'ai explicité dans mon
message précédent (précalcul)

D'autre part:
1) javascript peut être désactivé
2) le code peut être altéré par le client, donc si votre application
lui fait confiance, dommage...
3) question performances, bof bof même si ce n'est pas critique ici.

directement dans le navigateur avant le submit histoire d'éviter le
passage en clair sur le réseau.


Pour éviter le passage en clair sur le réseau on utilise HTTPS qui est
la bonne solution, car cela protège tout l'échange, il n'y a pas que le
mot de passe qui soit important.

Javascript a toutes les fonctions pour ça.


Nativement non AFAIK, mais des bibliothèques existent.

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>

Avatar
ftc
Juste pour chipoter, c'est encore mieux de passer le password en md5
directement dans le navigateur avant le submit histoire d'éviter le
passage en clair sur le réseau. Javascript a toutes les fonctions pour
ça.


Et ça change quoi qu'on intercepte le mot de passe en clair ou le mot de
passe haché avec md5 puisque de toute façon on intercepte le mot de
passe dans le format requis par le système ?

Si on veut sécuriser une interface d'identification avec couple
login/mot_de_passe fixés, la seule solution fiable est d'utiliser HTTPS.

Avatar
clifden

Mais quelque soit le digest utilisé, pour faire ce que vous faites, il
faudrait incorporer, outre le mot de passe, un secret (une chaine de
caractères connues uniquement de votre programme), et une valeur
aléatoire (stockée en même temps que votre mot de passe dans la base).



Pourriez-vous developpé un petit peu plus la technique, s'il vous plait?

Avatar
Patrick Mevzek
Mais quelque soit le digest utilisé, pour faire ce que vous faites, il
faudrait incorporer, outre le mot de passe, un secret (une chaine de
caractères connues uniquement de votre programme), et une valeur
aléatoire (stockée en même temps que votre mot de passe dans la base).



Pourriez-vous developpé un petit peu plus la technique, s'il vous plait?


Je ne suis pas sûr de savoir sur quelle partie porte la
question. Je recentre avec quelques idées générales, pas spécifiques
à PHP d'ailleurs, et vous nous direz les points à développer

- pas de mot de passe en clair, jamais, nulle
part, c'est mal, bouh ! On chiffrera les mots de passe si on a besoin de
pouvoir retrouver la valeur en clair (c'est parfois nécessaire). Par
exemple avec RC4. Mais c'est un tout autre sujet, restons sur les digests
ou hachages.

- on stocke le résultat d'un hachage des mots de passe, et on compare les
hachages entre eux

- si on n'a pas réinventé la roue, et qu'on s'est
contenté de réutiliser un algorithme existant et une implémentation
toute faite, on a normalement une probabilité suffisamment faible d'avoir
deux mots de passe différents avec le même hachage, donc pour les
besoins pratiques ca va. De courant, il y MD5 et SHA1 (ou ses extensions,
SHA512, etc...). Ce ne sont pas les seuls.
MD5 est perçu à l'heure actuelle comme irrémédiablement
irrécupérable (on trouve de plus en plus d'attaque contre lui et ca
devient de plus en plus faible) et SHA1 commence à montrer des signes de
faiblesse. Il n'y a pas de vrai remplacant, en tout cas autant déployé
que ces deux là.

- mettre dans une base de données le hachage du mot de passe, tel quel,
plutot que le mot de passe, c'est dangereux, car on est vulnérable à une
attaque de type précalcul. Si quelqu'un vole la base et tous les mots de
passe (enfin leur hachage), et on n'est jamais à l'abri d'une injection
SQL surtout avec les idées reçues à ce sujet qu'on voit fréquemment
ici par exemple, il suffit de comparer les hachages avec une liste
précalculée en prenant un dictionnaire de valeurs courantes (les mots
du dictionnaires et leurs permutations, etc...). Ce genre de listes
existe, et on peut donc ainsi «casser» rapidement une grande partie
des mots de passe, en général (car les utilisateurs utilisent des mots
de passe faible).

- pour déjouer cette attaque, on choisit un secret, une chaine de
caractères quelconque connue uniquement du programme:
$a='EgGAt10UrZ' bref une constante.
Pour trouver cette valeur, il faut voler le programme (le code source).
Quand on recoit un mot de passe on calcule digest($a+$password) qu'on stocke
et on compare de la même façon.
Le + au-dessus signifie en général la concaténation, et on prend
souvent la convention d'utiliser un caractère séparateur (pour déjouer
un certain nombre d'autres attaques, mais on arrive là vraiment à la
frange, donc il y a déjà d'autres choses plus embêtantes à gérer
correctement d'abord), donc en fait
digest($a+'*'+$password)
[idéalement, on devrait s'assurer que le séparateur utilisé ici
l'étoile, mais ca pourrait être n'importe quoi, n'apparaisse ni dans le
secret (facile), ni dans le mot de passe (plus difficile, sauf si on
limite le choix de l'utilisateur)]

En faisant ainsi l'attaque précédente est déjouée : on ne peut plus
utiliser de liste précalculée de valeurs du hachage, il faut connaître
le secret, et après générer les digests.
Il faut bien comprendre que cela n'empêche pas que quelqu'un réussisse
à voler la base (donc tous les hachages) et le code source (donc le
secret), c'est juste qu'a priori ca va lui prendre plus de temps (que de
voler juste la base dans le cas précédent), notamment à cause des
hachages à calculer seulement après le vol, et qui ne serviront que pour
ce méfait là.

On utilisera bien sûr des secrets différents dans des applications
différentes.
Idéalement on le changera de temps en temps, mais ce n'est possible que
si on n'a que faire des mots de passe existants. Ou alors on utilise
plusieurs secrets simultanément, et on note juste, en plus du mot de
passe, quel secret on a utilisé (pas sa valeur évidemment, qui ne doit
pas traîner dans la base, mais par exemple un index permettant de savoir
quel élément d'un certain tableau est utilisé comme secret).

Tout ceci améliore déjà la sécurité, mais ce n'est pas suffisant.
En effet, deux utilisateurs différents, avec le même mot de passe (ce
qui peut arriver souvent, vu qu'on a dit précédemment que les mots de
passe choisis étaient souvent faibles, c'est à dire choisis dans un
espace de valeur restreint, ce qui augmente donc la probabilité que deux
personnes «tombent» sur le même par rapport au cas où l'on choisira
dans l'espace dans sa totalité, en général n'importe quelle valeur)
auront toujours le même hachage dans la base, secret ou pas secret.

Si un utilisateur voit donc tous les hachages, et connait son mot de
passe, s'il voit un autre utilisateur avec le même hachage, il connait
donc le mot de passe d'un autre utilisateur (à cause de la propriété
posée comme vraie en préambule, à savoir probabilité négligeable
d'avoir deux mots de passe différents avec même digest).

Donc on va perturber le mécanisme, de manière différente pour chaque
utilisateur, chaque mot de passe. On créé une graine, tout simplement
une chaine aléatoire, générée au moment du premier stockage dans la
base, et on incorpore cette chaine dans le calcul du hachage.
Deux utilisateurs auront nécessairement deux graines différentes
(pareil on va estimer que la probabilité de retomber deux fois
aléatoirement sur la même valeur est négligeable), et donc même s'ils
ont le même mot de passe, leur hachage sera différent.

Par contre, ultérieurement, pour pouvoir recalculer le hachage avec le
mot de passe donné par l'utilisateur et comparer avec le premier hachage
il faut nécessairement stocker cette graine en clair, en plus du hachage.
Ce n'est pas très grave : si on réussit à voler le hachage, on
réussira aussi à voler cette chaine, donc cette graine ne sert à rien
pour limiter le précalcul, car cette attaque est déjouée par le
mécanisme précédent à savoir le secret du côté de l'application.
La graine permet juste d'assurer qu'on ne tombe jamais sur le même
hachage, même si on a les mêmes mots de passe en entrée.

Pour que ca marche bien, les cryptanalystes nous disent que la graine doit
être au moins aussi longue que le résultat du hachage. Si votre
algorithme de hachage produit 20 octets, il faut une graine de 20 octets.
On peut adopter la même règle pour le secret, ca ne mange pas de pain.


Si on veut ruser, on peut encore améliorer les choses, par exemple,
1) en utilisant plusieurs secrets différents (par exemple selon l'heure
de la journée de la création du premier hachage, ou toute autre
information qu'on stocke de toute façon déjà dans la base comme
«propriétés» de l'utilisateur, et qui ne sont jamais modifiées)
2) en ayant plusieurs secrets, une partie dans l'application et une partie
dans la base de données : c'est cette dernière qui calculerait le
premier hachage, en recevant de l'application certains éléments (le mot
de passe à hacher ou, plus rusé, le résultat d'un premier hachage sur
le mot de passe, et éventuellement un secret précis), et en exécutant
la fonction de hachage (qui existe dans tous les SGBDR je pense pour les
cas simples MD5/SHA1) en son sein.
Cela signifie qu'il faudrait voler à la fois le code source de
l'application pour avoir le premier secret, et réussir à avoir un accès
étendu au SGBDR (ce qui n'est pas forcément possible, même en cas
d'injection SQL, si on joue bien sur les privilèges) pour récupérer le
secret qui y sera en son sein, par exemple dans une extension de votre
crue, donc le code source ne serait même pas disponible (soit grâce aux
privilèges du SGBDR, soit avec un langage compilé).
3) pour palier aux éventuelles déficiences de certains algorithmes de
hachage, on peut faire du HMAC (détaillé dans le RFC2104) qui en gros
fait deux appels au même algorithme de hachage, avec une clef, en plus
des données à hacher.

Voilà donc quelques pistes.

--
Patrick Mevzek . . . . . . Dot and Co (Paris, France)
<http://www.dotandco.net/> <http://www.dotandco.com/>
Dépêches sur le nommage <news://news.dotandco.net/dotandco.info.news>


Avatar
Thomas Harding
Le 20-01-2006, clifden a écrit :
Mais quelque soit le digest utilisé, pour faire ce que vous faites, il
faudrait incorporer, outre le mot de passe, un secret (une chaine de
caractères connues uniquement de votre programme), et une valeur
aléatoire (stockée en même temps que votre mot de passe dans la base).



Pourriez-vous developpé un petit peu plus la technique, s'il vous plait?


Lire la RFC 2617, c'est un bon exercice...

--
Thomas Harding


Avatar
Demosthene
L'espace de valeurs est l'ensemble des entiers sur 128 bits, qu'on
représente souvent en hexadécimal. Mais cela peut être n'importe quelle
combinaison, et si on ne passe pas en hexadécimal, on a donc 16 octets de
valeur quelconque (0 à 255).

Bref si votre fonction md5 retourne 16 octets alors vous avez n'importe
quel octet, et pas seulement ceux représentants les caractères
alphanumériques.


Je vais regarder celà de plus près.

Merci de l'information :)

Démosthène

1 2