← Back to list

Enumeração de contas: exposição que começa antes do login

Em muitos fluxos de autenticação, o primeiro contato do aplicativo com o backend acontece por uma API aberta, pensada para iniciar a…

Lucas Carvalho · 2026-04-23 16:30 · 0 claps · 6.8 min read
#ios #swif #software-development #software-engineering
Open on Medium ↗
Wiki topics: MM · Multimodal & Generative Media 🌐 · Web Development

Enumeração de contas: exposição que começa antes do login

Em muitos fluxos de autenticação, o primeiro contato do aplicativo com o backend acontece por uma API aberta, pensada para iniciar a jornada do usuário. O problema aparece quando essa etapa começa a responder de forma diferente para contas existentes e inexistentes. A partir daí, o que deveria ser apenas o começo do login pode virar um mecanismo de descoberta de identidades válidas.

Essa diferença nem precisa ser gritante. Pode estar em um status code, em um campo a mais no retorno, em uma mensagem mais específica ou até no tempo de resposta. Para quem observa o comportamento da aplicação com intenção de mapear sua superfície, isso já pode bastar.

Quando esse tipo de distinção fica exposto, a enumeração de contas deixa de ser hipótese e passa a ser possibilidade concreta. E, com uma base de contas válidas em mãos, outros abusos ganham muito mais eficiência.

Observação: esse tema costuma parecer secundário até o momento em que alguém percebe que a API está ajudando, sem querer, a separar usuários reais de entradas inválidas.

O que é enumeração de contas

Enumeração de contas acontece quando um sistema permite inferir, direta ou indiretamente, se uma identidade existe na base.

Essa identidade pode ser um e-mail, CPF, telefone, username, matrícula, número de conta ou qualquer outro identificador que participe do fluxo inicial de autenticação, recuperação ou cadastro.

O problema não está apenas em retornar algo explícito como “usuário não encontrado”. Ele também aparece quando a aplicação entrega pistas suficientes para separar contas válidas de contas inválidas.

Exemplos comuns:

  • mensagens diferentes para “usuário não existe” e “senha inválida”
  • códigos HTTP diferentes
  • tempo de resposta inconsistente
  • diferenças no payload de erro
  • envio de OTP apenas para usuários existentes
  • mudança de etapa do fluxo só quando a conta é válida

Como isso acontece em um app com API inicial pública e sem validação

Imagine um aplicativo mobile cujo primeiro passo de login chama uma API pública para descobrir como seguir com a autenticação.

O app envia algo assim:

{
  "login": "lucas@email.com"
}

E a API responde de formas diferentes.

Para um usuário existente:

200 OK
{
  "nextStep": "password",
  "hasBiometrics": true,
  "maskedPhone": "***1234"
}

Para um usuário inexistente:

404 Not Found
{
  "message": "Usuário não encontrado"
}

Pronto. A enumeração já nasceu.

Mesmo que você esconda a mensagem, o problema continua existindo se houver qualquer diferença observável entre os dois cenários.

Por exemplo:

  • um caso responde em 80 ms e o outro em 20 ms
  • um fluxo retorna 200, outro 404
  • um payload traz metadados adicionais
  • uma conta válida dispara algum efeito colateral, como envio de SMS ou geração de challenge

Com isso, alguém pode automatizar centenas ou milhares de tentativas e montar uma base de identidades reais.

Observação: o atacante nem precisa “invadir” nada nesse momento. Às vezes ele só precisa observar com cuidado aquilo que o sistema já está entregando sozinho.

Por que isso é perigoso

Muita gente olha para enumeração de contas como se ela fosse um problema menor porque, isoladamente, ela não entrega acesso.

Mas ela reduz incerteza.

E reduzir incerteza é valioso para quem quer atacar.

Quando alguém consegue separar contas válidas de inválidas, isso ajuda em várias frentes:

  • força bruta mais eficiente
  • credential stuffing com menor ruído
  • abuso de recuperação de acesso
  • phishing mais convincente
  • fraude orientada a usuários reais
  • coleta de inteligência sobre a base de clientes

Em outras palavras, a enumeração não costuma ser o golpe final. Ela costuma ser a etapa que prepara o terreno.

