Como qualquer usuário logado virava admin — e a linha que corrigiu
21 de julho de 2026 · Time Orion
A vulnerabilidade mais constrangedora que encontramos no nosso próprio código não tinha nada de sofisticada. Não exigia ferramenta, nem conhecimento de protocolo, nem sequer sair do navegador.
Bastava estar logado e digitar uma linha no console.
O caso
Usávamos uma biblioteca de autenticação que permite estender o usuário com campos extras. Adicionamos os nossos — entre eles o papel, que decide o que a pessoa pode fazer no painel.
A mesma biblioteca oferece uma rota de autoatendimento para o usuário atualizar o próprio perfil: nome, preferências, esse tipo de coisa. É uma rota legítima, exposta por padrão, e — este é o ponto — ela não verifica permissão, porque não precisa: a pessoa está mexendo em si mesma.
O problema é que ela atualiza os campos que estão declarados como aceitáveis na entrada. E o nosso campo de papel estava.
Resultado: qualquer usuário autenticado, do papel mais restrito, podia enviar uma requisição para a rota de atualização de perfil declarando-se administrador. E funcionava.
Reproduzimos, confirmamos e corrigimos no mesmo dia.
A correção
Uma propriedade na declaração do campo:
role: { type: "string", input: false }
input: false diz à biblioteca que aquele campo existe no usuário mas nunca é aceito vindo do cliente. O papel passa a ser gravável apenas pelo caminho administrativo do nosso próprio código, que checa permissão.
Uma linha. E a lição não é sobre a linha.
O que a gente estava fazendo de errado
Confundir "campo do usuário" com "campo editável pelo usuário". Assim que se adiciona um campo à entidade de autenticação, a pergunta que precisa ser feita, campo a campo, é: quem pode escrever nisso? Nome e avatar: o próprio. Papel, status de ativo, vínculo com organização, limite de crédito: nunca o próprio.
Herdar superfície sem inventariá-la. A rota estava lá desde a instalação, documentada, e ninguém tinha olhado o que ela aceitava depois que estendemos o modelo. Toda biblioteca de autenticação traz rotas de autoatendimento; a extensão do modelo muda o que essas rotas podem fazer, e essa mudança é invisível no diff que adiciona o campo.
Presumir que "não documentado como perigoso" significa seguro. O comportamento estava correto do ponto de vista da biblioteca. O contrato dela é: "eu atualizo o que você declarou como entrada". Fomos nós que declaramos errado.
A regra que ficou
Todo campo sensível de identidade nasce não gravável pelo cliente. Ele só muda por um caminho que verifica permissão explicitamente.
E o hábito que acompanha: quando estendemos uma biblioteca que expõe rotas próprias, listamos as rotas dela e testamos cada campo novo contra cada uma. É uma tarde de trabalho e evita a categoria inteira.
Por que isso é comum
Escalada de privilégio por atribuição em massa é uma das vulnerabilidades mais antigas que existem — a versão clássica é o formulário de cadastro que aceita um campo admin escondido. Ela sobrevive porque o mecanismo que a causa é o mesmo que dá conveniência: aceitar um objeto e aplicar seus campos.
Frameworks modernos mitigam com listas de permissão. E a mitigação falha exatamente no ponto em que o desenvolvedor estende o modelo e esquece de revisar a lista — que é o nosso caso.
O que fazer hoje, no seu sistema
Três perguntas que levam quinze minutos e podem valer muito:
- Que rotas de autoatendimento a minha biblioteca de auth expõe? (Atualizar perfil, trocar e-mail, atualizar metadados.)
- Que campos eu adicionei ao usuário depois de instalar?
- Algum desses campos decide permissão, cobrança ou vínculo?
Se a interseção entre 2 e 3 não estiver marcada como não-editável, você tem o mesmo bug.
O resumo
A vulnerabilidade não estava na biblioteca — estava na fronteira entre o que ela oferece e o que a gente acrescentou. Fronteira de biblioteca é onde mora o bug que ninguém revisa, porque cada lado parece correto isoladamente.
Corrigir custou uma linha. Encontrar custou uma pergunta que a gente devia ter feito no dia em que adicionou o campo.