1 points par GN⁺ 2024-09-13 | 1 commentaires | Partager sur WhatsApp
  • 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 streamUserId des 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.com et 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 condition member d’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, de ChatListQuery, 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.co afin 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
  • 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 u0026 par & pour la consulter sans authentification
  • Les vidéos à lecture unique pouvaient être rejouées par l’attaquant, alors que l’application du destinataire affichait video expired aprè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 edited apparaissait 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 id vulnérable de la requête GraphQL ProfileUpdate, 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>/message ajoutait 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

 
GN⁺ 2024-09-13
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

    • Je ne pense pas que les deux soient équivalents. Les contrôles contre les dépassements de tampon relèvent de détails d’implémentation et de langage très précis, et peuvent survenir n’importe où dans une base de code
      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
    • Si quelqu’un arrive à confondre ça, il ne devrait pas toucher au code côté serveur
    • J’ai déjà surpris un développeur web qui faisait de l’authentification côté frontend avec une simple boîte de dialogue JavaScript. Le mot de passe était dans le JS et comparé bêtement
      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
    • Avec une « API DB automatique » côté backend, je pense que ce genre de chose peut arriver très facilement. Je pense par exemple à certaines configurations GraphQL automatiques
      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
    • Dans les apps mobiles, c’est malheureusement assez courant. C’est le raisonnement du type : « l’utilisateur va vraiment aller regarder l’app mobile de près ? »
      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...

    • On a été nombreux à constater qu’aujourd’hui, faire de mauvaises choses semble rapporter beaucoup plus que faire de bonnes choses
    • Il faudrait que The Guardian voie ça
  • Vu la catégorie de l’app, c’est un échec qui relève de la négligence pénale

    • J’ai été ce sous-traitant au rabais. Les chefs ne se souciaient de rien d’autre que des délais et des bugs visibles par les clients chargés de la revue
      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
    • Ce n’était donc pas une blague. Ce genre de vulnérabilités aurait déjà été embarrassant il y a 10 ans
  • 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

    • J’y réfléchis depuis des années, mais je n’ai pas assez de dopamine disponible pour le construire en parallèle de mon travail principal
      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
    • J’ai le sentiment que les nus devraient rester sous forme analogique. On peut alors contrôler leur diffusion de façon presque totale et absolue
      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

    • On dirait qu’ils n’ont réfléchi ni à la sécurité ni à rien du tout
      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
    • Il faut aussi garder à l’esprit que, par principe, les profils d’une app de dating sont accessibles à tout le monde. On ouvre l’app, les profils apparaissent. Il n’y a pas d’ACL
      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

    • C’est assez simple. Il suffit de traiter chaque resolver qui récupère des données comme un endpoint REST et de le protéger, puis d’avoir une liste blanche de requêtes dans laquelle on ajoute des éléments pendant le build CI
      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
    • Avec GraphQL, il faut définir les droits d’accès attribut par attribut, ou précompiler les requêtes et les mettre dans une liste blanche. Sinon, les données fuient
      https://hasura.io/docs/2.0/security/allow-list/
    • Toute bibliothèque GraphQL tierce digne de ce nom devrait implémenter une forme ou une autre d’ACL. Les plus populaires semblent le faire [1] [2]
      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 get et list à 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...
    • Avec HotChocolate, je n’ai pas eu ce problème. On peut facilement définir des règles d’autorisation sur les entités ou leurs attributs, et c’est géré automatiquement. C’est aussi applicable aux mutations
  • C’était une divulgation étonnamment responsable et attentionnée

    • Est-ce qu’ils ont inclus de vrais profils dans les captures d’écran du menu « Discover profiles » et de la liste des likes ? Si oui, même avec les visages masqués, c’est assez irresponsable
    • Les actes ne correspondent pas aux paroles
  • 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 je l’ai utilisée, elle était aussi vraiment mauvaise. À défaut de fuite mémoire bizarre ou de problème de confidentialité, l’UX était extrêmement mal implémentée
      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é
    • Quand je l’ai utilisée, la communauté était bonne, mais l’appli n’a jamais été correctement écrite
      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