O erro clássico: confiar demais em uma API pública sem contexto do dispositivo

O cenário piora quando a API inicial é pública e aceita chamadas sem nenhum mecanismo de legitimidade do cliente.

Nesse desenho, o backend só recebe um identificador e responde. Ele não tem evidência mínima de que aquela chamada veio do aplicativo legítimo, instalado de forma válida, executando em um ambiente esperado.

Isso abre espaço para:

  • scripts simples
  • emuladores
  • apps modificados
  • chamadas diretas fora do aplicativo oficial
  • automação em larga escala

A API, nesse caso, fica exposta como uma interface de consulta de existência.

E esse é justamente o ponto em que entra a discussão sobre garantir que o device é um meio legítimo para fazer a chamada.

O que muda quando o backend passa a exigir prova de legitimidade do device

A ideia aqui não é “confiar cegamente no celular”. A ideia é elevar o custo da chamada e parar de aceitar qualquer origem como se fosse equivalente.

Em vez de deixar a API pública inicial responder apenas com base no identificador informado, o servidor passa a exigir sinais adicionais de legitimidade do aplicativo e do dispositivo.

Isso pode incluir:

  • prova de integridade do app
  • prova de que a chamada veio do app oficial
  • prova de posse de chave gerada no device
  • vínculo entre requisição e dispositivo
  • validações de risco antes de responder algo sensível

Com isso, a API inicial deixa de ser apenas um endpoint aberto e passa a ser uma etapa protegida por confiança progressiva.

O que resolver de forma objetiva

Se o objetivo é mitigar enumeração de contas, há algumas decisões que ajudam bastante.

1. Tornar a resposta indistinguível

Esse é o primeiro passo, e ele é obrigatório.

O sistema não deve revelar se a conta existe ou não na resposta inicial.

Isso vale para:

  • mensagem
  • status code
  • estrutura do payload
  • tempo de resposta
  • efeitos colaterais visíveis

Exemplo melhor:

200 OK
{
  "message": "Se os dados informados forem válidos, o próximo passo será disponibilizado."
}

Ou então um fluxo que sempre devolve a mesma estrutura e decide internamente o que fazer sem expor essa distinção ao cliente.

2. Evitar enriquecimento precoce do fluxo

Não devolva logo no começo coisas como:

  • telefone mascarado
  • indicação de biometria cadastrada
  • método de autenticação existente
  • nome parcial do usuário
  • status de cadastro

Tudo isso ajuda quem está testando existência de contas.

3. Aplicar rate limit e proteção comportamental

Mesmo com resposta uniforme, a API inicial continua sendo sensível.

Então faz sentido aplicar:

  • rate limiting por IP
  • rate limiting por device
  • rate limiting por fingerprint transacional
  • proteção por janela de tempo
  • detecção de padrão automatizado

4. Exigir prova de cliente legítimo antes de responder etapas sensíveis

Se a primeira chamada já é relevante para descoberta de contas, o backend pode exigir um artefato de confiança emitido ou assinado a partir do app/device legítimo antes de entregar qualquer progressão útil do fluxo.

Não é para “decorar” a chamada com header aleatório. É para mudar o modelo de confiança.

Como garantir que o device é um meio legítimo para fazer essa chamada

Não existe bala de prata. O caminho mais sólido costuma ser combinar mecanismos.

App attestation

No iOS, a Apple oferece o App Attest dentro do ecossistema DeviceCheck. A proposta é permitir que o servidor valide evidências de que a chamada está associada ao app legítimo em um dispositivo Apple.

No Android, a Play Integrity API ajuda o backend a verificar se a interação veio do app genuíno, instalado pelo Google Play, rodando em um dispositivo Android íntegro e certificado.

Esses mecanismos não “resolvem tudo”, mas mudam bastante o jogo. Eles ajudam a filtrar chamadas de apps adulterados, ambientes suspeitos e clientes não oficiais.

Prova de posse de chave no device

Outro caminho forte é fazer o app gerar ou usar uma chave atrelada ao dispositivo e usar essa chave para assinar desafios ou requisições.

