Mettre un mot de passe dans un appel d'API REST

31

Supposons que j'ai une API REST qui est également utilisée pour définir / réinitialiser les mots de passe. Supposons également que cela fonctionne sur une connexion HTTPS. Y a-t-il une bonne raison de ne pas mettre ce mot de passe dans le chemin d'appel, disons également que je vais le coder en BASE64?

Un exemple serait de réinitialiser un mot de passe comme celui-ci:

http://www.example.com/user/joe/resetpassword/OLDPASSWD/NEWPASSWD

Je comprends que BASE64 n'est pas un cryptage, mais je veux seulement protéger le mot de passe pour surfer sur l'épaule dans ce cas.

Bart Friederichs
la source
61
Suggérez-vous des effets secondaires sur l'EEG? C'est là une violation du protocole.
Esben Skov Pedersen
27
Ce n'est pas vraiment REST car ce resetpassword/OLDPASSWD/NEWPASSWDn'est pas une ressource. C'est l'invocation d'un processus. Vous n'avez pas besoin de tout ranger dans une URL.
usr
5
@Esben: qui a dit que c'était un GET? Le PO n'a jamais dit cela.
dagnelies
3
Certes, il ne l'a pas fait dans la question. Mais son commentaire sur la réponse de Netch dit "Je suppose que je vais devoir utiliser POST après tout", donc nous pouvons supposer qu'il avait initialement l'intention / demandé GET. Comme le souligne Esben, c'est une mauvaise chose. GET doit être en lecture seule.
Mawg
4
Cet article perspicace expliquant de nombreux pièges des mécanismes de réinitialisation de mot de passe pourrait éventuellement aider à mieux comprendre le cas.
9000

Réponses:

76

