Pular para o conteúdo

Noções de segurança

Uma chave AWS exposta no seu frontend não é como uma chave OpenAI

Uma chave de acesso da AWS exposta no seu frontend lê e grava no seu armazenamento. A solução costuma ser uma presigned URL, não uma chave nova.

Vlad Tkachenko10 min de leitura
Uma página de código do aplicativo com um valor curto aceso e um longo encoberto, uma chave atravessada no canto dela e, atrás, uma estante de arquivos.

Em resumo

  • Uma chave de acesso da AWS exposta no seu frontend é um problema diferente do de uma chave que só consegue gastar dinheiro. O que ela alcança quem decide são as permissões dela.
  • Duas sequências assinam uma requisição da AWS: um identificador que começa com `AKIA` e uma metade secreta. Uma sem a outra não assina nada.
  • Uma chave nova colada no mesmo lugar é o mesmo achado com outro valor. A solução é uma presigned URL, para que o navegador não guarde credencial nenhuma.
  • Encontramos uma em 9 dos 31.056 aplicativos escaneados em que essa verificação foi até o fim.

Alguém abre o seu aplicativo em um navegador, aperta F12 e encontra uma sequência começando com AKIA no meio do seu código de frontend. Ou o GitHub escreve avisando que um commit que você subiu em março carrega uma credencial da AWS. O conselho que vem junto com qualquer um dos dois é sempre o mesmo: rotacione a chave. Para a maioria das chaves essa é quase a resposta inteira.

Aqui está a parte que guia após guia erra. Uma chave de acesso da AWS exposta no seu frontend não é uma chave que gasta dinheiro. É uma chave que abre coisas, e as duas dão errado em direções diferentes. Uma chave que só gasta é um cartão corporativo: alguém faz uma conta, você cancela o cartão, e o estrago é um número que dá para ler num extrato. Uma chave que abre coisas é o molho de chaves do depósito. O que ela alcança depende do que escreveram nela quando foi feita, e isso se lê numa única tela do seu console da AWS.

O que significa AKIA no começo de uma chave?

Marca uma chave de acesso de longa duração, que pertence a um usuário da sua conta ou ao login root da conta. A AWS anota o prefixo na própria referência de identificadores: AKIA para chaves de acesso e ASIA para as temporárias que o serviço de tokens distribui. Então os quatro primeiros caracteres dizem com qual tipo você está lidando, e AKIA é o tipo sem prazo de validade. Funciona até alguém desligar.

Uma credencial da AWS são duas sequências, e uma requisição precisa das duas para sair assinada. A primeira é o identificador da chave de acesso, a parte que começa com AKIA. A segunda é a chave de acesso secreta, uma sequência mais longa sem prefixo nenhum na frente. O identificador sozinho não assina nada.

Vale conferir isso antes de decidir o tamanho da manhã ruim, porque muda a resposta. Se o seu bundle carrega a sequência AKIA e nada parecido com a metade secreta dela, um visitante que leu a sua página levou o nome da chave e não a chave. Na prática as duas viajam juntas, porque o que botou uma no navegador botou as duas: um par de variáveis de ambiente chamadas AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY, lidas por código que deveria rodar num servidor.

As duas metades juntas assinam uma requisição. O identificador sozinho é um nome, e um nome não abre nada.

O que alguém consegue fazer com a minha chave de acesso da AWS?

O que as permissões dela permitirem, e nada além disso. Então são três respostas.

O que ela consegue ler? Todo bucket do S3 que a política listar, e todo objeto dentro deles, inclusive aqueles para os quais você nunca colocou link nenhum. Um bucket que nada no seu aplicativo menciona não está escondido de uma credencial que pode listá-lo.

O que ela consegue gravar? Os mesmos lugares, e essa é a metade que se esquece. Uma chave que grava também sobrescreve e apaga, e um arquivo trocado no lugar fica exatamente igual ao que você enviou até alguém abrir.

O que ela consegue ligar? Essa é a que tem taxímetro. Uma política larga o bastante para subir máquinas sobe máquinas, e isso cobra por hora enquanto estiver rodando. Nada na cópia que o navegador tem limita quanto da sua conta ela alcança; a política é o único limite que existe.

A pior versão disso é uma chave feita para o login root da conta. A AWS diz nas próprias orientações sobre o usuário root que ele tem acesso irrestrito a todos os serviços da AWS, aos recursos e aos dados de cobrança, e desaconselha criar qualquer chave de acesso para ele. Se a chave na sua página começa com AKIA e pertence ao usuário root, o que está exposto é a conta inteira e não um bucket.

Então não chute nada disso. Abra o IAM, ache o usuário dono da chave e leia as políticas ligadas a ele. É uma tela, e ela responde às três perguntas acima.

Basta rotacionar?

Desative hoje a chave exposta, e não ponha uma substituta no mesmo lugar.

Desativar é o primeiro passo certo e é mais suave do que soa. A AWS deixa um usuário ter até duas chaves de acesso ativas ao mesmo tempo, e isso existe justamente para que uma substituta suba antes de a antiga descer. Desativar deixa a chave ali e desligada, e dá para religar; apagar é irreversível, e a metade secreta de uma chave apagada não volta. Então desative, veja o que quebra e apague depois.

