- Un audit du backend de l’application de rencontre mobile Feeld a révélé 8 vulnérabilités, notamment l’exposition de profils, la lecture et la modification de messages, ainsi que l’accès à des pièces jointes de chat ; toutes sauf la première relèvent de la catégorie Broken Access Control de l’OWASP Top 10
- Les utilisateurs standards ne voient qu’un nombre limité d’informations dans l’interface de l’application, mais en inspectant les réponses via un proxy, il était possible d’obtenir des informations de niveau premium comme l’âge, la distance, la photo de profil et le
streamUserIddes utilisateurs ayant envoyé un « Like » - Plusieurs vulnérabilités s’enchaînaient en récupérant des identifiants comme streamUserId, profileId, messageId ou channelID depuis d’autres réponses API puis en les injectant dans des paramètres de requête, élargissant l’accès aux messages, matchs, profils, Likes et même à l’envoi de messages dans les chats d’autrui
- Les pièces jointes de chat étaient concernées dans tous les cas testés — photos normales, photos temporaires de 5 à 15 secondes, vidéos normales et vidéos à lecture unique — et certaines URL Cloudinary et Stream CDN étaient accessibles sans authentification
- FORTBRIDGE a divulgué ces problèmes à Feeld le 8 mars 2024 ; après plusieurs demandes de report de publication, Feeld a indiqué le 16 août 2024 avoir déployé des changements pour atténuer les points restants, et l’article de blog a été publié le 10 septembre 2024
Étendue des vulnérabilités constatées sur Feeld
- La cible est Feeld, une application de rencontre mobile similaire à Tinder et Bumble, avec des filtres basés sur la distance, l’âge, le genre, les couples et la localisation
- Les utilisateurs premium peuvent aussi rechercher selon le type de kink, des scénarios de threesome/group et les types de relation recherchés
- L’audit de sécurité a mis en évidence 8 vulnérabilités
- Exposition d’informations de profil à des utilisateurs non premium
- Lecture des messages d’autrui
- Accès sans authentification aux photos et vidéos jointes aux chats
- Suppression, restauration et modification des messages d’autrui
- Mise à jour des informations de profil d’autrui
- Réception de « Likes » depuis des profils arbitraires
- Envoi de messages dans les chats d’autrui
- Consultation des matchs d’autrui
- À l’exception de la première, tous les autres problèmes relèvent de la catégorie Broken Access Control de l’OWASP Top 10
Informations de profil exposées à des utilisateurs non premium
- Quand un utilisateur standard consulte, dans le menu Likes de l’application, les personnes qui l’ont aimé, seuls le nom et une photo floutée sont affichés
- En interceptant les requêtes et réponses avec un proxy comme Burp, la réponse contenait pourtant des informations comparables à celles visibles par un utilisateur premium
- âge
- distance
- photo de profil complète
streamUserId
- Les photos de profil étaient stockées sur
res.cloudinary.comet accessibles sans authentification - Le streamUserId obtenu dans la réponse pouvait ensuite être utilisé pour la vulnérabilité permettant de lire les messages d’autrui
Problèmes de contrôle d’accès sur les messages et les matchs
- Pour lire les messages d’une autre personne, il fallait connaître le streamUserId de la victime, une valeur exposée dans plusieurs requêtes API
- Le scénario d’exemple consistait à récupérer le streamUserId de la cible dans la réponse à la requête GraphQL
DiscoverProfiles, puis à injecter cette valeur dans la conditionmemberd’une requête sur les canaux de chat - En recherchant
"text"dans la réponse, il était possible de voir le nombre de messages échangés par la victime ainsi que leur contenu - La même méthode permettait aussi d’obtenir le messageId associé à chaque message, ensuite utilisé pour supprimer, restaurer ou modifier ces messages
- En remplaçant le paramètre
profileId, vulnérable, deChatListQuery, il était possible de consulter les matchs d’autres utilisateurs- Les informations visibles comprenaient imaginaryName, l’âge, les photos, le genre, la sexuality, le status et la date de naissance
Accès non authentifié aux pièces jointes de chat
- Les pièces jointes partagées dans les chats se répartissaient entre photos et vidéos
- les photos pouvaient être soit des photos normales consultables librement, soit des photos temporaires limitées à 5 à 15 secondes
- les vidéos pouvaient être soit des vidéos normales consultables librement, soit des vidéos à lecture unique
- Les photos normales étaient envoyées depuis l’application Feeld vers
api.cloudinary.com, qui renvoyait ensuite un photo_id- la photo était ensuite copiée vers
feeld.coafin d’être servie aux utilisateurs authentifiés - des chemins de la forme
cdn/chat-attachment/<receiver_profileId>/<photo_id>ou<sender_profileId>/<photo_id>étaient utilisés - la partie profileId du chemin pouvait être raccourcie à une chaîne arbitraire d’au moins un caractère, et la photo était tout de même renvoyée à un utilisateur authentifié
- un chemin préfixé par
/v1/renvoyait l’URL de la photo originale stockée sur Cloudinary, et cette URL était accessible sans authentification
- la photo était ensuite copiée vers
- Les photos temporaires utilisaient à l’envoi un paramètre supplémentaire tel que
visibilityMilliseconds:15000- l’endpoint destiné au destinataire supprimait la photo 5 à 15 secondes après l’accès, la rendant ensuite inaccessible
- un endpoint utilisant le profileId de l’uploadeur continuait cependant à renvoyer la photo aux utilisateurs authentifiés même après 5 à 15 secondes
- le chemin
/v1/renvoyait là aussi une URL Cloudinary accessible sans authentification
- Pour les vidéos, qu’il s’agisse de vidéos normales ou à lecture unique, l’URL était incluse dans le message de chat
- les vidéos normales étaient envoyées sur
us-east.stream-io-cdn.com - les vidéos à lecture unique suivaient un flux d’upload côté
chat.stream-io-api.com - un attaquant pouvait récupérer l’URL via la vulnérabilité précédente de lecture des messages puis remplacer
u0026par&pour la consulter sans authentification
- les vidéos normales étaient envoyées sur
- Les vidéos à lecture unique pouvaient être rejouées par l’attaquant, alors que l’application du destinataire affichait
video expiredaprès une première lecture
Manipulation des messages, modification de profils et falsification de Likes
- Sur l’endpoint
chat.stream-io-api.com/messages/<messageId>, les méthodes DELETE et PUT permettaient d’agir sur les messages d’autrui - Un message supprimé apparaissait dans le chat sous la forme
This message was deleted, mais si l’attaquant rejouait la même requête DELETE, le message original lui était renvoyé - L’attaquant pouvait modifier un message à partir de son messageId même sans faire partie de la conversation
- la victime voyait alors le message modifié lorsqu’elle ouvrait la notification
- un marqueur
editedapparaissait sous le message, mais sans indiquer qui l’avait modifié - les noms de compte n’étaient pas uniques et pouvaient être modifiés
- En remplaçant l’ID victime dans le paramètre
idvulnérable de la requête GraphQLProfileUpdate, il était possible de mettre à jour des informations de profil comme le nom, la sexuality, l’âge ou la bio - Dans la requête GraphQL
ProfileLike, il était possible, en étant connecté comme profile#1, de faire comme si profile#2 avait envoyé un « Like » à profile#3- dans l’exemple, un Like était envoyé depuis un profil arbitraire vers son propre profil, puis apparaissait dans la liste Likes d’un compte premium
Envoyer des messages dans les chats d’autrui
- Un attaquant pouvait envoyer des messages dans le chat d’autres personnes sans en être participant
- La valeur nécessaire était le channelID, obtenu via la vulnérabilité précédente de lecture des messages
- Une requête POST vers
channels/messaging/<channelID>/messageajoutait alors un message à ce canal - La victime recevait une notification et pouvait consulter le message
- Le système indiquait dans la notification que le message provenait du nom de l’attaquant, mais celui-ci pouvait changer le nom de son profil et les noms n’étaient pas uniques
Chronologie de la divulgation
- Le 8 mars 2024, FORTBRIDGE a divulgué à Feeld l’ensemble des problèmes
- Le même jour, Feeld a demandé les informations des comptes utilisés pour les tests
- Le 2 avril 2024, FORTBRIDGE a demandé une mise à jour, et Feeld a répondu qu’une enquête était en cours tout en demandant de reporter la publication
- Le 28 mai 2024, Feeld a déployé plusieurs correctifs et a demandé jusqu’à deux semaines de délai pour vérifier si les problèmes signalés étaient résolus
- Le 8 juin 2024, trois mois s’étaient écoulés depuis l’e-mail initial de divulgation
- Le 15 juillet 2024, Feeld a indiqué que certains problèmes nécessitaient des correctifs plus complexes
- Le 4 août 2024, Feeld a demandé que la publication soit suspendue jusqu’à la résolution des points restants
- Le 16 août 2024, Feeld a répondu avoir mis en œuvre des changements pour atténuer les derniers points identifiés
- Le 8 septembre 2024, six mois s’étaient écoulés depuis la divulgation initiale
- Le 10 septembre 2024, l’article de blog a été publié
- En août 2025, cette recherche a été présentée à DEF CON 33
1 commentaires
Avis de Hacker News
On dirait que les contrôles d’autorisation n’ont été implémentés que côté frontend, et pas seulement sur un ou deux endpoints, mais presque partout
Conceptuellement, c’est une erreur facile à éviter, mais j’en ai vu des similaires trop souvent pour avoir envie de l’admettre
La solution « vérifier toutes les autorisations côté backend » ressemble un peu, pour les dépassements de tampon, à « ajouter des contrôles de limites partout ». Toute la communauté sait ce qu’il faut faire, mais faire en sorte que tout le monde l’applique systématiquement n’est pas simple
Les contrôles d’autorisation, eux, se font à des frontières spécifiques et relèvent de la manière dont l’application est conçue. Chaque fois que j’ai pu influencer la façon de développer un projet, j’ai insisté pour séparer clairement le développement des API backend du code client frontend. D’expérience, cela rend ce genre de problèmes beaucoup plus facile à éviter et à tester, et cela donne aussi une API développeur « gratuitement ». Honnêtement, c’est la principale raison pour laquelle je préfère cette approche
Je l’ai découvert parce que le propriétaire du compte lamp m’a contacté en disant que toutes ses données avaient soudainement disparu. Les logs montraient que Google Bot avait cliqué sur tous les liens « Delete » de l’interface d’administration interne. C’était possible parce que JavaScript est opt-in. J’ai appelé le développeur pour lui expliquer ce qu’il avait fait, et ce jour-là j’ai perdu beaucoup de confiance dans les gens du web
Je le signale chaque fois que j’en vois, mais je suis assez inquiet de voir à quel point, parfois, on réfléchit peu au périmètre de l’API client
J’aurais envie de blâmer les juniors, le no-code ou le code généré par IA, mais comme je suis aussi paresseux qu’eux, je me contente de secouer la tête et de passer à autre chose
C’est une très bonne raison de ne pas fournir de données personnelles exactes. Par exemple une date de naissance
Les apps de dating, en particulier, semblent demander ce genre d’information, mais il vaut mieux éviter. Mieux vaut indiquer une valeur à environ un an près de sa vraie date de naissance
Cette app de dating n’est pas très connue, mais elle s’adresse aux personnes ayant d’autres préférences, comme le BDSM ou le sexe en groupe, ainsi qu’aux utilisateurs queer. Dans de nombreuses régions du monde, ces informations sont évidemment extrêmement sensibles
Ils ont beaucoup été dans la presse cette semaine parce qu’ils gagnent bien leur vie
https://www.theguardian.com/technology/article/2024/sep/08/t...
Vu la catégorie de l’app, c’est un échec qui relève de la négligence pénale
La menace de peines de prison aux États-Unis et dans l’UE, l’assurance liée aux données et le coût de cette assurance seront probablement les seuls garde-fous. Si les photos ne sont pas du genre qu’on pourrait poster sur LinkedIn, la prime devrait être astronomique
Bien sûr, les incitations ne doivent pas encourager la dissimulation
Le secteur du dating en ligne est un désastre. Il n’y a que 2 ou 3 entreprises avec un service qu’on peut qualifier d’utile, et elles sont malveillantes, incompétentes, ou les deux
Il nous faut peut-être désormais quelque chose comme un service de dating open source fédéré. Quelque chose qui, au minimum, ne vende pas les données, ne fasse pas fuiter des photos nues, et ne vous expose pas à être battu, violé ou assassiné. Plus facile à dire qu’à faire, évidemment
ActivityPub a même la structure nécessaire pour rendre cela possible via la publication d’enregistrements Person. Si l’on priorise en particulier les besoins des personnes non monogames, non hétérosexuelles et non conformes au genre, il y a un énorme espace d’innovation
Cela dit, les apps de dating sont un domaine vraiment difficile à pénétrer. Pour être utile, il faut une masse critique d’utilisateurs dans une zone donnée, et dès qu’on monétise, l’app devient inévitablement moins utile. Ce n’est pas pour rien qu’okcupid s’est dégradé après avoir cessé d’avoir un caractère non lucratif
Et puis il y a aussi le problème de la modération
Si quelqu’un veut transformer une forme analogique en copie numérique, c’est son droit, mais il faut savoir qu’aucun système n’est ni ne sera jamais assez sûr pour empêcher les fuites et la diffusion
Les jeunes, en particulier, ne prennent pas en compte les conséquences et la honte qui peuvent, et risquent fortement, survenir à long terme. Proposer ce genre de fonctionnalité revient simplement à inviter des conséquences négatives
C’est vraiment horrible. Il est clair qu’ils n’ont absolument pas réfléchi à la sécurité
Je suis développeur de jeux, et nous faisons plus d’efforts pour garder nos jeux équitables que cette entreprise n’en fait pour protéger ses utilisateurs. Ils devraient se faire démolir en justice
Avant même de comprendre que l’app était truffée de bugs, j’avais été très surpris de voir que la section des centres d’intérêt ne fournissait aucun contexte. Par exemple, presque tout le monde indiquait Domination ou Submission comme centres d’intérêt, mais sans aucun contexte sur le rôle recherché. Ne pas comprendre à quel point c’est fondamentalement raté dans cette scène signifie qu’ils ne comprennent globalement rien
Les messages et les photos privées sont un autre sujet
Pour le dire de façon provocatrice, c’est un problème de GraphQL
GraphQL permet au frontend d’interroger les données. C’est élégant, mais du point de vue du backend, c’est très opaque, et c’est généralement implémenté avec des bibliothèques tierces qui ne connaissent rien au contrôle d’accès
Si l’on ne met pas en œuvre le contrôle d’accès directement dans la base de données, il est très difficile, dans le code backend, de décortiquer les requêtes GraphQL pour savoir quels enregistrements doivent être renvoyés ou restreints. Le faire dans la base de données n’est pas le pire choix, et c’est clairement mieux que de le faire côté frontend
Pour implémenter correctement le contrôle d’accès côté backend, il faut comprendre la requête, connaître le schéma de la base de données et créer des modèles, classes, fonctions, etc. capables de décider si « user_id vaut XXX, cet utilisateur peut-il voir cette image dans ce contexte ou non ? ». Avec GraphQL, c’est beaucoup plus facile à implémenter côté frontend, donc c’est manifestement ce qu’ils ont fait
Je ne dis pas non plus que l’implémentation de GraphQL était bonne, ni que le problème vient entièrement de GraphQL. Je veux dire que GraphQL, en cherchant à éviter au backend d’avoir à comprendre les requêtes, rend ce genre de situation de sécurité complexe plus difficile, et donc rend cette erreur plus facile à commettre
[0] Par exemple, une image donnée peut être accessible publiquement sur un profil utilisateur, mais visible seulement par les personnes avec qui il y a un match, ou seulement dans le contexte d’un chat (hors chat de groupe), ou inaccessible à tout moment pour les utilisateurs bloqués. Ce seul cas peut déjà créer tout un tas de cas limites complexes
Pas besoin de manipuler l’AST ni de comprendre le contexte du reste de la requête. Dans le resolver qui récupère les photos, il suffit de répondre à « l’utilisateur ABC peut-il voir les photos de l’utilisateur XYZ ? ». Si ce n’est pas efficace, on peut précharger certaines données ou utiliser dataloader
En revanche, si vous utilisez une bibliothèque magique qui transforme GraphQL en SQL, c’est une autre histoire
https://hasura.io/docs/2.0/security/allow-list/
Une idée simple consiste à implémenter l’autorisation dans le modèle de données. Il s’agit de faire en sorte que GraphQL délègue
getetlistà un modèle de ressources capable d’implémenter l’autorisation selon le contexte de la requête[1] https://www.apollographql.com/docs/apollo-server/security/au...
[2] https://docs.graphene-python.org/projects/django/en/latest/a...
C’était une divulgation étonnamment responsable et attentionnée
Ce n’est pas très surprenant. Je l’utilise, mais je dirais qu’elle est conçue de façon aussi incompétente que mon appli bancaire. Peut-être même pire, et elle ne fonctionne presque jamais correctement
Je ne sais pas comment ils ont pu construire ça comme ça
Quand on voit cette appli et Fetlife, on voit bien que ces communautés ont un vrai problème : elles restent sur la première appli apparue, quelle que soit sa qualité
Puis, il y a quelque temps, ils ont fait un jour de bascule en déployant en même temps une nouvelle appli et de nouveaux serveurs pour tout le monde, et la plupart des gens ne pouvaient même pas se connecter. Ceux qui réussissaient à se connecter perdaient leurs avantages premium s’ils étaient clients payants, et rencontraient des problèmes comme la disparition des likes et des chats. Je n’ai finalement jamais réussi à me connecter et j’ai abandonné l’appli à ce moment-là
Honnêtement, je suis surpris que les chercheurs aient attendu aussi longtemps avant de publier
Si l’on donne six mois à une startup médiocre pour corriger une énorme faille de confidentialité de ce genre, elle continuera à abuser du privilège même de pouvoir collecter ce type d’informations. Je pense qu’il faudrait leur donner deux mois puis publier. Elles doivent apprendre qu’on ne joue pas aux dés avec les informations privées des gens
Exemple : https://news.ycombinator.com/item?id=41517747