Voltar para todas as publicações
Publicado em · por Renaud Deraison

A wordlist já conhecia o seu diretório pessoal

O F5 Labs registrou 807 ataques e 32.000 eventos em um único mês contra servidores de desenvolvimento Vite expostos, usando um truque de query string que passa direto pela própria deny-list do Vite. Os scanners não estão procurando a sua aplicação. Eles pedem /home/ubuntu/.aws/credentials por caminho absoluto, e /proc/self/environ, e terraform.tfstate. Em um workspace do Bromure Agentic Coding, essa porta está voltada para um switch privado, e o arquivo que a wordlist quer não guarda chave nenhuma.

Este atacante não lhe envia pacote nem arquivo compactado. É o seu próprio servidor de desenvolvimento que está escutando, e um estranho pede a ele arquivos pelo nome. Qual máquina responde decide o que ele leva.

Você pede ao agente para colocar o frontend no ar para dar uma olhada. Ele digita npm run dev, abre a porta, carrega a página, e você volta a revisar o diff. Em algum ponto desse ciclo, em um docker-compose.yml ou em uma flag --host que você acrescentou semanas atrás para que o preview ficasse acessível pelo celular, o servidor passou a escutar em todas as interfaces em vez do loopback. Doze minutos depois, algo que se apresenta como Googlebot pede ao seu servidor de desenvolvimento /@fs/../.env?raw?? e recebe um HTTP 200.

O F5 Labs publicou os números em 11 de setembro. Ao longo de um único mês de dados de sensores, agosto de 2026, seus honeypots registraram 807 ataques agrupados por sessão e cerca de 32.000 eventos brutos dirigidos a servidores de desenvolvimento Vite expostos, contra uma linha de base de 1.732 eventos nos três meses anteriores. Isso é dezoito vezes o tráfego, de um mês para o outro, por causa de um único bug. O BleepingComputer noticiou o caso em 14 de setembro.

Uma query string que passa pela deny-list

O Vite serve arquivos do sistema de arquivos do host por uma rota interna chamada /@fs/, que é como o servidor de desenvolvimento entrega ao seu editor um módulo que vive fora da raiz do projeto. Como essa rota pode alcançar qualquer lugar, o Vite traz uma deny-list, server.fs.deny, para bloquear os alvos óbvios: arquivos .env, certificados, fontes privadas.

A CVE-2026-39364, publicada em 7 de abril, permite que um atacante pule essa deny-list decorando a requisição com uma query string. O F5 descreve o mecanismo:

O servidor processa a requisição, normaliza o caminho e remove ou interpreta mal a query string durante a validação de acesso, deixando de acionar a verificação server.fs.deny.

O servidor não aplica a filtragem por deny-list e serve o arquivo alvo com uma resposta HTTP 200.

O bug afeta o Vite 7.1.0 até 7.3.2, e as versões 8.x anteriores à 8.0.5. O F5 capturou estes formatos de requisição em uso real, cada um curto o bastante para caber em uma linha: GET /@fs/.env?raw??, GET /@fs/../.env?raw??, GET /@fs/..%252f..%252f..%252f..%252froot/.env?raw?? e GET /@fs/..%252f..%252f..%252f..%252fproc/self/environ?raw??. Outras variantes do mesmo ferramental usam ?import&raw, ?import&url&inline, ?inline&import e ?raw?import.

Esses truques de parâmetro carregam seus próprios números de CVE: CVE-2025-30208, CVE-2025-31125 e CVE-2024-45811, três contornos mais antigos do @fs ainda carregados nos mesmos scanners. Quem opera a frota de varredura não se reequipou para um bug novo. Apenas acrescentou mais uma linha a um arquivo que já tinha.

