Je suis en train de construire une application web en utilisant Rails. Pour l'instant, j'utilise Devise avec des sessions HTTP, ce qui a été assez facile à mettre en place et fonctionne bien.
L'application consiste en un URL fournissant une application web AJAX. Le reste des URLs disponibles appartiennent à l'API REST. Ainsi, toutes les demandes de données sont effectuées via AJAX.
Maintenant, j'aimerais étendre le tout pour supporter les clients natifs. J'ai lu beaucoup de choses sur l'authentification sans état, l'authentification http basic et digest, les sessions http, les cookies, xsrf, etc... Et maintenant j'ai l'impression que je ne peux pas avoir une application sécurisée, parce qu'il y a toujours un moyen de détourner certaines parties de celle-ci.
1. : Session HTTP vs. jeton d'authentification apatride
Quelle est la différence ? Je ne comprends pas.
-
Session HTTP :
- Le client demande une URL (première demande au serveur).
- Le serveur donne la réponse normale plus une chaîne unique (== ID de session)
- Le client doit envoyer cette chaîne avec chaque requête (ce qui est fait automatiquement en utilisant l'en-tête HTTP).
- Le client se connecte -> Le serveur mémorise que cet ID de session particulier est maintenant connecté.
- Le client visite une page qui nécessite une authentification -> Il n'y a rien de spécial à faire, car l'ID de session sera automatiquement envoyé au serveur via l'en-tête HTTP.
-
jeton d'authentification apatride :
- URL de la demande du client (première demande au serveur)
- Le serveur ne donne que la réponse normale sans toute clé ou jeton ou identifiant
- (rien de spécial ici)
- Le client se connecte -> Le serveur crée un jeton d'authentification et envoie ce jeton au client dans la réponse.
- Le client visite une page qui nécessite une authentification -> Le client doit soumettre le jeton d'authentification.
Pour moi, les deux manières sont assez similaires. Avec Rails, je peux aussi choisir de stocker la session dans la base de données... Devise ferait la même chose avec le jeton d'authentification sans état.
2. : La méthode d'authentification
Pour l'instant, j'utilise POST /users/sign_in
avec {"user":{"email":"e@mail.com","password":"p455w0rd"}}
.
Mais il existe d'autres possibilités, comme l'authentification HTTP de base et l'authentification HTTP digest, mais aussi des solutions comme oAuth (trop importante pour mon objectif).
D'après ce que j'ai lu :
- Concernant sign_in sécurité, il n'y a pas de différence entre l'actuel
POST /users/sign_in
et HTTP basic auth. Les deux utilisent le texte clair. - Pour déconnexion L'authentification de base HTTP présente un inconvénient : la déconnexion n'est possible qu'en fermant la fenêtre du navigateur
- L'authentification par résumé HTTP présente un avantage considérable : elle ne transmet pas du tout le mot de passe (juste un résumé du mot de passe). plus chaîne générée au hasard)
- (Allemand) Wikipedia dit : HTTP digest auth n'est pas supporté par tous les navigateurs. Cette information est peut-être trop ancienne !
Ce dont j'ai besoin :
- les noms d'utilisateur et haché mots de passe (bcrypt) stockés dans une base de données.
- l'utilisateur peut changer son mot de passe et le mot de passe a pas à envoyer en clair. (Le même problème se produit lorsqu'il s'agit de l'inscription de l'utilisateur). Solutions possibles ?
- bien sûr : en utilisant SSL/TLS
- le client demande un
want_to_change_password_salt
et l'utilise pour crypter le mot de passe du côté client. mais ( ?!) de cette façon, j'ai envoyé une partie essentielle du mot de passe haché sur le fil. plus le mot de passe haché. Cela me semble peu sûr !
3. : Jeton CSRF
Comme indiqué ci-dessus, j'ai actuellement un site web AJAX normal qui utilise l'API REST. Il est protégé par XSRF : Le site est livré par rails et a donc intégré le jeton XSRF. Je le lis en utilisant AJAX et le transmet en faisant un POST
. Rails renvoie alors les données demandées ainsi qu'un nouveau jeton XSRF, que j'utilise ensuite pour l'étape suivante POST
.
Je veux maintenant modifier mon application serveur pour qu'elle fonctionne avec des clients natifs. Un client natif ne chargera pas la page HTML et ne pourra donc pas récupérer un jeton CSRF. Les options suivantes me sont donc venues à l'esprit :
- Créer une ressource REST de jeton XSRF. Ainsi, le client (natif) doit demander un jeton XSRF à cette ressource avant de pouvoir effectuer la première opération.
POST
. - Désactiver entièrement la protection XSRF.
Questions :
- Comment fonctionne la protection XSRF (dans Rails) ? Comment le serveur sait-il quel jeton appartient à quel client ? Le seul moyen auquel je pense, ce sont les sessions. Cette hypothèse conduit à :
- Si je désactive la session afin de créer une API REST totalement apatride, la protection XSRF ne fonctionnera plus. N'est-ce pas ?
4. : Jeton d'authentification apatride
Ici, j'ai surtout beaucoup de questions :
- A-t-il les mêmes problèmes de sécurité que les sessions HTTP ? Ce que je veux dire : Voler l'ID de session a le même effet que de voler le jeton d'authentification. N'est-ce pas ?
- L'expiration du jeton d'authentification devrait fonctionner de la même manière que pour les sessions HTTP : Le serveur doit stocker quelque part (base de données ou session) un horodatage et le vérifier.
- sign_out fonctionne aussi de la même manière ?
- Session : Détruire la session sur le serveur
- Auth token : Détruire le jeton sur le serveur
- D'après ce que j'ai lu, il devrait être plus sûr de stocker le jeton d'authentification dans l'en-tête HTTP (tout comme l'ID de session), car les journaux du serveur peuvent contenir
GET
et pourrait donc contenir le jeton. - Devrait-il s'agir d'un simple jeton d'authentification ou serait-il préférable que le client transmette également son ID utilisateur ou même le mot de passe haché ? ICI J'ai lu que le client devait envoyer :
user_id
expiration_date
- un hash (ou HMAC ?) de [
user_id
,expiration_date
,SECRET_KEY
]. OùSECRET_KEY
est essentiellement une chaîne aléatoire générée par le serveur.
Désolé pour cet énorme message, mais la sécurité est essentielle ! Et je ne veux pas faire d'erreurs de conception qui pourraient probablement exposer des données privées.
Merci :)
Voici un peu de nouvelles informations et de nouvelles questions ;-)
:
5. : Clients natifs
En ce qui concerne les clients natifs, il n'y a pas de ( facile ) d'utiliser les sessions :
-
Un client natif est pas de navigateur
-
Il ne pourra donc pas gérer facilement les cookies (et sans cookies, il n'y a pas de gestion de session typique).
Il y a donc 3 choix possibles :
-
Implémenter la gestion des sessions pour les clients natifs. Ce serait comme :
- Connexion
- lire l'en-tête HTTP de la réponse pour obtenir les cookies
- enregistrez localement toutes les données de cookie dont vous avez besoin (en particulier celles qui concernent la session)
- envoyez cet identifiant de session avec chaque demande que vous faites
-
N'utilisez pas du tout les sessions. Du point de vue d'un client natif, c'est à peu près la même chose que 1. :
- Connexion
- Obtenez un jeton d'authentification à partir de l'en-tête HTTP ou du corps de la réponse (c'est votre application, mais c'est à vous de décider).
- sauvegarder ce jeton localement
- envoyez ce jeton avec chaque demande
- L'approche hybride. Cela signifie essentiellement que le serveur doit faire la distinction entre le navigateur et le client natif, puis vérifier l'identifiant de session et les données de session fournis ou (pour les clients natifs) vérifier le jeton d'authentification fourni.
6. : Token CSRF avec authentification apatride (= sans session/ sans cookies)
La protection CSRF protège vos utilisateurs contre les sites web malveillants qui tentent d'effectuer une requête sur votre API au nom de votre utilisateur connecté, mais sans que celui-ci le sache. C'est assez simple lorsqu'on utilise des sessions :
- L'utilisateur se connecte à votre API
- La session est créée
- Le navigateur de vos utilisateurs aura un cookie défini avec cet ID de session.
- Chaque demande de votre utilisateur à votre API est automatiquement authentifiée, car le navigateur envoie tous les cookies (y compris l'identifiant de session) avec chaque demande à votre API.
Le site web attaquant doit donc simplement faire ce qui suit :
- Écrire un HTML personnalisé
<form>
qui pointe vers votre API - Laissez l'utilisateur cliquer en quelque sorte sur le
Submit
bouton
Bien sûr, ce formulaire sera quelque chose comme :
<form action="http://your.api.com/transferMoney" method="post">
<input type="hidden" name="receiver" value="ownerOfTheEvilSite" />
<input type="hidden" name="amount" value="1000.00" />
<input type="submit" value="WIN MONEY!!" />
</form>
Cela conduit à ce qui suit hypothèses :
-
La protection CSRF n'est nécessaire que parce que les navigateurs envoient automatiquement des cookies.
-
Les clients natifs n'ont pas besoin de protection CSRF (bien sûr, votre navigateur ne peut pas accéder aux données d'authentification (jeton, cookie, etc.) de votre application native, et celle-ci n'utilisera pas de navigateur pour communiquer avec l'API).
-
Si la conception de votre API n'utilise pas de cookies pour authentifier l'utilisateur, il n'y a aucune possibilité de faire du CSRF. En effet, l'attaquant doit connaître le jeton d'authentification et l'envoyer explicitement avec la requête malveillante.
Si vous voulez sécuriser votre application, vous pouvez bien sûr utiliser des jetons CSRF avec votre mécanisme d'authentification sans état, mais je suis pratiquement sûr qu'il n'y a pas de gain de sécurité supplémentaire.
7. : Les bonnes méthodes HTTP à choisir
Se connecter / Se connecter et se déconnecter / Se déconnecter :
Jamais utiliser GET
pour (au moins) trois raisons :
-
Dans la plupart des cas, la protection CSRF ne protège que les requêtes POST, PUT, PATCH et DELETE. Un CSRF pourrait donc connecter un utilisateur à son insu en utilisant une requête GET.
-
Les demandes GET doivent jamais modifier l'état de l'application. Mais lorsque l'on utilise des sessions, l'état de l'application change à la connexion et à la déconnexion, car une session est créée ou détruite.
-
Lors de l'utilisation d'une requête GET et de la transmission des informations d'authentification en tant que paramètres d'URL (c.-à-d.
http://your.api.com/login?username=foo&password=bar
) il y a un autre problème : les journaux du serveur ! La plupart des serveurs enregistrent simplement chaque requête HTTP, y compris tous les paramètres URL. Cela signifie que : Si votre serveur est piraté, il n'est pas nécessaire de craquer les hachages de mots de passe de votre base de données, il suffit de jeter un coup d'œil aux fichiers journaux du serveur. En outre, un administrateur malveillant pourrait également lire les informations de connexion de chaque utilisateur. Solutions :- Utilisez POST (ou la méthode de votre choix) et envoyez les informations d'authentification dans le corps de la requête. Ou bien :
- Envoyez les informations d'authentification dans les en-têtes HTTP. Car ces informations n'apparaissent normalement pas dans les fichiers journaux du serveur. Ou bien :
- Jetez un coup d'œil à la configuration du serveur, et dites-lui de supprimer tous les paramètres d'URL nommés "password" (ou de les obscurcir, de sorte que l'URL devienne
login?username=foo&password=***
à l'intérieur des rondins). Mais je suggère d'utiliser simplement le corps de la requête pour ce type d'information avec la méthode POST.
Vous pourriez donc utiliser par exemple :
POST http://your.api.com/authentication
pour la connexion
DELETE http://your.api.com/authentication
pour la déconnexion
8. : Mots de passe et hachage
L'authentification ne fonctionne qu'avec une certaine clé secrète. Et bien sûr, cette clé doit être gardée secrète. Ce qui veut dire :
-
Jamais stocker un mot de passe en clair dans votre base de données. Il existe plusieurs librairies disponibles pour le sécuriser. À mon avis, la meilleure option est
bcrypt
. -
bcrypt : Il a été optimisé pour hacher les mots de passe. Il génère automatiquement un sel et hache le mot de passe plusieurs fois (rounds). De plus, la chaîne de hachage générée contient tout ce qui est nécessaire : Le nombre de rounds, le sel et le hachage. Vous n'avez donc qu'à stocker cette chaîne et il n'est pas nécessaire d'écrire quoi que ce soit à la main.
-
Bien entendu, vous pouvez également utiliser toute autre bibliothèque de hachage fort. Mais pour la plupart d'entre elles, vous devez implémenter vous-même le salage et l'utilisation de plus d'un tour. De plus, elles ne vous donneront pas une seule chaîne comme le fait bcrypt, mais vous devrez vous débrouiller pour stocker les tours, le sel et le hachage et les réassembler ensuite.
-
rondes : Il s'agit simplement de la fréquence à laquelle le mot de passe doit être haché. En utilisant 5000 tours, la fonction de hachage retournera le hash du hash du hash du hash du mot de passe . Il y a essentiellement une seule raison de faire cela : Cela coûte de l'énergie au processeur ! Cela signifie que : Quand quelqu'un essaie de forcer votre hachage, cela prend 5000 fois plus de temps en utilisant 5000 balles. Pour votre application elle-même, cela n'a pas beaucoup d'importance : Si l'utilisateur connaît son mot de passe, il ne reconnaîtra pas si le serveur a pris 0.0004ms ou 2ms pour le valider.
-
bons mots de passe : La meilleure fonction de hachage est inutile, si le mot de passe est trop simple. S'il peut être craqué, en utilisant un dictionnaire, cela n'a pas vraiment d'importance si vous le hachiez avec 5000 tours : Cela prendra peut-être quelques heures de plus, mais que sont quelques heures, si cela peut prendre des mois ou des années ? Assurez-vous cependant que les mots de passe de vos utilisateurs contiennent les recommandations habituelles (minuscules + majuscules + chiffres + caractères spéciaux, etc. pp.).