TL;DR

Durante um engajamento autorizado de pentest (WEB + API) contra screening.globo.com, solicitado pelo CSIRT Globo, identificamos que o banco de dados Firestore de produção do projeto estava completamente aberto: leitura, escrita e deleção sem nenhuma credencial. Qualquer pessoa na internet, sem autenticação, conseguia ler a base inteira de usuários (PII completa) e também criar, sobrescrever ou apagar registros. A causa raiz foi uma única Security Rule permissiva (allow read, write: if true) replicada em produção e em desenvolvimento. A falha já foi corrigida pela Globo, que autorizou a publicação deste write-up.

Este post reconstrói o raciocínio do teste do zero: como cheguei do reconhecimento inicial até a confirmação de escrita/deleção irrestrita, os erros de configuração por trás de cada etapa, e o que fica de lição pra quem faz — ou defende contra — esse tipo de ataque.


Contexto e escopo

Escopo do Engajamento
Alvo
screening.globo.com
Tipo
Misto — WEB + API
Nível
ASVS v5.0 L2
Metodologia
OWASP WSTG v4.2 + catálogo API-SEC-NN
Data
29/08/2026
Solicitante
CSIRT Globo

O engajamento seguiu duas fases: reconhecimento e mapeamento de superfície, e execução + validação, com cada requisição registrada em uma Replay Collection nomeada — nada de achado "confirmado de cabeça"; toda evidência foi reproduzida e revisada antes de virar finding oficial.


Cadeia de ataque — visão geral

Antes de detalhar cada passo, vale ter uma visão macro da cadeia completa. Cada etapa alimenta a seguinte — e em nenhum momento foi necessária qualquer credencial:

🔍 PASSO 1 — RECONHECIMENTO

Bundle JS público em s.glbimg.com entrega apiKey, projectId e appId — para dois ambientes (PRD e DEV).

📖 PASSO 2 — LEITURA IRRESTRITA

GET anônimo no Firestore REST API retorna 200 OK com 135 KB+ de PII — coleção inteira de usuários exposta.

✍️🗑️ PASSO 3 — ESCRITA E DELEÇÃO

PATCH cria documento sintético, DELETE remove — ambos sem autenticação. Integridade comprometida.

🪞 PASSO 4 — ESPELHO DEV

Ambiente DEV (gglobo-screening-hdg-dev) com mesma regra permissiva e dados aparentemente reais.


Passo 1 — O bundle JS que entregou o mapa todo

Antes de qualquer teste na API, o reconhecimento passivo em s.glbimg.com (CDN estático que serve os assets da aplicação) já revelava mais do que deveria: o bundle JavaScript público carregado pelo frontend continha, em texto plano, o objeto de configuração do Firebase — apiKey, projectId, storageBucket e appId, para dois ambientes: produção (gglobo-screening-hdg-prd) e desenvolvimento (gglobo-screening-hdg-dev).

Isso, isoladamente, não é uma vulnerabilidade — o próprio Google documenta que a API Key do Firebase identifica o projeto, não autentica o usuário; ela pode aparecer no client-side sem problema se as Security Rules estiverem corretas. Mas é exatamente esse "se" que transforma um bundle público em reconhecimento completo: com projectId e apiKey em mãos, um atacante já tem tudo que precisa para formular requisições diretas contra a API REST do Firestore — sem precisar de nenhum scan adicional.

Exposição de configuração de cliente não é o achado em si — é o multiplicador do achado seguinte.


Passo 2 — Testando se a porta realmente estava destrancada

Com o projectId de produção em mãos, o próximo passo é o mais óbvio possível: uma requisição GET direta contra o endpoint REST do Firestore, sem nenhum header de autenticação.

GET /v1/projects/.../documents/users/ 200 OK
GET /v1/projects/gglobo-screening-hdg-prd/databases/(default)/documents/users/ HTTP/1.1
Host: firestore.googleapis.com

Sem Authorization, sem cookie, sem qualquer credencial de usuário. A resposta: 200 OK, mais de 135 KB de corpo, contendo a coleção inteira de usuários — cada documento com PII completa (email, nome, sobrenome, empresa, cargo, país, território e role na plataforma).

Um 200 OK anônimo desse tamanho é a assinatura de uma Security Rule do tipo:

allow read, write: if true;

