1 pontos por GN⁺ 2024-09-13 | 1 comentários | Compartilhar no WhatsApp
  • Uma revisão do backend do aplicativo de namoro móvel Feeld encontrou 8 vulnerabilidades, incluindo exposição de perfis, leitura e modificação de mensagens e acesso a anexos de chat; todas, exceto a primeira, se enquadram em Broken Access Control do OWASP Top 10
  • Usuários comuns veem apenas informações limitadas na tela do app, mas ao inspecionar as respostas com um proxy era possível obter informações de nível premium, como idade, distância, foto de perfil e streamUserId de usuários que enviaram “like”
  • Várias vulnerabilidades eram encadeadas obtendo identificadores como streamUserId, profileId, messageId e channelID em outras respostas de API e inserindo esses valores como parâmetros de requisição, ampliando o escopo de acesso até mensagens, matches, perfis, likes e envio de mensagens em chats de terceiros
  • Os problemas com anexos de chat foram confirmados em fotos comuns, fotos com limite de 5 a 15 segundos, vídeos comuns e vídeos de reprodução única; algumas URLs de Cloudinary e Stream CDN eram acessíveis sem autenticação
  • A FORTBRIDGE divulgou os problemas à Feeld em 8 de março de 2024; após vários pedidos de adiamento da publicação, a Feeld respondeu em 16 de agosto de 2024 que havia implementado mudanças para mitigar os itens restantes, e o post do blog foi publicado em 10 de setembro de 2024

Escopo das vulnerabilidades encontradas no Feeld

  • O alvo é o aplicativo de namoro móvel Feeld, semelhante a Tinder e Bumble, com filtros por distância, idade, gênero, casal e localização
  • Usuários premium também podem buscar por tipo de kink, cenários de menáge à trois/grupo e tipo de relacionamento de interesse
  • A revisão de segurança identificou 8 vulnerabilidades
    • Exposição de informações de perfil para usuários não premium
    • Leitura de mensagens de terceiros
    • Acesso sem autenticação a anexos de foto e vídeo em chats
    • Exclusão, restauração e modificação de mensagens de terceiros
    • Atualização de informações de perfil de terceiros
    • Recebimento de “Like” em perfis arbitrários
    • Envio de mensagens em chats de terceiros
    • Visualização dos matches de terceiros
  • Com exceção da primeira, os demais problemas se enquadram na categoria Broken Access Control do OWASP Top 10

Informações de perfil expostas a usuários não premium

  • Quando um usuário comum vê, no menu Likes, quem curtiu seu perfil, o app mostra apenas algo como o nome e uma foto borrada
  • Ao interceptar requisições e respostas com uma ferramenta de proxy como Burp, a resposta continha informações no mesmo nível visto por usuários premium
    • Idade
    • Distância
    • Foto de perfil completa
    • streamUserId
  • As fotos de perfil ficam armazenadas em res.cloudinary.com e podiam ser acessadas sem autenticação
  • O streamUserId obtido na resposta podia então ser usado na vulnerabilidade de leitura de mensagens de terceiros

Problemas de controle de acesso em mensagens e matches

  • Para ler mensagens de terceiros, era necessário o streamUserId da vítima, e esse valor era exposto em várias requisições de API
  • Um fluxo de exemplo consistia em obter o streamUserId do usuário-alvo na resposta da requisição GraphQL DiscoverProfiles e depois inserir esse valor na condição member da requisição de canal de chat
  • Ao buscar por "text" na resposta, era possível verificar a quantidade e o conteúdo das mensagens trocadas pela vítima
  • Pela mesma abordagem, também era possível obter o messageId associado a cada mensagem, usado para excluir, restaurar e modificar mensagens
  • Ao alterar o parâmetro vulnerável profileId de ChatListQuery, era possível visualizar os matches de outros usuários
    • Entre as informações visíveis estavam imaginaryName, idade, fotos, gênero, sexuality, status e data de nascimento