Un bon serveur enregistre toutes les requêtes qui lui sont envoyées, y compris les URL (souvent, sans partie variable après '?'), L'adresse IP source, le temps d'exécution ... Voulez-vous vraiment que ce journal (potentiellement lu par un large groupe d'administrateurs) contienne des informations sécurisées en tant que mots de passe? Base64 n'est pas un frein contre eux.

Netch
la source
42
Ce n'est pas la principale raison d'utiliser POST. C'est une raison de sécurité. Mais comme Esben le note déjà dans les commentaires, changer d'état avec un GET est une violation d'un tel service de repos
Pinoniq
2
@BartFriederichs: et l'historique du navigateur se souviendrait de l'URL. Et essayer un tas de mots de passe de manière anonyme en créant une page Web qui contient un lien pour tous les mots de passe que vous souhaitez essayer, et en laissant Googlebot faire les demandes réelles ...
RemcoGerlich
2
"Mais comme Esben le note déjà dans les commentaires, changer d'état avec un GET est une violation d'un tel service de repos" J'ai également remarqué ce commentaire, mais je ne vois pas où quelqu'un dit qu'il s'agissait d'une demande GET. Vous pouvez incorporer des informations dans un URI et toujours les poster, après tout. Ce n'est pas vraiment RESTful , car l'URI ne nomme pas réellement une ressource.
Joshua Taylor
Salut, bonne réponse, mais je ne suis pas d'accord avec le "sans partie variable après"? "" ... il y en a beaucoup qui stockent l'URL complète !!!
dagnelies
2
"changer d'état avec un GET est une violation d'un tel service de repos" - Ne soyons pas trop pris dans REST. Changer d'état avec un GET est une violation de HTTP .
Dan Ellis
69

Ce que vous proposez n'est ni sécurisé ni RESTful.

@Netch a déjà mentionné le problème des journaux, mais il y a aussi un autre problème dans le fait que vous montrez les mots de passe envoyés par HTTP, ce qui rend trivial la capture de mots de passe avec n'importe quel type de renifleur de fil ou d'attaque de l'homme du milieu.

Lorsque vous effectuez une demande GET à l'aide de REST, les différents éléments de l'URL représentent des éléments plus fins. Votre URL se lit comme si vous renvoyiez une partie NEWPASSWD d'un OLDPASSWD faisant partie d'un mot de passe de réinitialisation. Cela n'a aucun sens sémantique. Les GET ne doivent pas être utilisés pour enregistrer des données.

Vous devriez faire quelque chose comme ça:

POST https://www.example.com/user/joe/resetpassword/
{oldpasswd:[data], newpasswd:[data]}

POST parce que vous écrivez des données et https parce que vous ne voulez pas qu'elles soient reniflées.

(C'est vraiment la sécurité de la barre basse. Le minimum absolu que vous devriez faire.)

Gort le robot
la source
2
Cela ne signifierait-il pas le hachage des mots de passe côté client? Est-ce recommandé?
Rowan Freeman
1
Remarque: cela ne permettra pas d'appliquer les exigences de mot de passe (longueur, etc.). Cela peut ne pas être un problème dans votre cas, bien que ce soit une pratique de sécurité fréquente et parfois exigée par certaines entités.
Paul Draper
8
Vous ne créez pas un nouvel enregistrement mais mettez à jour un enregistrement existant (généralement), donc cela devrait être PUT plutôt que POST.
4
Ce n'est pas très reposant. resetpassword n'est pas une ressource et encore moins une sous-ressource. Cependant, /user/joe/passwordc'est un peu mieux mais pas optimal.
whirlwin
12
@CamilStaps Non, vous ne pouvez pas l'utiliser PUT, car il PUTest idempotent. Mais lorsque le mot de passe a été changé de en secretavec supersecretsuccès, la même demande échouera la deuxième fois, c'est donc POSTcorrect ici. Bien sûr, comme l'a dit @whirlwin, cette ressource n'est pas bien nommée.
Residuum
60

Le schéma proposé présente des problèmes dans plusieurs domaines.

Sécurité

Les chemins d'URL sont fréquemment enregistrés; mettre des mots de passe non hachés dans le chemin est une mauvaise pratique.

HTTP

Les informations d'authentification / autorisation doivent apparaître dans l'en-tête d'autorisation. Ou potentiellement, pour les éléments basés sur un navigateur, l'en-tête Cookie.

DU REPOS

Les verbes comme resetpassworddans votre URL sont généralement un signe clair d'un paradigme de transfert d'état non représentatif. Une URL doit représenter une ressource. Qu'est-ce que cela signifie pour GET resetpassword? Ou SUPPRIMER?

API

Ce schéma nécessite de toujours connaître le mot de passe précédent. Vous voudrez probablement autoriser plus de cas; par exemple, le mot de passe est perdu.


Vous pouvez utiliser l' authentification de base ou Digest , qui sont des schémas bien compris.

PUT /user/joe/password HTTP/1.0
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Content-Type: text/plain
Host: www.example.com

NEWPASSWD

Il ne met pas d'informations ultra-sensibles dans le chemin d'accès et suit les conventions HTTP et REST.

Si vous devez autoriser un autre mode d'autorisation (par exemple, un jeton envoyé via un canal vérifié pour réinitialiser le mot de passe), vous pouvez simplement utiliser un en-tête d'autorisation différent sans avoir à changer quoi que ce soit d'autre.

Paul Draper
la source
4

En dehors de la sécurité, le problème est que ce n'est pas une approche très RESTful.

OLDPASSWDet NEWPASSWDne représentent rien dans votre hiérarchie de ressources et pire encore, l'opération n'est pas idempotente.

Vous ne pouvez donc utiliser POSTque votre verbe et vous ne devez pas inclure les deux mots de passe dans votre chemin d'accès aux ressources.

biziclop
la source
1
Vous ne créez pas un nouvel enregistrement mais mettez à jour un enregistrement existant (généralement), donc cela devrait être PUT plutôt que POST.
2
@CamilStaps S'il ne s'agissait que de définir le mot de passe, cela pourrait l'être. Mais comme il est probable que l'ancien mot de passe doit également être vérifié, il rend l'opération non idempotente et PUTest donc disqualifié en tant que verbe. Il pourrait être modifié pour fonctionner, PUTmais dans sa forme actuelle, ce n'est pas le cas.
biziclop
OLDPASSWD est une information d'authentification et ne devrait pas du tout être dans l'URL.
Pas nécessairement, il est courant de demander explicitement l'ancien mot de passe en plus de l'authentification.
biziclop
3

Le problème est d'éviter les mots de passe en texte brut dans vos demandes. Il existe deux options pour répondre aux exigences de service Web reposantes.

1. Hachage côté client

  • Je suppose que vous stockez vos mots de passe comme par exemple le hachage (mot de passe + sel)
  • Vous pouvez hacher le nouveau mot de passe avec un sel du côté client
  • Cela signifie: créer un nouveau sel côté client, créer un hachage, par exemple hachage (newPassword + newSalt)
  • Envoyez le nouveau hachage créé plus le sel à votre service Web reposant
  • Envoyer l'ancien mot de passe également en tant que hachage (oldPassword + oldSalt)

2. Cryptage

  • Créer une ressource "clé unique" (otk) pour un utilisateur comme / otk / john
  • Cette ressource renvoie une clé unique unique aléatoire et sécurisée, par exemple kbDlJbmNmQ0Y0SmRHZC9GaWtRMW0ycVJpYzhMcVNZTWlMUXN6ZWxLdTZESFR et un ID unique, par exemple 95648915125
  • Votre service Web reposant doit stocker cette otk aléatoire pour la prochaine communication sécurisée avec l'ID 95648915125
  • Cryptez votre nouveau et ancien mot de passe avec l'otk, par exemple AES (pour des raisons de sécurité, vous devez utiliser deux otks distincts pour l'ancien et le nouveau mot de passe)
  • Envoyez les mots de passe cryptés à votre ressource de changement de mot de passe avec l'ID 95648915125
  • Une combinaison otk et ID est autorisée à fonctionner une seule fois, vous devez donc supprimer cette combinaison après avoir changé le mot de passe
  • Meilleure option possible: envoyer le mot de passe actuel / ancien par Basic-Auth.

Remarque: HTTPS est requis pour les deux options!

maz258
la source
1
Pourquoi devrais-je double-crypter (votre schéma de cryptage avec OTK et HTTPS) l'échange de mots de passe? Quel est le vecteur d'attaque ici qui n'est pas couvert par HTTPS?
Bart Friederichs
1
Le seul but est d'éviter d'éventuels journaux de serveur. Un corps de requête HTTP (par exemple avec un nouveau mot de passe en texte brut) peut également être enregistré bien que HTTPS soit utilisé. Une autre attaque possible est l'utilisation d'un certificat auto-signé qui est spécialement utilisé à des fins internes.
maz258
2

Quelles sont les fonctionnalités d'une opération de réinitialisation de mot de passe?

  1. Ça change quelque chose.
  2. Il y a une valeur à laquelle il est défini.
  3. Seules certaines personnes sont autorisées à le faire (l'utilisateur, un administrateur ou l'un ou l'autre, peut-être avec des règles différentes sur la façon dont l'un ou l'autre peut le faire).

Le point 1 ici signifie que vous ne pouvez pas utiliser GET, vous devez soit POSTER quelque chose représentant l'opération de changement de mot de passe à un URI représentant une ressource qui gère les changements de mot de passe, soit METTRE quelque chose représentant le nouveau mot de passe à un URI représentant le mot de passe ou représentant quelque chose (par exemple le utilisateur) dont ce mot de passe est une caractéristique.

Généralement, nous POSTONS, notamment parce que cela peut être gênant de METTRE quelque chose que nous ne pourrons pas OBTENIR plus tard et bien sûr, nous ne pouvons pas OBTENIR le mot de passe.

Le point 2 sera donc des données représentant le nouveau mot de passe, dans ce qui est POSTé.

Le point 3 signifie que nous devrons autoriser la demande, ce qui signifie que si l'utilisateur est l'utilisateur actuel, nous aurons besoin que le mot de passe actuel nous soit prouvé (mais pas nécessairement recevoir le mot de passe actuel, si par exemple un défi basé sur le hachage a été utilisé pour prouver sa connaissance sans l'envoyer).

L'URI doit donc être quelque chose comme <http://example.net/changeCurrentUserPassword>ou<http://example.net/users/joe/changePassword> .

Nous pouvons décider que nous voulons recevoir le mot de passe actuel dans les données POST ainsi que dans le mécanisme d'autorisation général utilisé.

Jon Hanna
la source