— a regra menos restritiva que o Firestore permite, equivalente a deixar o banco inteiro público na internet. Não existe meio-termo aqui: ou a regra exige autenticação, ou não exige nada, e o comportamento observado confirmou a segunda opção.

Reproduzi a leitura mais de uma vez (incluindo por ID de documento específico) para eliminar a hipótese de resposta em cache ou comportamento intermitente — o resultado foi consistente em todas as tentativas, o que é o que se espera antes de elevar isso de suspeita para finding crítico.


Passo 3 — De "consigo ler" para "consigo apagar"

Confirmar leitura sem autenticação já seria crítico por si só (exposição de PII de todos os clientes cadastrados). Mas a pergunta natural do próximo passo é: as Security Rules também liberam escrita?

Testar isso contra dados reais está fora de cogitação — a resposta certa é criar um documento sintético, validar o comportamento, e limpar tudo depois. Foi exatamente esse o fluxo:

1. PATCH — Criar um documento de teste, sem autenticação

PATCH /v1/projects/.../documents/users/pentest-bbounty-...-DELETE-ME-2 200 OK
PATCH /v1/projects/gglobo-screening-hdg-prd/databases/(default)/documents/users/pentest-bbounty-1788044488-DELETE-ME-2
Host: firestore.googleapis.com
Content-Type: application/json

{
  "fields": {
    "first_name": { "stringValue": "PENTEST-ORCHESTRATOR" },
    "test_marker": { "stringValue": "pentest-bbounty-2026-08-29-delete-immediately" }
  }
}

Resposta: 200 OK, documento criado com createTime e updateTime retornados pela própria API — sem exigir nenhum token.

Requisição PATCH no Caido criando o documento sintético pentest-bbounty, com resposta 200 OK e createTime/updateTime retornados pelo Firestore
Figura 1 — PATCH sem autenticação criando o documento de teste PENTEST-ORCHESTRATOR na coleção users/ de produção. Resposta 200 OK confirma escrita irrestrita.

2. DELETE — Remover o mesmo documento, também sem autenticação

DELETE /v1/projects/.../documents/users/pentest-bbounty-...-DELETE-ME 200 OK
DELETE /v1/projects/gglobo-screening-hdg-prd/databases/(default)/documents/users/pentest-bbounty-1788044488-DELETE-ME
Host: firestore.googleapis.com

Resposta: 200 OK, corpo vazio ({}) — deleção confirmada.

Requisição DELETE no Caido removendo o documento sintético, com resposta 200 OK e corpo vazio
Figura 2 — DELETE sem autenticação removendo o documento sintético. Corpo de resposta vazio ({}) com status 200 confirma a deleção.

3. GET — Confirmar que o documento realmente não existe mais

GET /v1/projects/.../documents/users/pentest-bbounty-...-DELETE-ME 404 NOT FOUND
GET /v1/projects/gglobo-screening-hdg-prd/databases/(default)/documents/users/pentest-bbounty-1788044488-DELETE-ME
Host: firestore.googleapis.com

Resposta: 404 Not Found. Prova de deleção efetiva, sem resíduo em produção.

Requisição GET no Caido confirmando 404 Not Found após a deleção do documento sintético
Figura 3 — GET de confirmação retornando 404, provando que o documento sintético foi de fato removido e não deixou resíduo em produção.

Esse ciclo completo — criar, confirmar, apagar, confirmar de novo — é o que separa uma suposição de uma evidência reproduzível. Nenhum dado real de cliente foi tocado em nenhum momento; o único artefato escrito em produção durante todo o teste foi esse documento sintético, e ele saiu de lá limpo.

O resultado eleva o problema de uma falha de confidencialidade (F01, leitura) para uma falha de integridade completa (F02): qualquer agente externo, sem credencial nenhuma, pode sobrescrever ou apagar permanentemente registros reais de clientes.


Passo 4 — O ambiente DEV tinha o mesmo buraco (e dados que pareciam reais)

Lembra do segundo projectId encontrado no bundle JS? gglobo-screening-hdg-dev apresentava exatamente a mesma configuração de regras — leitura aberta, sem autenticação — e os dados retornados aparentavam ser reais e recentemente atualizados (não uma massa sintética de testes esquecida).

Ambientes de desenvolvimento tendem a herdar (ou nunca corrigir) as mesmas regras permissivas do projeto que os originou, e times de segurança que auditam só produção deixam esse espelho destrancado.