Acesso sem autenticação a anexos de chat

  • Os anexos compartilhados em chats se dividem em fotos e vídeos
    • As fotos podem ser fotos comuns visualizáveis ou fotos com limite de 5 a 15 segundos
    • Os vídeos podem ser vídeos comuns visualizáveis ou vídeos de reprodução única
  • Fotos comuns são enviadas do app Feeld para api.cloudinary.com, e a resposta retorna um photo_id
    • Depois, a foto é copiada para feeld.co e entregue a usuários autenticados
    • São usados caminhos no formato cdn/chat-attachment/<receiver_profileId>/<photo_id> ou <sender_profileId>/<photo_id>
    • Mesmo reduzindo a parte profileId do caminho para uma string arbitrária de pelo menos 1 caractere, a foto ainda era retornada para usuários autenticados
    • Um caminho com /v1/ na frente retornava a URL da foto original armazenada no Cloudinary, e essa URL era acessível sem autenticação
  • Fotos com tempo limitado usam parâmetros adicionais no upload, como visibilityMilliseconds:15000
    • O endpoint do destinatário apaga a foto após 5 a 15 segundos do acesso, deixando-a indisponível
    • O endpoint usando o profileId de quem enviou continuava retornando a foto para usuários autenticados mesmo após 5 a 15 segundos
    • O caminho /v1/ retornava uma URL do Cloudinary, e essa URL era acessível sem autenticação
  • Tanto vídeos comuns quanto vídeos de reprodução única incluíam a URL na mensagem de chat
    • Vídeos comuns eram enviados para us-east.stream-io-cdn.com
    • Vídeos de reprodução única usavam um fluxo de upload via chat.stream-io-api.com
    • Ao obter a URL pela vulnerabilidade anterior de leitura de mensagens e trocar u0026 por &, um invasor podia assistir sem autenticação
  • Vídeos de reprodução única podiam ser reproduzidos novamente pelo invasor, enquanto no app do destinatário apareciam como video expired após uma visualização

Manipulação de mensagens, alteração de perfil e falsificação de likes

  • No endpoint chat.stream-io-api.com/messages/<messageId>, era possível operar sobre mensagens de terceiros com os métodos DELETE e PUT
  • Mensagens excluídas apareciam no chat como This message was deleted, mas, se o invasor chamasse a mesma requisição DELETE, recebia de volta a mensagem original
  • O invasor podia modificar mensagens usando o messageId mesmo sem participar do chat
    • Quando a vítima tocava na notificação, via a mensagem alterada
    • Havia um indicador edited abaixo da mensagem, mas sem mostrar quem a editou
    • O nome da conta não é único e pode ser alterado
  • Ao trocar o parâmetro vulnerável id da requisição GraphQL ProfileUpdate pelo ID da vítima, era possível atualizar informações de perfil como nome, sexuality, idade e bio
  • Na requisição GraphQL ProfileLike, era possível, estando logado como profile#1, fazer parecer que profile#2 enviou um “Like” para profile#3
    • No exemplo, após enviar um Like ao próprio perfil a partir de um perfil arbitrário, esse Like apareceu na lista Likes de uma conta premium

Envio de mensagens em chats de terceiros

  • O invasor podia enviar mensagens em chats de outras pessoas mesmo sem participar deles
  • O valor necessário era o channelID obtido na vulnerabilidade anterior de leitura de mensagens
  • Ao enviar uma requisição POST para channels/messaging/<channelID>/message, a mensagem era adicionada ao canal
  • A vítima recebia uma notificação e podia verificar a mensagem
  • O sistema mostrava a notificação como vinda do nome do invasor, mas o invasor podia alterar o nome do perfil, e nomes não são únicos