O que não encerra o assunto é criar uma chave nova e colar onde estava a antiga. O vazamento nunca foi de verdade sobre aquele valor. Foi sobre um navegador segurando uma credencial sem prazo, e tudo o que a sua página baixa, cada visitante baixa. Uma sequência AKIA novinha no mesmo bundle volta como o mesmo achado no próximo escaneamento.

A AWS diz isso com todas as letras no guia de auditoria de segurança, falando de aplicativos móveis: eles nunca deveriam conter chaves de acesso embutidas, nem mesmo criptografadas. Um bundle de navegador se lê com mais facilidade do que um binário de aplicativo, então a mesma resposta vale aqui e dá menos trabalho a um estranho.

Se você já achou a chave num lugar público, a ordem em que se trabalha importa, e o que fazer quando uma chave de API vaza deixa essa ordem pronta para chaves de todo tipo.

O que uma presigned URL faz no lugar

Deixa o navegador fazer uma coisa só com um arquivo só sem guardar credencial nenhuma.

O seu próprio servidor fica com a chave de acesso, onde um visitante não consegue ler. Quando a sua página precisa colocar um arquivo no S3 ou tirar um de lá, ela pede ao seu servidor, e o seu servidor assina uma URL para aquele objeto e aquela ação e devolve. O navegador usa a URL e não leva mais nada junto. A AWS descreve uma presigned URL como acesso temporário a objetos do S3 sem credenciais da AWS, e o mesmo mecanismo cobre baixar e enviar.

De volta ao depósito. Uma presigned URL é receber o entregador na porta com a caixa que ele veio buscar, em vez de passar o molho e torcer para ele achar. O que o entregador leva para de funcionar quando o relógio dele acaba.

Para o caso em que cada pessoa logada chega aos próprios arquivos, a AWS aponta para outro lugar de novo: as orientações sobre proteger chaves de acesso dizem que aplicativos móveis deveriam usar o Amazon Cognito para cuidar de identidades em vez de embutir chaves de acesso. O que o Cognito dá ao navegador é uma credencial temporária, do tipo ASIA do começo deste artigo, que para de funcionar sozinha depois de um tempo. Uma cópia raspada da sua página numa terça já está morta na quarta, e essa é a única propriedade que uma chave AKIA não tem.

O caminho de cima coloca no navegador algo que serve para todo arquivo, amanhã como hoje. O de baixo dá a ele um arquivo e um prazo.

Como verifico se o meu aplicativo entrega uma chave da AWS?

Procure no que o navegador baixou, não no que está no seu projeto. São dois arquivos diferentes, e o que conta é o que os seus visitantes recebem.

No navegador. Abra o seu site publicado, aperte F12 e procure AKIA no JavaScript carregado. Chrome e Edge procuram em todos os arquivos carregados de uma vez pelo painel Sources.

No seu projeto. Procure AKIA, AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY. Depois procure os dois prefixos que colocam um valor no bundle de propósito, VITE_ e NEXT_PUBLIC_, e leia cada valor atrás deles. Por que esses dois prefixos publicam tudo o que nomeiam é a mecânica por trás da maioria desses casos, e é uma regra do build que ninguém digitou por engano.

De fora. O nosso scan gratuito lê o seu site publicado do jeito que um estranho leria e informa quais credenciais ele vê no que você publicou, essa inclusive. Leva uns 20 segundos e não pede conta: escaneie seu aplicativo de graça.

Em todos os aplicativos que escaneamos, esse é um achado raro: uma chave de acesso da AWS apareceu em 9 dos 31.056 aplicativos em que essa verificação foi até o fim. As que aparecem bem mais são as do Google, que em geral estão certas, e as da OpenAI, que em geral não estão. O que realmente vaza de aplicativos feitos assim põe todas lado a lado com os números.

O que a chave fez enquanto esteve lá fora

Abra o histórico de eventos do CloudTrail e filtre pelo identificador da chave de acesso.

O histórico de eventos está ligado em toda conta da AWS sem precisar ativar nada, guarda os últimos 90 dias e não custa nada a mais. Cada entrada carrega o identificador da chave que fez a chamada, então dá para reler o que a sua fez. Isso responde às perguntas de configuração: um bucket criado, uma política mudada, um usuário adicionado.

Aí vem a parte que é bom saber antes de ir olhar. O histórico guarda eventos de management. Ler um objeto do S3 é um evento de data, e a AWS é explícita ao dizer que os trails não registram esses por padrão e que eventos de data não aparecem no histórico. Ligá-los grava daquele momento em diante.

Então a leitura honesta é esta. Se a chave foi usada para mudar a sua conta, o registro mostra. Se foi usada para baixar o conteúdo de um bucket, provavelmente não mostra, e você decide o que contar aos seus clientes sabendo que pode ter acontecido.

Como manter uma chave fora do próximo build