uma requisição, um arquivoscannerGET /@fs/../.env?raw??User-Agent: Googlebot/2.1servidor de dev vitecaminho normalizado para a entrega/@fs/ → leitura em discoserver.fs.denyquery string mal tratada, teste puladoarquivo lido e devolvidosem auth, sem sessão, sem log que você leiaHTTP 200o conteúdo do arquivo,no corpo da respostajá nos mesmos scannersCVE-2025-30208 · ?raw??CVE-2025-31125 · ?inline&importCVE-2024-45811 · ?import&raw
O contorno em uma única troca. A rota /@fs/ do Vite existe para servir arquivos do projeto, e server.fs.deny existe para mantê-la longe dos segredos. Acrescentar uma query string no fim, cuja forma comum é ?raw??, faz o caminho ser normalizado para a entrega mas tratado de forma errada na verificação de acesso, então a deny-list fica em silêncio e o arquivo volta com um 200. O path traversal na mesma requisição sai do projeto, e é assim que um servidor de desenvolvimento de frontend acaba lendo /proc/self/environ.

A wordlist é um mapa da máquina de um desenvolvedor

Leia o que os scanners pedem e repare em quão pouco daquilo diz respeito à sua aplicação.

Eles pedem .env, .env.local, .env.production, .env.development e .env.staging. Pedem terraform.tfstate, terraform.tfvars, .terraform/terraform.tfstate, serverless.yml e .serverless/serverless-state.json. Pedem .azure/credentials e .azure/accessTokens.json. Pedem /etc/passwd, /proc/1/environ, /proc/self/cwd/.env e /proc/self/environ. Esse último caminho guarda o bloco de ambiente do próprio processo do servidor de desenvolvimento, onde vai parar uma API_KEY que o seu shell exportou.

E então pedem credenciais AWS percorrendo a lista de diretórios pessoais sob os quais o processo de um desenvolvedor poderia estar rodando:

/root/.aws/credentials, /home/ec2-user/.aws/credentials, /home/ubuntu/.aws/credentials, /home/node/.aws/credentials, /home/www-data/.aws/credentials, /home/admin/.aws/credentials, /home/debian/.aws/credentials, /var/www/.aws/credentials, /usr/src/app/.aws/credentials, /app/.aws/credentials. Depois .aws/config, .aws/credentials.backup, .aws/credentials.bak, .aws/sso/cache/, rootkey.csv, aws-exports.js e amplifyconfiguration.json.

Esses caminhos inventariam a máquina de um desenvolvedor, enumerada por nome de usuário, e o Vite é apenas a porta. O bug é acessório; três bugs mais antigos na mesma rota pegam carona nas mesmas requisições. Os operadores estão apostando que um processo escutando em um endereço roteável roda sob um usuário cujo diretório pessoal guarda chaves de verdade.

O tráfego vem vestido para sobreviver a um olhar distraído nos logs. O F5 registrou cabeçalhos User-Agent forjados alternando entre Googlebot/2.1, ClaudeBot/1.0, GPTBot/1.4, PerplexityBot/1.0, OAI-SearchBot/1.3 e Amazonbot/0.1, além de valores forjados de X-Forwarded-For e X-Real-IP para furar allow-lists de IP. As origens ficam nas faixas 34.x e 35.x do Google Cloud, espalhadas por várias regiões, lideradas pelos Estados Unidos com 17.297 eventos, depois a Bélgica com 4.407 e os Países Baixos com 4.011. Neste mês, uma linha no seu log de acesso que diz ser um crawler de IA é prova fraca de que um crawler a enviou.

Duas das recomendações do F5 são perguntas de arquitetura

O F5 encerra com cinco conselhos. Três são comuns e corretos: atualizar para 7.3.2 ou 8.0.5, filtrar /@fs/ na borda, verificar crawlers por DNS reverso em vez de confiar no cabeçalho. Os outros dois descrevem uma postura que você precisa sustentar, e não uma tarefa que você termina.

Garanta que servidores de desenvolvimento não escutem em interfaces externas. Audite configurações de Docker compose, regras de ingress do Kubernetes e security groups na nuvem.