Linha do tempo da divulgação

  • Em 8 de março de 2024, a FORTBRIDGE divulgou todos os problemas à Feeld
  • No mesmo dia, a Feeld pediu as informações das contas usadas nos testes
  • Em 2 de abril de 2024, a FORTBRIDGE pediu uma atualização, e a Feeld solicitou adiar a publicação dizendo que estava investigando
  • Em 28 de maio de 2024, a Feeld informou ter implantado várias correções e pediu até 2 semanas de atraso para verificar se os problemas encontrados haviam sido resolvidos
  • Em 8 de junho de 2024, haviam se passado 3 meses desde o email inicial de divulgação
  • Em 15 de julho de 2024, a Feeld respondeu que alguns problemas exigiam correções mais complexas
  • Em 4 de agosto de 2024, a Feeld pediu que a publicação fosse adiada até a resolução dos itens restantes
  • Em 16 de agosto de 2024, a Feeld respondeu que havia implementado mudanças para mitigar os achados restantes
  • Em 8 de setembro de 2024, haviam se passado 6 meses desde a divulgação inicial
  • Em 10 de setembro de 2024, o blog foi publicado
  • Em agosto de 2025, a pesquisa foi apresentada na DEF CON 33

1 comentários

 
GN⁺ 2024-09-13
Comentários do Hacker News
  • Parece que a verificação de permissões foi implementada apenas no frontend, e não só em um ou dois endpoints, mas praticamente em tudo
    Conceitualmente é um erro fácil de evitar, mas já vi erros parecidos com uma frequência que dá até vergonha de admitir
    A solução “verifique todas as permissões no backend” me parece semelhante ao “coloque verificações de limites em todos os lugares” para buffer overflow. A comunidade como um todo sabe o que deve fazer, mas não é fácil fazer todo mundo aplicar isso de forma consistente

    • Acho que as duas coisas não são iguais. A verificação de buffer overflow é um detalhe muito específico de implementação e de linguagem, e pode acontecer em qualquer ponto da base de código
      Já a verificação de permissões acontece em uma fronteira específica e tem a ver com a forma como a aplicação é projetada. Sempre que pude influenciar o modo de desenvolvimento de um projeto, insisti em separar claramente o desenvolvimento da API de backend do código do cliente frontend. Pela minha experiência, isso torna muito mais fácil evitar e testar esse tipo de problema, e ainda cria uma API para desenvolvedores “de graça”. Sinceramente, esse é o principal motivo pelo qual prefiro essa abordagem
    • Se alguém confunde isso a esse ponto, não deveria mexer em código do lado do servidor
    • Uma vez peguei um desenvolvedor web fazendo autenticação no frontend com uma caixa de diálogo JavaScript comum. Ele colocava a senha no JS e fazia uma comparação simples
      Descobri porque o dono da conta lamp entrou em contato dizendo que todos os seus dados tinham sumido de repente. Olhando os logs, o Google Bot tinha clicado em todos os links “Delete” da tela interna de administração. Isso foi possível porque JavaScript é opt-in. Liguei para o desenvolvedor e expliquei o que ele tinha feito; naquele dia perdi muita confiança no pessoal de web
    • Acho que isso pode acontecer com muita facilidade quando se usa uma “API automática de banco de dados” no backend. Alguns setups automáticos de GraphQL, por exemplo, vêm à mente
      Sempre marco isso quando vejo, mas é bem preocupante como às vezes se pensa pouco no escopo da API do cliente
    • Infelizmente, é algo bastante comum em apps móveis. É aquela lógica de “o usuário vai mesmo ficar fuçando no app móvel?”
      Dá vontade de culpar juniores, no-code e código feito por IA, mas eu sou tão preguiçoso quanto eles, então só balanço a cabeça e sigo em frente
  • É um ótimo motivo para não colocar informações pessoais exatas. Por exemplo, coisas como data de nascimento
    Apps de namoro, em especial, parecem exigir esse tipo de informação, mas é melhor não fornecer. É melhor colocar uma data cerca de um ano para mais ou para menos em relação ao aniversário real
    Este app de namoro não é muito conhecido, mas é voltado a pessoas com preferências diferentes, como BDSM e sexo em grupo, e a usuários queer. Em muitas regiões do mundo, esse tipo de informação é, nem é preciso dizer, extremamente sensível

  • Nesta semana apareceu bastante na imprensa porque está ganhando muito dinheiro
    https://www.theguardian.com/technology/article/2024/sep/08/t...

    • Muita gente tem visto que hoje em dia parece muito mais lucrativo criar algo ruim do que criar algo bom
    • O The Guardian precisa ver isso
  • Considerando a categoria do app, é uma falha no nível de negligência criminal

    • Eu era aquele contratado barato. Os chefes não se importavam com nada além de prazo e bugs visíveis para os revisores do cliente
      Acho que ameaça de prisão nos EUA e na UE, seguro relacionado a dados e o custo desse seguro serão os únicos freios. Se as fotos não forem do tipo que você colocaria no LinkedIn, o preço deveria ser absurdo
      Claro que os incentivos não devem encorajar acobertamento
    • Não era brincadeira. Essas vulnerabilidades já teriam sido vergonhosas 10 anos atrás
  • O setor de namoro online é uma bagunça. Só existem 2 ou 3 empresas com serviços que dá para chamar de úteis, e elas são malignas, incompetentes ou as duas coisas
    Talvez agora precisemos de algo como um serviço de namoro federado open source. No mínimo, algo que não venda dados, não vaze fotos nuas e não faça as pessoas serem espancadas, estupradas ou assassinadas. Não que isso seja fácil

    • Tenho pensado em algo assim há anos, mas não tenho dopamina sobrando para construir isso em paralelo ao trabalho principal
      O ActivityPub também já tem uma estrutura que possibilitaria isso por meio da publicação de registros Person. Há um espaço enorme para inovação, especialmente se as necessidades de não monogamia, não heterossexualidade e não conformidade de gênero forem priorizadas
      Mas apps de namoro são uma área realmente difícil de entrar. Para serem úteis, precisam de uma massa acumulada de usuários em uma região específica, e, quando se monetiza, o app inevitavelmente fica menos útil. Há um motivo para o okcupid ter piorado depois que deixou de ter um caráter sem fins lucrativos
      E ainda há o problema de moderação
    • Sinto que nudes deveriam permanecer em forma analógica. Assim, você tem controle quase total e absoluto sobre a distribuição
      Se alguém quiser transformar a forma analógica em uma cópia digital, é direito da pessoa, mas ela precisa saber que nenhum sistema é, nem poderá ser, seguro o suficiente para impedir vazamentos e distribuição
      Especialmente pessoas jovens não levam em conta as consequências e a vergonha que podem surgir, e provavelmente surgirão, no longo prazo. Oferecer esse tipo de recurso é apenas convidar resultados negativos
  • É realmente horrível. Fica claro que não houve nenhuma reflexão sobre segurança
    Sou desenvolvedor de jogos, e nós nos esforçamos mais para manter os jogos justos do que essa empresa para manter os usuários seguros. Eles deveriam ser destruídos por processos

    • Parece que não pensaram não só em segurança, mas em nada
      Mesmo antes de perceber que o app era cheio de bugs, fiquei muito surpreso ao ver que a seção de interesses não dava contexto nenhum. Por exemplo, quase todo mundo tinha Domination ou Submission como interesse, mas não havia nenhum contexto sobre qual papel a pessoa queria. Não saber o quanto isso é fundamentalmente errado nessa cena significa não saber nada no geral
    • É preciso considerar que perfis em apps de namoro, por princípio, são acessíveis a todos. Você abre o app e os perfis aparecem. Não há nada como ACL
      Mensagens e fotos privadas são outra questão
  • Para ser provocador, isso é um problema do GraphQL
    O GraphQL permite que o frontend consulte dados. É legal, mas, do ponto de vista do backend, isso é muito opaco e geralmente é implementado com bibliotecas de terceiros que não sabem nada sobre controle de acesso
    Se você não vai implementar o controle de acesso no próprio banco de dados, fica muito difícil, no código do backend, destrinchar consultas GraphQL e descobrir quais registros devem ser retornados ou restringidos. Fazer isso no banco de dados não é a pior opção e certamente é melhor do que fazer no frontend
    Para implementar um controle de acesso adequado no backend, é preciso entender a consulta, conhecer o esquema do banco de dados e criar modelos, classes, funções etc. capazes de decidir se “o user_id XXX pode ou não ver esta imagem neste contexto”. No GraphQL, é muito mais fácil implementar isso no frontend, então é evidente que foi o que fizeram
    Não estou dizendo que a implementação do GraphQL tenha sido boa, nem que a culpa seja inteiramente do GraphQL. Quero dizer que o GraphQL tenta eliminar a necessidade de o backend entender a consulta, o que torna situações de segurança complexas como essa mais difíceis e, portanto, torna esse erro mais fácil de cometer
    [0] Por exemplo, uma determinada imagem pode ter acesso público no perfil do usuário, mas ser visível apenas para pessoas com match, ou apenas no contexto de um chat (excluindo chats em grupo), ou nunca ser acessível a um usuário bloqueado. Só esse caso já pode criar um monte de casos de borda complexos

    • É bem fácil. Trate cada resolver que busca dados como um endpoint REST e proteja-o, e mantenha uma lista de permissões de consultas à qual os itens são adicionados durante o build de CI
      Não é preciso mexer na AST nem entender o contexto do restante da consulta. No resolver que busca fotos, basta responder: “o usuário ABC pode ver as fotos do usuário XYZ?”. Se for ineficiente, dá para pré-carregar alguns dados ou usar um dataloader
      Mas, se você estiver usando alguma biblioteca mágica que transforma GraphQL em SQL, aí a história muda
    • No GraphQL, é preciso definir permissões de acesso por atributo ou pré-compilar as consultas e colocá-las em uma lista de permissões. Qualquer outra abordagem vaza dados
      https://hasura.io/docs/2.0/security/allow-list/
    • Qualquer biblioteca GraphQL de terceiros minimamente utilizável deveria implementar algum tipo de ACL. Parece que as mais populares fazem isso [1] [2]
      Uma ideia simples é implementar a autorização no modelo de dados. Ou seja, fazer o GraphQL delegar get e list a um modelo de recursos que possa implementar autorização conforme o contexto da requisição
      [1] https://www.apollographql.com/docs/apollo-server/security/au...
      [2] https://docs.graphene-python.org/projects/django/en/latest/a...
    • Ao usar HotChocolate, não tive esse problema. É fácil aplicar regras de autorização a entidades ou a propriedades de entidades, e isso é tratado automaticamente. Também pode ser aplicado a mutations
  • Foi uma divulgação surpreendentemente responsável e cuidadosa

    • Eles incluíram perfis reais nas capturas de tela do menu “Discover profiles” e da lista de likes? Se sim, mesmo com os rostos borrados, isso é bem irresponsável
    • As ações não condizem com as palavras
  • Não é uma grande surpresa. Eu uso, mas diria que foi feito com tanta incompetência quanto o app do meu banco. Talvez até pior; quase nada funciona direito
    Não sei como conseguiram fazer isso

    • Quando usei, também era péssimo. Quando não eram vazamentos de memória estranhos ou problemas de privacidade, a UX era implementada de forma extremamente ruim
      Vendo este app e o Fetlife, há um grande problema nessas comunidades continuarem presas ao primeiro app que apareceu, independentemente da qualidade
    • Quando usei, a comunidade era boa, mas o app nunca foi escrito direito
      Então, há algum tempo, fizeram um flag day em que lançaram ao mesmo tempo um novo app e novos servidores para todo mundo, e a maioria sequer conseguiu fazer login. Quem conseguiu entrar e era cliente pagante perdeu os benefícios premium, likes e chats desapareceram, e assim por diante. Eu acabei nunca conseguindo fazer login e abandonei o app naquele ponto
  • Sinceramente, me surpreende que os pesquisadores tenham esperado tanto para divulgar
    Se você dá 6 meses para uma startup ruim corrigir uma brecha de privacidade tão grave, ela continuará abusando do privilégio de coletar esse tipo de informação em primeiro lugar. Acho que deveriam dar só 2 meses e divulgar. Eles precisam aprender que não se brinca de roleta com as informações privadas das pessoas