Aqui a ideia não é só perguntar “quem está chamando?”, mas também “você consegue provar posse da chave que espero desse cliente?”.

Isso ajuda a sair de um modelo baseado apenas em identificador enviado para um modelo com evidência criptográfica de origem.

Challenge do servidor

Em vez de aceitar chamadas diretas e repetíveis, o backend pode emitir um challenge e exigir que o app o devolva processado da forma esperada.

Esse challenge deve ser:

  • curto
  • de uso único
  • vinculado à sessão ou tentativa
  • validado no backend
  • descartado após uso

Isso reduz replay e aumenta o custo de automação.

Binding transacional

Outra camada útil é vincular a prova técnica ao contexto da transação.

Por exemplo:

  • device attestation
  • nonce
  • request signing
  • janela curta de validade
  • correlação com telemetria de risco

O ganho aqui é que a chamada deixa de ser apenas “um POST com login” e passa a exigir coerência entre cliente, artefato, tempo e contexto.

O que esses mecanismos não resolvem sozinhos

App attestation não substitui:

  • resposta uniforme
  • rate limit
  • observabilidade
  • proteção contra enumeração por tempo
  • análise de abuso
  • desenho correto do fluxo

Se a API continuar respondendo de forma diferente para conta existente e inexistente, você ainda terá problema. A diferença é que talvez ele fique restrito a um conjunto menor de clientes capazes de passar pela primeira barreira.

Ou seja: attestation ajuda muito, mas não corrige sozinho um fluxo mal desenhado.

Observação: esse é um erro comum em segurança mobile. Às vezes se coloca uma tecnologia forte na borda, mas o comportamento funcional continua entregando informação demais.

Ferramentas e possibilidades que ajudam nisso

No iOS

  • App Attest, para validar evidências de legitimidade do app junto ao backend
  • DeviceCheck, como parte do ecossistema de verificação da Apple
  • armazenamento seguro de chaves no ecossistema Apple, combinado com assinatura de challenge
  • detecção de jailbreak e sinais de ambiente comprometido, como camada complementar

No Android

  • Play Integrity API, para obter verdicts sobre legitimidade do app e do ambiente
  • Android Keystore e estratégias de prova de posse de chave
  • detecção de root, tampering e execução suspeita como sinal complementar
  • validação server-side forte dos tokens ou verdicts recebidos

No backend

  • resposta uniforme
  • rate limiting
  • anti-automation
  • challenge-response
  • request signing
  • telemetria de risco
  • correlação de eventos
  • observabilidade para padrões de enumeração
  • bloqueio progressivo e step-up

Como referência de boas práticas

O OWASP MASVS e o material do OWASP MAS tratam segurança mobile como disciplina estruturada, incluindo verificação de controles e fraquezas relacionadas à ausência de attestation no app.

Fechamento

Enumeração de contas não costuma nascer de algo extraordinário. Muitas vezes, ela aparece quando o próprio fluxo inicial entrega pistas demais para quem está olhando com atenção.

O problema já não é só a existência de uma API pública, mas a forma como ela responde e o quanto ela confia em qualquer cliente que consiga chegar até ela.

No fim, proteger esse cenário passa por duas decisões simples de entender, mas importantes de sustentar: não revelar se a conta existe e não tratar toda chamada como igualmente legítima.

Agradeço a todos que chegaram até o final. Se este artigo ajudou, mesmo que um pouco, já valeu a pena escrevê-lo.

Até a próxima, Lucas Carvalho


메타데이터
post_id
af849d05bc56
slug
enumeração-de-contas-exposição-que-começa-antes-do-login-af849d05bc56
url
https://medium.com/@lucascarvalhodeveloper/enumera%C3%A7%C3%A3o-de-contas-exposi%C3%A7%C3%A3o-que-come%C3%A7a-antes-do-login-af849d05bc56
canonical_url
https://medium.com/@lucascarvalhodeveloper/enumera%C3%A7%C3%A3o-de-contas-exposi%C3%A7%C3%A3o-que-come%C3%A7a-antes-do-login-af849d05bc56
author_url
https://medium.com/@lucascarvalhodeveloper
status
ok
fetched_at
2026-06-21 15:33:18