Rotacione segredos expostos: se um servidor de desenvolvimento Vite sem correção esteve acessível a partir de redes externas durante agosto de 2026, trate as variáveis .env locais, as credenciais AWS, os tokens de acesso do Azure e os arquivos de estado do Terraform como potencialmente comprometidos.

A primeira pede que você mantenha uma promessa sobre cada porta, em cada arquivo compose, em cada branch, por toda a vida do projeto, num momento em que quem digita npm run dev costuma ser um agente e não você. A segunda pergunta o que você faz depois, e responde: rotacione o que a máquina conseguia ver.

Ambas ficam mais fáceis se a porta abrir em outro lugar, e se os arquivos que a wordlist nomeia não guardarem nada que valha a pena rotacionar.

Onde um workspace do Bromure coloca a porta

O Bromure Agentic Coding roda agentes de código dentro de uma VM Linux virtualizada por hardware no seu Mac, com os controles de segurança do lado do host dessa fronteira. Dois deles respondem a esta campanha.

O servidor de desenvolvimento escuta dentro de um switch privado. No modo NAT padrão, cada VM de workspace se conecta a um único switch L2 de software no escopo do processo, multiplexado sobre uma única interface vmnet, em uma sub-rede privada. Essa sub-rede é 192.168.64.0/24 a menos que a sua própria LAN já a use, o Bromure roda seu próprio servidor DHCP nela, e cada workspace mantém um MAC determinístico e um lease estável. O manual declara a consequência: o seu Mac alcança as VMs, a sua LAN física não as enxerga, e conexões de entrada vindas de outro lugar são impossíveis a menos que você mesmo publique um serviço. Um agente que escuta em 0.0.0.0 dentro dessa VM escutou em cada interface que ela tem, e todas elas estão voltadas para um switch que começa e termina no seu laptop. Você não tem security group para auditar, porque não tem rota de entrada para proteger.

Você ainda consegue ver o que está escutando, e é uma lista, não uma auditoria. O painel do workspace traz um cartão Listening Ports mostrando cada socket alcançável de fora no convidado, como um endpoint <IP-da-VM>:<porta> com cópia em um clique. Isso transforma a recomendação nº 2 do F5 em um cartão que você olha de relance, atualizado a partir do convidado a cada segundo e meio, em vez de uma auditoria que você agenda. Quando você de fato quiser que o mundo veja um preview, um serviço provavelmente HTTP ganha um botão de globo que publica aquele serviço específico por um quick tunnel da Cloudflare, atrás de um diálogo de consentimento único. Você expõe um serviço clicando nele, serviço por serviço, no host, em vez de deixar uma flag viva em um arquivo compose.

Você continua com o motivo pelo qual recorreu a --host em primeiro lugar. O sidecar Chromium descartável compartilha o mesmo segmento L2 da VM do workspace, então o navegador embutido carrega o servidor de desenvolvimento do agente no endereço da VM. Não em localhost, já que o navegador roda em uma máquina separada.

Se outras máquinas realmente precisarem alcançar a VM, o modo Bridged a coloca na sua LAN física. Você define isso por workspace no editor de workspace, no host, e ele volta para NAT se a interface estiver indisponível no lançamento. Você faz essa escolha em um painel, não em um arquivo de configuração que o agente pode editar.

uma máquina de dev expostaum endereço roteável, um usuário real, arquivos reaisGET /@fs/../.env?raw??chega de 34.x, 200 OK/home/ubuntu/.aws/credentialsum id de chave de acesso e um segredo vivo/proc/self/environcada token que o shell exportoudepoisrotacionar as chaves, os tokens, o arquivo de estado,e adivinhar a janelaem um workspaceum switch privado, um home cheio de substitutosnenhuma rota de entrada para chegarvmnet NAT · 192.168.64.0/24cartão Listening Portscada socket aberto, e um globo para publicar uma wordlist cai em falsificaçõessk-ant-api03-brm-… · ghp_… · credential_processdepoisresetar o disco se você quiser,e não rotacionar nada
A mesma varredura, contra duas máquinas. Em uma máquina de desenvolvimento com endereço roteável, a requisição chega a um processo rodando como um usuário real, e cada caminho da wordlist resolve para um arquivo real: .env com chaves vivas, ~/.aws/credentials com um segredo, /proc/self/environ com os tokens exportados pelo shell. Em um workspace do Bromure Agentic Coding a porta está voltada para um switch vmnet privado sem rota de entrada. Publique o serviço de propósito e a wordlist ainda assim cai em substitutos, e em um ~/.aws/config que nomeia um auxiliar em vez de guardar uma chave.