O que mais apareceu no caminho (achados secundários)

Nenhum deles crítico isoladamente, mas valem registro:

  • Headers de segurança ausentes em screening.globo.com — faltavam X-Frame-Options, Strict-Transport-Security, Referrer-Policy e Permissions-Policy.
  • Múltiplos subdomínios do ecossistema Globo (s3.glbimg.com, p.glbimg.com, s2-screening.glbimg.com, tags.globo.com) sem Content-Security-Policy — relevante especialmente nos hosts que servem JavaScript, onde a ausência de CSP agrava qualquer cenário futuro de comprometimento de supply chain.
  • Um parâmetro suspeito (platform) em horizon-track.globo.com/configs, sinalizado por padrões associados a File Inclusion — classificado como informativo, não validado. O scanner identifica padrão de nome, não comportamento; ficou como recomendação de teste manual (LFI/RFI, path traversal) para uma fase 2.

Resumo dos achados

ID Descrição Severidade Impacto
F01 Leitura irrestrita da coleção users/ — PII completa exposta CRITICAL Confidencialidade
F02 Escrita e deleção irrestrita em produção — sem autenticação CRITICAL Integridade
F03 Ambiente DEV com mesma regra permissiva e dados reais HIGH Confidencialidade
F04 Headers de segurança ausentes (HSTS, CSP, X-Frame-Options) INFO Hardening
F05 Parâmetro platform com pattern de File Inclusion (não validado) INFO Investigação

Por que isso importa

Não é só "achamos um bug". A combinação dos três achados principais forma uma cadeia completa de exploração sem nenhuma barreira técnica:

  1. O bundle JS público entrega projectId e apiKey — reconhecimento zero-esforço.
  2. A Security Rule allow read, write: if true remove qualquer necessidade de autenticação.
  3. O resultado é acesso não autenticado total — leitura, escrita e deleção — sobre PII real de clientes, em produção.

Do ponto de vista de conformidade, isso toca diretamente LGPD (Art. 46/48) e os capítulos V8 (Authorization) e V4 (API and Web Service) do ASVS. Do ponto de vista prático: a correção é cirúrgica — trocar uma linha de Security Rule — e pode ser aplicada sem downtime. É o tipo de achado onde o esforço de exploração é mínimo e o esforço de correção também é, o que só reforça o quanto vale auditar Security Rules do Firebase/Firestore como item recorrente de checklist, não como afterthought.


Remediação

A Globo aplicou as correções e autorizou a publicação. Todas as ações abaixo foram executadas:

CORREÇÕES APLICADAS

Corrigir as Security Rules em produção e desenvolvimento, com a mesma urgência nos dois ambientes

allow read: if request.auth != null && request.auth.uid != null;
allow write: if request.auth.uid == resource.data.uid;

Auditar todas as coleções do projeto — o problema raramente fica isolado em uma única collection

Remover a configuração do projeto DEV do bundle de produção (ambientes diferentes não deveriam compartilhar a mesma base de código de frontend)

Auditar logs de acesso do Firestore em busca de acessos anônimos anteriores à correção

Adicionar os headers de segurança faltantes via CDN/proxy reverso, de forma centralizada para todo o ecossistema *.glbimg.com e *.globo.com


Lições para quem faz (ou defende contra) esse tipo de teste

📦

Bundle JS é superfície de reconhecimento

Toda config de client-side exposta merece ser tratada como ponto de partida de um teste, não como ruído.

🟢

200 OK anônimo é sinal, não coincidência

Vale sempre testar o "óbvio" antes de assumir que já está coberto.

🧹

Provar escrita exige disciplina de limpeza

Documento sintético, ciclo completo de criação-confirmação-remoção-confirmação — sem isso, é alegação, não evidência.

🪞

Ambiente DEV ≠ risco reduzido

Se ele espelha a mesma infraestrutura e carrega dado real, ele é produção com outro nome.

🔇

Nem todo alerta de scanner é finding

O parâmetro platform ficou como informativo justamente por não ter sido validado manualmente — rotular certo o nível de confiança de um achado é parte do trabalho.


Este engajamento foi conduzido de forma autorizada, com evidências registradas e validadas antes da criação de qualquer finding oficial. A Globo corrigiu a vulnerabilidade e autorizou a publicação deste conteúdo.