Discussion:
Jetons a usage unique
(trop ancien pour répondre)
mpg
2009-04-14 21:15:08 UTC
Permalink
Bonjour,

Dans une discussion voisine a été évoquée la méthode des jetons à usage
unique pour contribuer à sécuriser certains aspects sensibles, comme la
modification/suppression de données.

Avez-vous des liens vers des exemples clairs de mise en œuvre correcte
cette méthode en PHP, et si possible explicant les principaux cas où il
est opportun de l'utiliser ? Ou une bonne âme se sent-elle d'humeur à me
fournir ici même l'exemple et/ou les explications ?

J'avoue que pour l'instant, d'une part je ne sais pas trop comment
mettre en œuvre la méthode (encore que je finirais par y arriver sans
doute), mais surtout je ne suis pas sûr de bien saisir la différence
d'usage avec une session par exemple, ce qui est plus grave.

Merci d'avance !
--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
John GALLET
2009-04-14 21:44:01 UTC
Permalink
Bonjour/soir,
Post by mpg
Dans une discussion voisine a été évoquée la méthode des jetons à usage
unique pour contribuer à sécuriser certains aspects sensibles, comme la
modification/suppression de données.
Ca sert surtout à éviter qu'on te pique ton identifiant de session, vu
qu'il est à usage unique au sens jetable.
Post by mpg
Avez-vous des liens vers des exemples clairs de mise en œuvre correcte
cette méthode en PHP, et si possible explicant les principaux cas où il
est opportun de l'utiliser ? Ou une bonne âme se sent-elle d'humeur à me
fournir ici même l'exemple et/ou les explications ?
Peu importe l'implémentation, que ce soit en tripotant les sessions
natives PHP pour faire changer l'identifiant de session, en les codant
soi même pour le faire changer, ou en ajoutant un jeton dans les données
attachées à une session fixe, le but est le suivant:

Au lieu de faire toujours transiter le login+pass à chaque requête, on
les remplace par un identifiant de session impossible à deviner, de
durée de vie limitée dans le temps. Ca évite qu'ils soient interceptés
et si l'identifiant est récupéré par un attaquant, il n'est valable
qu'un certain temps. Sa fenêtre d'attaque est donc réduite.

Poussons donc le raisonnement plus loin: créons un jeton qui ne servira
a priori à rien s'il est intercepté, un jeton qui n'est accepté qu'une
fois et une seule côté serveur. S'il est intercepté dans le client (par
xss par exemple) on est morts pareil ou presque parce que le script
attaquant via xss se servira du jeton jetable avant l'utilisateur
légitime (voir le worm xss de twitter récemment par exemple) mais si on
récupère ce jeton après son utilisation (par exemple par un referer)
alors il ne servira à rien.
Post by mpg
J'avoue que pour l'instant, d'une part je ne sais pas trop comment
mettre en œuvre la méthode (encore que je finirais par y arriver sans
doute), mais surtout je ne suis pas sûr de bien saisir la différence
d'usage avec une session par exemple, ce qui est plus grave.
Deux solutions: ou il la remplace, ou on l'y adjoint. C'est une variable
transmise par http comme une autre, que ce soit en cookie (beurk) en
get, en post (ou en pigeon voyageur ;-)...), simplement elle est
regénérée à chaque requête pour être vérifiée puis détruite à la
réception suivante.

Bien entendu, si tu n'a utilisé que les sessions natives php 4 et
supérieur, ça peut sembler un peu "magique". Demande toi comment on
gérait les sessions en PHP3 :-) Si besoin (pub éhontée, je ne gagne rien
dessus): http://www.saphirtech.com/p5/web_dynamique.pdf chapitre 10. Il
suffit de remplacer le timeout par un delete systématique.

a++;
JG
Patrick Mevzek
2009-04-15 08:46:51 UTC
Permalink
Post by John GALLET
Post by mpg
J'avoue que pour l'instant, d'une part je ne sais pas trop comment
mettre en œuvre la méthode (encore que je finirais par y arriver sans
doute), mais surtout je ne suis pas sûr de bien saisir la différence
d'usage avec une session par exemple, ce qui est plus grave.
Deux solutions: ou il la remplace, ou on l'y adjoint.
Pour moi il n'y a qu'une seule : c'est en plus, pas à la place.

Sessions et jetons uniques sont orthogonaux. On peut utiliser l'un sans
l'autre, l'autre sans l'un, aucun des deux ou les deux. Mais ils ne
remplissent pas le même rôle et ne sont pas interchangeables.

