O Payload Que Ninguém Inspeciona

E aí, dev! Abre a aba Network em qualquer página do Next.js App Router e filtra por Content-Type: text/x-component. O que você vê não é JSON. É o Flight — o protocolo de streaming line-delimited do React, com seu próprio sistema de tipos, sua própria resolução de referências e suas próprias regras para reconstruir comportamento executável no cliente. 🚀

A maioria dos devs React nunca olha. O framework resolve, então ninguém questiona o que essa confiança implica. Aí em dezembro de 2025, o CVE-2025-55182 caiu — um RCE não autenticado de CVSS 10.0 na camada de desserialização do Flight. A CISA colocou no catálogo de vulnerabilidades exploradas. A Sysdig ligou a exploração in-the-wild a atores estado-nação da Coreia do Norte deployando implantes file-less via blockchain Ethereum.

Isso não é um bug de parsing isolado. É sintoma. O Flight reconstrói referências executáveis, componentes lazy-loaded, endpoints RPC e estado assíncrono a partir de um stream de texto. Isso é um sistema de desserialização — e herda todos os riscos clássicos de desserialização.

Security researcher analyzing React Flight protocol payload in browser network tab for deserialization vulnerabilities Programming Illustration

Flight On The Wire: O Sistema de Prefixo $

Cada linha do Flight segue ID:TAG:PAYLOAD. O interessante é o sistema de prefixo $ — quando o parser do cliente encontra uma string começando com $, ele não trata como texto literal. Ele roteia por um caminho de resolução específico em parseModelString.

PrefixoTipoComportamento do Parser
$Model ReferenceResolve para outro chunk no stream
$:Property AccessNavega nas propriedades de um chunk resolvido ($1:user:name)
$FServer ReferenceRepresenta uma Server Action chamável (RPC)
$LLazy ComponentAdia o carregamento do componente
$@Raw ChunkRetorna o wrapper interno Chunk (Thenable)
$BBlob/BinaryDispara o handler de blob

O $: é o crítico para exploração. Ele permite property traversal arbitrário dirigido por dados no stream. Se você já auditou JavaScript para prototype pollution, esse padrão é familiar.

A Gadget Chain do React2Shell

A causa raiz ficava em getOutlinedModel, a função que resolve paths profundos de referências $::

// Loop vulnerável — sem verificação hasOwnProperty
for (key = 1; key < reference.length; key++)
  parentObject = parentObject[reference[key]];

O atacante manda $1:__proto__:constructor:constructor, e o loop sobe de um objeto JSON até Object.prototype, depois Function. Function("código arbitrário")() executa. Sem allowlist, sem check de __proto__.

A partir daí, a chain compõe features legítimas do Flight:

  1. Prototype walk até Function via $:
  2. Raw chunk self-reference via $@0 para pegar um handle interno mutável
  3. Thenable hijack — seta .then para Chunk.prototype.then
  4. Context confusion — sobrescreve _response._formData.get apontando para Function
  5. Trigger via $B0 — blob handler chama Function("comando_atacante")()

Cada passo é uma feature legítima. A vulnerabilidade emerge da composição. Olha só isso! 👀

Node.js server processing streaming React Server Component chunks with prototype pollution attack vectors highlighted Developer Related Image

Defesas, Ranqueadas por Impacto

1. Validação de Schema em Toda Server Action

A defesa mais impactante no nível de aplicação. Valide com Zod ou Valibot no topo de cada Server Action — antes de logar, antes de qualquer lógica. Use .safeParse(), não .parse().

"use server"
import { z } from "zod"

const CommentSchema = z.object({
  postId: z.string().uuid(),
  body: z.string().min(1).max(5000),
})

// Bom: valida o argumento bruto primeiro
export async function addComment(data) {
  const parsed = CommentSchema.safeParse(data)
  if (!parsed.success) return { error: "Invalid input" }
  await db.comments.create(parsed.data)
}

Se sua Server Action não começa com um schema parse, trate como vulnerabilidade. Considere uma regra ESLint customizada que flagra qualquer export "use server" sem validação na primeira instrução.

2. O Pacote server-only

Importe server-only no topo de qualquer arquivo com credenciais de DB ou lógica interna. O build quebra se um Client Component importar transitivamente. Cuidado com barrel files — eles quebram a fronteira silenciosamente.

3. CSRF Além dos Defaults do Framework

O CVE-2026-27978 mostrou que o check Origin vs Host do Next.js tem edge cases (bypass de Origin: null via iframes sandboxed). Seta SameSite=Strict nos cookies de sessão, adiciona tokens CSRF por sessão para operações críticas, e nunca coloca "null" em allowedOrigins.

4. Verifique Sua Versão do Patch

O fix do RCE chegou em React 19.0.1, 19.1.2, 19.2.1. Fixes de DoS precisam de 19.0.4+, 19.1.5+ ou 19.2.4+. Checa teu lockfile:

npm ls react react-dom react-server-dom-webpack

5. Taint API — Guardrail de Dev, Não Fronteira de Segurança

taintObjectReference pega erros honestos (passar user object inteiro pro Client Component). Mas taint rastreia referências, não conteúdo — qualquer spread ou round-trip de serialização quebra. Defesa em profundidade útil, não fronteira primária.

6. WAFs — Redução de Ruído, Não Fronteira

WAFs pegam scanners automatizados. Não param atacantes motivados: buffers de inspeção são ~128KB, então prepende 130KB de padding e o payload passa. Trate WAF como redução de ruído, não segurança.

O Que Ainda Está Estruturalmente Exposto

  • MITM no Flight stream — texto plano, estrutura previsível. Injeta imports $I, server references $F, ou modifica data rows D.
  • Enumeração de Server Actions — server-reference-manifest.json exposto entrega um mapa completo de API para ataques IDOR.
  • Tampering de closures criptografadas — NEXT_SERVER_ACTIONS_ENCRYPTION_KEY estático em multi-server + file read = closures forjadas.
  • Ativação de supply chain — pacotes npm dormentes bundled mas nunca carregados podem ser ativados via referências $I injetadas.

Developer reviewing ReactFlightClient.js source code to understand Flight protocol deserialization sinks Algorithm Concept Visual

Pra Onde Isso Vai

O Flight protocol resolve um problema genuinamente difícil: streaming de árvores de componentes interativas com hidratação progressiva e code splitting server-driven. Funciona. Mas funciona serializando referências executáveis num protocolo de texto e confiando na estrutura do stream dos dois lados.

O patch de hasOwnProperty está correto. Os fixes de DoS estão no lugar. Mas expor property traversal arbitrário e reconstrução de Thenables executáveis através de um protocolo de rede foi um erro de design. O patch trata o sintoma.

Esperar que o parser trate todo edge case não funcionou historicamente — GWT, JSF ViewState, ASP.NET ViewState seguiram o mesmo padrão. Se você shipa Server Components, leia react-client/src/ReactFlightClient.js. Saiba o que seu framework confia em seu nome.

Leitura complementar:

Fonte: Smashing Magazine — Weaponizing and Defending the React Flight Protocol

Este conteúdo foi elaborado com o auxílio de ferramentas de IA, com base em fontes confiáveis, e revisado pela nossa equipe editorial antes da publicação. Não substitui o aconselhamento de um profissional especializado.