Uma credencial sai para o navegador na hora do build, então o próximo deploy é o que precisa de atenção. A chave que você tira hoje volta na tarde em que alguém acrescenta uma função que precisa do S3 e pede a um agente para ligar os fios.

O Reeve Monitor relê o seu site publicado por você:

  • as nove verificações a cada hora, em até três aplicativos
  • uma mensagem quando um resultado muda, para que uma chave que chega numa terça não espere você olhar
  • se o aplicativo responde, a cada 60 segundos
  • um relatório mensal do que ele viu

Se o seu aplicativo guarda os dados num projeto Supabase, o Reeve Care guarda uma cópia disso também:

  • uma cópia criptografada do seu banco Supabase toda noite, guardada onde o seu projeto não alcança
  • cada cópia verificada antes de contar, contando as linhas de cada tabela
  • uma restauração em um clique quando você precisar
  • também os seus arquivos enviados, assim que você conectar uma credencial de Storage
  • tudo o que o Monitor faz

Cada uma dessas nove verificações descreve o que um estranho consegue alcançar. Uma cópia do banco de ontem à noite responde à outra pergunta, a de o que alguém já alterou enquanto a chave esteve lá fora.

O que fazer esta semana

O que fazer

  • Abra o seu site publicado, aperte F12 e procure AKIA no JavaScript carregado. Esse é o arquivo que os seus visitantes têm, e é o único que conta.
  • Se achar uma, desative hoje no IAM. Desativar é reversível e vale na hora; apagar não é, e a metade secreta não volta.
  • Leia as políticas ligadas ao usuário dono da chave antes de decidir o tamanho disso. O que ela lê, o que ela grava e o que ela liga são três respostas separadas.
  • Troque o padrão em vez do valor. O seu servidor guarda a chave e assina uma presigned URL para um objeto, ou o Amazon Cognito dá ao navegador uma credencial temporária que expira sozinha.
  • Filtre o histórico de eventos do CloudTrail pela chave nos últimos 90 dias. Ele mostra o que a chave configurou, e não mostra quais arquivos ela leu a menos que os eventos de data já estivessem ligados.

Comece pela busca com F12, porque em um minuto ela resolve se você tem algum problema. Se aparecer alguma coisa, a checklist de segurança de 10 minutos cobre o resto do que um aplicativo recém-lançado costuma deixar aberto, e mover um segredo para uma edge function é o padrão para toda chave que nunca deveria ter estado na página.

Perguntas frequentes

Alguma vez é seguro colocar chaves da AWS no código do frontend?

Não. A AWS diz isso no próprio guia de auditoria de segurança, falando de aplicativos móveis: um aplicativo nunca deveria conter chaves de acesso embutidas, nem mesmo criptografadas. Um bundle de navegador se lê com mais facilidade do que um binário de aplicativo, então a mesma resposta vale aqui e dá menos trabalho a um estranho. Se a sua página precisa mesmo chegar ao S3, os caminhos previstos são uma presigned URL assinada pelo seu próprio servidor, ou credenciais temporárias do Amazon Cognito, que expiram sozinhas.

O que alguém consegue fazer com a minha chave de acesso da AWS?

Exatamente o que as permissões por trás dela permitirem, e é por isso que não existe uma resposta só. Uma chave presa a um usuário com acesso de leitura a um bucket lê esse bucket. Uma chave presa a um administrador faz tudo o que um administrador faz, inclusive ligar máquinas que cobram por hora. Uma chave do usuário root da conta é o pior caso, porque a AWS afirma que o usuário root tem acesso irrestrito a todos os serviços, recursos e dados de cobrança. Abra o IAM e leia as políticas ligadas à chave antes de decidir o quanto se preocupar.

O que é uma presigned URL?

Uma URL longa que deixa um navegador fazer uma coisa só com um arquivo só no S3, por um número fixo de segundos, sem guardar nenhuma credencial da AWS. O seu próprio servidor guarda a chave e assina a URL quando pedem. A AWS descreve presigned URLs como acesso temporário a objetos do S3 sem credenciais da AWS, e elas servem tanto para baixar quanto para enviar.

Como verifico se o meu aplicativo entrega uma chave da AWS?

Procure no que o seu navegador baixou, não no que está no seu projeto. Abra o seu site publicado, aperte F12 e procure AKIA no JavaScript carregado. Depois procure no seu projeto por AKIA, AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY, e por qualquer nome de variável que comece com VITE_ ou NEXT_PUBLIC_ e guarde uma delas, porque são esses prefixos que colocam um valor no bundle logo de início.

Minha chave da AWS está no GitHub. E agora?

Desative primeiro no IAM, o que é reversível e vale na hora, e depois crie uma substituta para o servidor que precisava dela. Tirar a chave do código não fecha nada, porque o valor antigo continua no seu histórico de commits e em todo clone que alguém tenha feito. Em seguida abra o histórico de eventos do CloudTrail e filtre pela chave para ver o que ela fez.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Não sabe como está o seu próprio app?

Faça um scan gratuito e receba uma nota clara de A a F em cerca de 20 segundos. Sem conta e sem cartão.

Verificar meu app grátis

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.