On peut par contre imaginer construire un mécanisme de persistence de
session uniquement à partir de suite(s) de jetons uniques (et donc sans
identifiant de session mais en ayant une session quand même). J'ai pas
mal réfléchi à cela car c'est plus complexe qu'il n'y paraît (et peut-
être en pratique inutilisable), notamment si on veut permettre l'usage du
bouton BACK par le client ou une navigation multi-onglets ou multi-
utilisateurs (au sens de l'application) dans le même navigateur en même
temps (ce qui me semble être des MUSTs, les 3).
--
Patrick Mevzek . . . . . . . . . . . . . . Dot and Co
<http://www.dotandco.net/> <http://www.dotandco.com/>
<http://www.dotandco.net/ressources/icann_registrars/prices>
<http://icann-registrars-life.dotandco.net/>
John GALLET
2009-04-15 10:30:55 UTC
Permalink
Re,
Post by Patrick Mevzek
Pour moi il n'y a qu'une seule : c'est en plus, pas à la place.
Si c'est utilisé comme tu le décris, tout à fait. Mais même sans pousser
jusqu'à lier un état au jeton (on peut très bien le lier à la session
qui est elle même associée à des droits donc des actions possibles ou
non) il est assez courant par exemple de changer d'identifiant de
session quand on passe de navigation "anonyme" à navigation "logguée"
(i.e. avec login/pass) ou de manière générale quand on change de niveau
de droits d'accès.
Post by Patrick Mevzek
Sessions et jetons uniques sont orthogonaux. On peut utiliser l'un sans
l'autre, l'autre sans l'un, aucun des deux ou les deux. Mais ils ne
remplissent pas le même rôle et ne sont pas interchangeables.
Personnellement je trouve que l'ajout d'une couche de plus ne sert à
rien. On doit de toutes façons conserver côté serveur à "qui"
"appartiennent" les jetons, donc je ne vois pas trop l'intérêt de faire
transiter les deux vu que de toutes façons le rejet du jeton entraînera
le rejet de la requête. Mais je n'ai jamais eu le besoin de pousser le
codage jusqu'au point de ce que tu décris, qui me semble bien tortueux.
On "sait" pertinement côté serveur si les droits associés via l'id
session ou le jeton sont valides pour exécuter l'action associée,
personnellement ça me suffit.
Post by Patrick Mevzek
On peut par contre imaginer construire un mécanisme de persistence de
session uniquement à partir de suite(s) de jetons uniques (et donc sans
identifiant de session mais en ayant une session quand même).
Sachant que tout jeton appartient à une session/à un utilisateur (que tu
le stockes uniquement côté serveur ou que ça transite sur le client)
j'avoue que je ne vois pas où est le problème de modélisation, mais il
se peut parfaitement que je raconte une ânerie, la nuit a été courte.

a++;
JG

Patrick Mevzek
2009-04-15 08:50:12 UTC
Permalink
Post by mpg
Dans une discussion voisine a été évoquée la méthode des jetons à usage
unique pour contribuer à sécuriser certains aspects sensibles, comme la
modification/suppression de données.
Avez-vous des liens vers des exemples clairs de mise en œuvre correcte
cette méthode en PHP, et si possible explicant les principaux cas où il
est opportun de l'utiliser ? Ou une bonne âme se sent-elle d'humeur à me
fournir ici même l'exemple et/ou les explications ?
Pas de lien sous la main, d'autres compléteront, je me permets juste
quelques pistes.

L'usage des jetons uniques permet notamment de résoudre les problèmes
décrits dans certaines règles de
http://innocentcode.thathost.com/rules.html
(le site du bouquin dont j'ai parlé dans le même fil où l'on parlait des
jetons récemment)
que je traduis rapidement :

Règle 3: dans le contexte du serveur, la notion de sécurité côté client
n'existe pas
Règle 19: quand cela est possible, utiliser une couche d'indirection pour
les données générées par le serveur
Règle 20: passer aussi peu d'informations que possible sur l'état interne
du serveur au client
Règle 21: ne pas prendre pour acquis que les requêtes viendront dans un
certain ordre

Cela tourne autour des problèmes de modification (y compris suppression)
de données, typiquement dans une base de données côté serveur (mais peu
importe comment les données sont stockées), et avec différents degrès
d'accès (tout n'est pas modifiable par tout le monde, et il y a
potentiellement une authentification préalable).
On peut aussi de la même façon tenir compte des cas de formulaires
tronçonnés en plusieurs pages quelque soit la raison.

Dans tous ces cas, le serveur doit exposer certains détails (ex: numéro
de l'enregistrement en cours de modification) au client, qui certes a
priori n'en fera rien, mais va les renvoyer ultérieurement, c'est
l'exemple des champs cachés.

S'il y a plusieurs pages (y compris lors de la suppression genre une page
de confirmation, ou une page avec une liste d'enregistrement à supprimer
et une suivante pour la modification, etc.) on veut que le client passe
bien dans l'ordre qu'on a prévu. Conformément à la règle 21, cela ne peut
être garanti, ce qui expose d'ailleurs au cas des liens malicieux dans
les emails etc... dont la simple lecture (voire même pas avec une URL
dans un img src et le visionnage automatique des images côté client, même
pas d'action de l'utilisateur...) provoque une action, en "sautant" des
étapes.