E se você publicar o serviço de propósito

Às vezes você quer mesmo o preview na internet, para um cliente ou um colega. Então clique no globo, abra o túnel e deixe os scanners encontrarem. Desça a wordlist, entrada por entrada, e veja o que volta.

.env e /proc/self/environ devolvem o ambiente do workspace, e cada credencial ali é falsa. O Bromure deriva cada substituto do valor real mais um sal de 32 bytes próprio da instalação, via HKDF-SHA256, mantendo o formato que um validador no cliente espera: uma chave da Anthropic se lê sk-ant-api03-brm-…, um token do GitHub é ghp_ mais 36 caracteres, o do GitLab é glpat- mais 20. O Bromure escreve esses substitutos nas variáveis de ambiente e em ~/.git-credentials, ~/.docker/config.json, ~/.kube/config e nas configurações MCP. Os valores reais nunca entram na VM; ficam cifrados no Mac, e um proxy no lado do host troca cada um deles no fio no último momento, restrito ao host de destino a que pertence.

/home/ubuntu/.aws/credentials é a entrada mais afiada, porque /home/ubuntu é o diretório pessoal de uma VM de workspace do Bromure. O scanner acerta qual usuário roda o servidor. O arquivo mesmo assim não está lá. A configuração AWS do Bromure não escreve arquivo de credenciais nenhum; ela escreve ~/.aws/config com uma linha credential_process apontando para um auxiliar que entrega, por um socket do host, o seu access key ID real emparelhado com uma chave secreta falsa de quarenta caracteres, e omite o token de sessão. Os SDKs da AWS, a CLI aws, o terraform e o boto3 pegam isso sozinhos. Um scanner que lê o arquivo de configuração recebe o caminho de um auxiliar que não consegue chamar.

Leve o segredo falso assim mesmo e você terá levado algo inerte. O convidado assina suas requisições com essa falsificação, e o host tira a assinatura e assina de novo com a chave real na saída. Uma requisição que chegue à AWS por qualquer outro caminho falha com InvalidSignatureException. A última recomendação do F5 é tratar cada segredo local como comprometido e rotacioná-lo. A linha correspondente na própria documentação do Bromure diz o contrário: como só o substituto vazou, a credencial real nunca precisa ser rotacionada.

Uma entrada da wordlist nomeia um arquivo que vale dinheiro de verdade: terraform.tfstate. Arquivos de estado vivem onde o repositório vive, então a sua lista de pastas compartilhadas decide a exposição deles. Pastas do host se conectam a um workspace como montagens virtiofs em /home/ubuntu/<basename>, limitadas a oito por workspace, e são a única parte do sistema de arquivos do seu Mac que a VM alcança. Compartilhe o repositório em que o agente trabalha e nada mais, e o traversal encontra um diretório em vez de um disco.

A maior parte das histórias de segurança para desenvolvedores neste ano descreve algo que chega: um pacote, um arquivo compactado, um arquivo de documentação. Aqui não chega nada. Em vez disso, 32.000 requisições por mês pedem a um processo que você iniciou que leia arquivos em voz alta para um estranho, e o desfecho depende de qual máquina roda esse processo e do que há no diretório pessoal dela.

Coloque o servidor de desenvolvimento do agente em um switch onde só o seu Mac está, e encha o diretório pessoal dele de substitutos. Instale o Bromure Agentic Coding, e então deixe os scanners perguntarem.