Pour forcer cela, il faut donc trouver un moyen. Tester le Referer ne
marche pas (il peut être absent, et trafiquable aisément de toute façon).
Alors l'idée est de mettre une information dans la page 1, et on ne
donnera pas la page 2 si on (le serveur) ne reçoit pas cette information,
compte-tenu que ce "jeton" (token) est tel qu'il ne peut être deviné par
le client (on va donc éviter le compteur qui s'incrémente... et préférer
un hachage style SHA1 avec une partie fixe - un "mot de passe" - et une
partie variable - comme l'heure).

Donc en résumé, au début, le serveur génère un jeton unique, c'est une
chaine opaque, sans aucun sens, qui sera stocké par le client dans le
formulaire HTML ou l'URL (GET vs POST :-))
Il vaut mieux ne pas utiliser de cookie pour cela, ou lier cette
information à la session en cours (s'il y en a une, et sauf si la session
est gérée sans cookies, ce qui est rare), parce que on veut pouvoir
permettre deux onglets par exemple, sur deux formulaires du même serveur
mais des enregistrements différents (donc des jetons différents) en même
temps ou presque.

Lorsque le client demande la "page 2", le serveur peut donc vérifier s'il
a le bon jeton (car il aura stocké ce jeton au moment de son envoie lors
de la page 1, et ce jeton sera lié à la session en cours - s'il y en a
une - pour éviter que les clients puissent échanger les jetons ; et il
faudra aussi un mécanisme d'expiration pour supprimer automatiquement les
jetons côté serveur au bout d'un certain temps de non utilisation, comme
les identifiants de session en fait), et le client n'aura la page 2 (ou
la réalisation de son action de suppression) que s'il a fourni le bon
jeton.
Sinon on pourra estimer que c'est une attaque, ou que le client ne suit
pas le bon chemin, volontairement ou non (ca peut être un bug de
l'application aussi).

Une fois qu'on commence à passer un jeton unique dans chaque formulaire,
on peut aussi arriver à la conclusion, en phase avec les règles 19 et 20
citées précédemment, qu'on n'a en fait plus trop besoin de passer quoi
que ce soit d'autre dans les formulaires :-)

Ce qui résout un autre problème. Mettons qu'on soit en cours d'édition de
l'enregistrement numéro 42 de la base. Cette information sera quelque
part côté client, dans un champ caché du formulaire ou dans l'URL.
C'est gênant car cela donne des indications au client sur le stockage
côté serveur. Il ne faut pas être très curieux pour se dire que ca
pourrait être rigolo de changer le code et mettre 43 au lieu de 42 et
voir ce qui se passe.
Et là on voit de tout... Soit on peut ainsi récupérer toute la base de
proche en proche, voire tout supprimer (selon ce qu'on fait), etc. parce
que le serveur n'aura jamais imaginé que le client donne autre chose que
ce qu'il est sensé donné (règle 3). Même si la phase d'autorisation est
correctement gérée on pourra avoir un message "accès refusé" qui est
différent de "enregistrement inexistant", parfois dans une attaque, la
simple connaissance que quelque chose existe même sans pouvoir y accéder
à ce stade, est utile.

Donc si on cumule tout ce qui précède on fait cela :
- le client veut modifier/supprimer l'élément 42 (qu'il aura sélectionné
via un lien, une liste déroulante, etc.)
- le serveur génére un jeton unique, il le stocke, le lie à la session,
et lie aussi ce que j'appelle un "form bag" c'est à dire un lot de
données, ici, il stockera id=42 pour
- le serveur renvoit au client donc un code HTML avec tout ce qu'il faut
pour modifier ce qu'il faut, et une seule chose en champ caché : le
numéro du jeton
- quand le client soumet la page, le serveur récupère le jeton, il
vérifie (dans cet ordre) qu'il est valide (syntaxiquement), non expiré,
correctement lié à la session en cours, puis récupère les données
associées (le form bag stocké uniquement côté serveur) et sait ainsi
qu'il faut opérer sur l'enregistrement 42.

Mais ainsi le client ne peut pas essayer de "taper" ailleurs, car il n'a
rien à modifier, juste le jeton. Si on assure que le jeton est opaque,
unique et non généré de manière triviale, alors le client est obligé de
prendre le chemin que le serveur lui donne, tout le reste sera détecté et
ineffectif.

On peut gagner aussi légérement en performances (moins de données à
échanger), et c'est applicable à AJAX.

Dis autrement (parfois c'est utile de dire les choses autrement avec un
autre point de vue), le jeton unique et la valise de données associée est
une forme de sérialisation de l'état de l'application à un instant t
pour un client donné (juste avant son départ sur le réseau vers le
navigateur), pour pouvoir le récupérer à un instant t' (juste après le
retour en provenance du navigateur)

Cela nécessite cependant de voir toute l'application un peu différemment,
et c'est un peu plus lourd à mettre en place, sans être impossible. En
contre-partie on déjoue tout de même un certain nombre de problèmes (en
en introduisant d'autres, par exemple autour de la génération, du
stockage et de la durée de vie des jetons)

Bon je ne sais pas si je suis très clair finalement.
Par contre rien de ce qui précède n'est spécifique à PHP. C'est une bonne
pratique des applications sans état (donc du web), en général.
--
Patrick Mevzek . . . . . . . . . . . . . . Dot and Co
<http://www.dotandco.net/> <http://www.dotandco.com/>
<http://www.dotandco.net/ressources/icann_registrars/prices>
<http://icann-registrars-life.dotandco.net/>
Continuer la lecture sur narkive:
Loading...