Envio de registos, alertas e telemetria através de um diodo de dados

Descubra como
Utilizamos inteligência artificial para as traduções dos sítios e, embora nos esforcemos por garantir a exatidão, estas podem nem sempre ser 100% precisas. Agradecemos a sua compreensão.

React2Shell (CVE-2025-55182): Vulnerabilidade crítica de execução remota de código nos Server do React

Por Loc Nguyen, Penetration Test Team Lead
Partilhar esta publicação

A CVE-2025-55182 é uma vulnerabilidade crítica de execução remota de código pré-autenticação nos React Server , com uma pontuação CVSS de 10,0 — a classificação de gravidade mais elevada possível. No âmbito do Programa de BolsasOPSWAT , os nossos bolseiros realizaram uma análise técnica abrangente desta vulnerabilidade, examinando a sua causa principal no protocolo de deserialização do React Flight, a cadeia de exploração completa e o seu impacto generalizado no ecossistema web moderno. Este blogue apresenta as nossas conclusões, juntamente com orientações práticas para os defensores.

O React tornou-se uma das bibliotecas front-end mais utilizadas em todo o mundo, estando na base de uma parte significativa das mobile web e mobile modernas. Os inquéritos aos programadores do Stack Overflow classificam consistentemente o React entre as principais estruturas web, com uma adoção que ultrapassa os 40% dos programadores profissionais a nível global. Paralelamente a este crescimento, a equipa do React introduziu Server React Server (RSC) como uma funcionalidade central do React 19 — uma mudança de paradigma que transfere a lógica de renderização do cliente para o servidor, permitindo um desempenho otimizado e uma integração mais estreita entre o código do lado do servidor e do lado do cliente.

No entanto, esta evolução arquitetónica introduziu uma nova superfície de ataque crítica. A 29 de novembro de 2025, o investigador de segurança Lachlan Davidson comunicou uma vulnerabilidade na lógica de deserialização do lado do servidor do React ao programa Bug Bounty da Meta. Divulgada publicamente a 3 de dezembro de 2025, com o código CVE-2025-55182, a falha permite a execução remota de código sem autenticação através de uma única solicitação HTTP manipulada. Classificada como CWE-502 (Deserialização de Dados Não Confiáveis), a vulnerabilidade não requer autenticação, interação do utilizador nem configuração especial da aplicação — uma implementação padrão do tipo «create-next-app» criada para produção é imediatamente explorável.

Figura 1: CVE-2025-55182 (fonte: NVD)

O impacto foi imediato e grave. Em menos de 48 horas após a divulgação pública, foram observadas várias campanhas de exploração em circulação. De acordo com a The Shadowserver Foundation, foram identificados mais de 77 000 endereços IP públicos como potencialmente vulneráveis. A telemetria da Cloudflare registou mais de 582 milhões de tentativas de exploração na semana seguinte à divulgação, com uma intensidade de ataque média superior a 3.500 IPs de origem únicos por hora e um pico de 16.585 IPs de ataque simultâneos. A Wiz Research informou que 39% dos ambientes na nuvem continham instâncias vulneráveis. A CISA adicionou a vulnerabilidade ao seu catálogo de Vulnerabilidades Exploradas Conhecidas (KEV) a 5 de dezembro de 2025.

Os autores das ameaças agiram com notável rapidez e diversidade. A Trend Micro documentou várias campanhas — incluindo as campanhas das botnets «emerald» e «nuts» — que utilizaram beacons do Cobalt Strike, implantes Sliver, o agente de monitorização Nezha, túneis Fast Reverse Proxy (FRP) e uma nova carga útil denominada «Secret-Hunter», que aproveita ferramentas de código aberto para a recolha de credenciais, como o TruffleHog e o Gitleaks. Threat Intelligence Google Threat Intelligence identificou grupos de ameaças distintos ligados à China (UNC6600, UNC6586, UNC6588, UNC6603) a utilizar ferramentas especializadas — incluindo o tunelador MINOCAT, o downloader SNOWLIGHT, o backdoor COMPOOD e o backdoor HISONIC — a par de agentes ligados ao Irão e de grupos com motivações financeiras que conduzem campanhas de mineração de criptomoedas. A AWS documentou grupos ligados à China a experimentar código de exploração já a 4 de dezembro, antes de o código de prova de conceito completo estar disponível publicamente.

Sobre Server React Server

O React é uma biblioteca JavaScript para a criação de interfaces de utilizador, mantida pela Meta e por uma vasta comunidade de código aberto. Server React Server (RSC), introduzidos com o React 19, representam uma mudança fundamental na forma como as aplicações React lidam com a renderização. Ao contrário dos componentes de cliente tradicionais, que são executados inteiramente no navegador, os componentes de servidor são executados no servidor, produzindo uma representação serializada da interface do utilizador que é transmitida para o cliente. Este design reduz a quantidade de JavaScript enviada para o navegador, melhora as métricas de tempo de interação e permite o acesso direto a recursos do lado do servidor, tais como bases de dados e sistemas de ficheiros.

Figura 2: React Server (RSC)

O RSC baseia-se num protocolo de serialização personalizado denominado «Flight» para codificar e transmitir dados entre o cliente e o servidor. Quando um cliente invoca uma Server (anteriormente conhecida como Server ), o navegador agrupa os argumentos da função numa solicitação HTTP estruturada, utilizando o formato Flight. O servidor deserializa esta carga útil, executa a função solicitada e transmite o resultado de volta ao cliente. Esta forte interdependência entre cliente e servidor (embora arquitetonicamente elegante) significa que qualquer falha na lógica de deserialização pode ter consequências imediatas e catastróficas, como demonstra o CVE-2025-55182.

A vulnerabilidade afeta não só o próprio React, mas todo o ecossistema de frameworks construídos com base nele. O Next.js (que recebeu um aviso separado, CVE-2025-66478, posteriormente rejeitado por ser uma duplicata), o React Router, o Waku, o plugin RSC do Parcel, o plugin RSC do Vite e o RedwoodSDK são todos afetados. Mesmo as aplicações que não definem explicitamente Server podem estar vulneráveis se o suporte a RSC estiver ativado no framework.

Contexto técnico

Antes de analisarmos a vulnerabilidade, é importante referir três conceitos fundamentais que estão na base da cadeia de exploração: o comportamento do comando `await` do JavaScript com objetos `thenable`, a percussão da cadeia de protótipos e o modelo de dados baseado em blocos do protocolo React Flight.

O `await` e os objetos `Thenable` do JavaScript

O operador `await` suspende a execução de uma função assíncrona até que a expressão aguardada seja resolvida. Quando o `await` encontra uma Promise nativa, aguarda a resolução e devolve o valor cumprido. No entanto, o `await` não requer uma Promise nativa — qualquer objeto com um método `.then()`, conhecido como «thenable», é tratado como uma construção semelhante a uma Promise.

Quando o `await` encontra um objeto `thenable`, invoca o método `.then()` desse objeto, passando as callbacks `resolve` e `reject` fornecidas pelo sistema. O valor passado para `resolve` torna-se o resultado da expressão `await`. Fundamentalmente, se o valor resolvido for ele próprio um thenable, o método .then() desse objeto aninhado é chamado recursivamente até que se chegue a um valor primitivo ou a uma Promise resolvida. Este comportamento de resolução recursiva é fundamental para a exploração da vulnerabilidade CVE-2025-55182.

Percurso da cadeia de protótipos

Todos os objetos JavaScript mantêm uma ligação interna ao seu protótipo, acessível através da propriedade __proto__. Quando se acede a uma propriedade num objeto, o motor JavaScript verifica primeiro as propriedades do próprio objeto. Se a propriedade não for encontrada, o motor percorre a cadeia de protótipos — subindo por cada ligação __proto__ — até que a propriedade seja encontrada ou a cadeia termine em undefined.

Um atacante pode explorar este mecanismo de herança para aceder a propriedades fora do âmbito pretendido de um objeto. Ao incluir __proto__ nos caminhos de acesso às propriedades, um atacante pode aceder a métodos internos e construtores que a aplicação nunca pretendia expor. Em JavaScript, a expressão obj.__proto__.constructor.constructor resulta no construtor global Function, que pode criar e executar funções arbitrárias a partir de uma entrada de cadeia de caracteres.

O React Flight Protocol e o modelo de dados baseado em blocos

When a client invokes a Server Function, the browser sends an HTTP POST request with a multipart/form-data body. Each form field contains a numbered “chunk” of serialized data. The Flight protocol uses special string prefixes to encode data types: $<id> references the resolved value of another chunk, $@<id> references the raw chunk object itself, $W<id> represents a Set, $K<id> represents FormData, and $B<id> triggers the blob handler.

Considere uma Server definida da seguinte forma:

Figura 3: Exemplo Server

O pedido HTTP correspondente contém vários campos de formulário, cada um composto por uma chave e um valor: o campo 0 contém a matriz de argumentos com referências como "$W1" e "$K2", enquanto os campos 1 e 2_* contêm os dados a que essas referências remetem. O servidor processa cada campo à medida que este chega, armazenando os resultados intermédios em objetos denominados «chunks».

Figura 4: Pedido HTTP «multipart/form-data» correspondente gerado ao invocar a Server de exemplo

Um chunk é um objeto thenable com quatro propriedades principais: status (o estado de resolução), value (os dados armazenados), reason (informações sobre o erro) e _response (uma referência ao objeto de resposta pai). Quando o servidor encontra o chunk await, o método .then() do chunk é invocado. Se o status do chunk for INITIALIZED, a callback de resolução recebe chunk.value. Se o status for PENDING, BLOCKED ou CYCLIC, as callbacks são enfileiradas para execução posterior.

Figura 5: Estado do objeto Chunk durante a desserialização

O chunk 0 representa normalmente a matriz de argumentos da Server invocada. Depois de todos os campos do formulário terem sido recebidos e todas as referências internas terem sido resolvidas, chunk_0.value contém a matriz de argumentos totalmente montada, que é então passada para a função de destino.

Tratamento de pedidos de ponta a ponta (Next.js → Deserialização do React Flight)

O texto a seguir descreve como o Next.js processa um pedido Server recebido em condições normais, desde a camada HTTP até ao motor de deserialização do React Flight.

Figura 6: Visão geral do tratamento de pedidos Server do Next.js

Função handleAction() - Next.js

Quando uma Server é invocada, o pedido entra na função `handleAction`. Esta função valida os metadados, verifica os cabeçalhos e os tokens CSRF e confirma se o pedido é uma ação de recuperação válida. É então criado um fluxo chamado busboyStream para analisar o corpo do formulário multipart. A função decodeReplyFromBusboy vincula emissores de eventos a este fluxo, acionando as funções de tratamento de deserialização ServerReact Serverà medida que os dados brutos são recebidos. O valor de retorno de decodeReplyFromBusboy é chunk_0; o operador await resolve-o e passa o seu valor montado para a Server invocada.

Figura 7: função handleAction

A função getChunk devolve o chunk correspondente a um determinado ID. Se esse chunk ainda não existir, cria um ResolvedModelChunk (se já houver dados em response._formData) ou um PendingChunk (se ainda não tiverem chegado dados para esse ID).

Figura 8: função getChunk

Quando a função `decodeReplyFromBusboy` devolve `chunk_0`, o bloco continua no estado PENDING. O operador `await` chama `chunk_0.then()` e armazena temporariamente as callbacks `resolve` e `reject` em `chunk_0.value` e `chunk_0.reason`. Estas callbacks são reativadas pela função `wakeChunk` assim que a resolução da referência estiver concluída.

Figura 9: função wakeChunk

Processo de deserialização - React Server

Quando o busboyStream recebe um campo de dados brutos completo, aciona o emissor de eventos «field», invoca a função resolveField e inicia a deserialização — convertendo os dados brutos do formulário em objetos JavaScript totalmente construídos. As seguintes funções controlam este processo.

resolveField(resposta, chave, valor)

Figura 10: função resolveField

A chave e o valor são anexados a response._formData. A função recupera então o fragmento correspondente ao ID que coincide com a chave. Se esse fragmento já existir, a função resolveModelChunk é chamada para o reconstruir. Esta resolução diferida é necessária porque um valor pode conter referências a campos cujos dados brutos ainda não chegaram; nesse caso, o React Server um PendingChunk com callbacks personalizados de resolução e rejeição para tratar essas referências posteriormente.

resolveModelChunk(chunk, valor, id)

Figura 11. Função resolveModelchunk

resolveModelChunk cria um ResolvedModelChunk com o estado RESOLVED_MODEL e injeta os dados brutos. Em seguida, reconstrói o chunk através de initializeModelChunk e chama wakeChunk para acionar quaisquer callbacks de resolução e rejeição em fila, concluindo a resolução do objeto ou da referência.

initializeModelChunk(chunk)

Figura 12: função initializeModelChunk

A função `initializeModelChunk` altera o estado do chunk para `CYCLIC` — indicando que a resolução de referências está em curso — e inicia a deserialização. Ela constrói um objeto JavaScript bruto a partir de `chunk.value` utilizando `JSON.parse` e, em seguida, passa esse objeto para a função `reviveModel`.

reviveModel(resposta, objetoPai, chavePai, valor, referência)

Figura 13: Função reviveModel

O reviveModel processa recursivamente cada componente do objeto analisado. Quando encontra um valor de cadeia de caracteres, chama a função parseModelString para o processar.

parseModelString(resposta, objeto, chave, valor, referência)

Figura 14. Função parseModelString

A função `parseModelString` analisa o prefixo da string para lidar com diferentes tipos de codificação. No caso de referências que começam por $, a função `getOutlinedModel` é chamada para resolver a referência entre blocos.

getOutlinedModel(resposta, referência, objetoPai, chave, mapa)

Figura 15: função getOutlinedModel

O getOutlinedModel divide a referência no delimitador «:» para formar um caminho de acesso à propriedade e, em seguida, percorre esse caminho no objeto chunk de destino para devolver o valor referenciado. Conforme detalhado na Análise de Vulnerabilidades abaixo, a ausência de validação destes nomes de propriedades é precisamente o ponto onde reside a vulnerabilidade.

Análise de vulnerabilidade

Causa principal

CVE-2025-55182 originates from insufficient input validation in the getOutlinedModel() function within React’s server-side Flight reply handler (ReactFlightReplyServer.js). When a chunk reference includes a property path - such as $<id>:<prop1>:<prop2> - the function resolves it by traversing the specified properties on the target chunk object, computing the result as chunk[prop1][prop2].

Figura 16: Validação insuficiente dos dados de entrada na função getOutlinedModel()

A falha crítica reside no facto de estes nomes de propriedades nunca serem validados. O servidor não verifica se as propriedades solicitadas são propriedades próprias do objeto ou propriedades herdadas do protótipo. Um invasor pode, portanto, incluir __proto__ no caminho da propriedade para percorrer a cadeia de protótipos e alcançar métodos internos que nunca deveriam ser acessíveis a partir de entradas controladas pelo utilizador. Por exemplo, a referência $1:__proto__:then resolve-se em Chunk.prototype.then — uma função que o invasor pode então invocar com argumentos controlados.

Figura 17: Protótipo de traversal através de entrada maliciosa

Código vulnerável

A cadeia de exploração aproveita dois percursos de código distintos na lógica de deserialização Flight do React.

O primeiro é o Chunk.prototype.then, que determina o comportamento dos chunks enquanto thenables. Quando o operador await é aplicado a um chunk no estado INITIALIZED, é chamada a função resolve(chunk.value). Se chunk.value for, por sua vez, um thenable (um objeto com um método .then() ), o operador await invoca recursivamente chunk.value.then(). Esta resolução recursiva é o mecanismo através do qual um atacante redireciona a execução para uma função arbitrária.

O segundo é o manipulador do prefixo $B (blob) dentro da função parseModelString():

Figura 18: $B (blob) na função parseModelString

No caso $B, a função chama response._formData.get(response._prefix + id). Tanto _formData.get como _prefix são propriedades do objeto _response armazenado no interior do chunk. Ao controlar estas propriedades através da percussão da cadeia de protótipos, um atacante pode redirecionar esta chamada para invocar o construtor global Function com código arbitrário como argumento.

Exploração

Through prototype chain traversal, an attacker reaches the global Function constructor via the path <any_object>.constructor.constructor. Because Chunk.prototype.then is a function, the path $1:constructor:constructor resolves to the global Function constructor, which accepts a string and returns a callable function containing that code.

Figura 19: Construtor da função global

Para demonstrar o potencial impacto no mundo real, os nossos investigadores criaram uma carga útil de prova de conceito em consonância com a análise documentada de forma independente por várias equipas de investigação em segurança. A exploração funciona em duas fases:

Fase 1 - Construir o fragmento falso:

The object delivered in field 0 acts as a fake chunk. Its then property is set to Chunk.prototype.then via the reference path $1:__proto__:then, allowing the Flight deserialization engine to invoke prototype-level behavior on this attacker-constructed object. The _response._formData.get property is pointed at the global Function constructor via $1:constructor:constructor, and _response._prefix is set to the malicious JavaScript code. The value field contains the string {"then": "$B0"}, instructing the blob handler to invoke itself on the same chunk when resolved. The status field is set to resolved_model so that initializeModelChunk is triggered when .then() is called, causing value to be parsed and the blob handler to fire.

Como o campo 1 ainda não foi recebido nesta altura, o React Server cria Server callbacks de resolução e rejeição para tratar da referência pendente.

Fase 2 - Resolução do gatilho:

Assim que o campo 1 — que contém "$@0", uma referência bruta ao chunk 0 — é entregue, o chunk pendente é resolvido e aponta diretamente para o chunk falso. Isto aciona o wakeChunk, que processa as callbacks em fila e inicia a percussão da cadeia de protótipos durante a resolução da referência. Assim que o chunk falso estiver totalmente resolvido, o wakeChunk é chamado novamente. Como o callback de resolução para o chunk falso é a função de resolução implícita do Node.js, este invoca o método .then() do chunk e resolve o seu valor — acabando por construir e executar o código malicioso injetado através do construtor Function.

A exploração completa requer apenas uma única solicitação HTTP:

Figura 20. Pedido malicioso

Replacing {{COMMAND}} with any JavaScript code executes it on the server. The reason: -1 field prevents a toString() error during processing. The Next-Action header may contain any arbitrary value - even x - because the vulnerable deserialization occurs before the server validates the requested Server Function. This is what makes the vulnerability pre-authentication: the payload is processed during the deserialization phase, before any application-level authentication or authorization logic is reached.

Caso a exploração seja bem-sucedida, um atacante obtém acesso total ao contexto de execução do Node.js no servidor, incluindo acesso ao `child_process` para a execução de comandos de shell, variáveis de ambiente que contêm credenciais de bases de dados e API , o sistema de ficheiros local e pontos de acesso de metadados na nuvem que permitem o movimento lateral.

Prova de Conceito

Os nossos colegas reproduziram a vulnerabilidade num ambiente de laboratório controlado, utilizando uma aplicação Next.js padrão gerada com o create-next-app e preparada para produção — sem quaisquer alterações à configuração predefinida. A reprodução confirmou que a carga útil de exploração de uma única solicitação descrita acima permite a execução remota de código de forma fiável.

Figura 21. Aplicação web Next.js vulnerável
Figura 22. O atacante compromete o servidor Next.js vulnerável

A demonstração controlada revelou que um atacante com acesso à rede de um servidor Next.js vulnerável pode executar código Node.js arbitrário — incluindo a criação de um shell reverso através de `child_process.exec()`, a leitura de variáveis de ambiente e o acesso ao sistema de ficheiros local — sem fornecer quaisquer credenciais nem acionar quaisquer verificações de autenticação ao nível da aplicação. O valor arbitrário aceite para o cabeçalho Next-Action confirma ainda mais a natureza pré-autenticação da falha: o servidor processa e deserializa a carga útil antes de realizar qualquer pesquisa de ação ou verificação de autorização.

Mitigação

A equipa do React lançou correções a 3 de dezembro de 2025 — no mesmo dia em que a vulnerabilidade foi divulgada publicamente. As versões corrigidas estão disponíveis como React 19.0.1, 19.1.2 e 19.2.1. O patch adiciona uma validação rigorosa de propriedades em getOutlinedModel() e reviveModel(), bloqueando explicitamente a resolução de propriedades de protótipo herdadas — incluindo __proto__, constructor e prototype — a partir de caminhos de referência controlados pelo utilizador em cargas úteis Flight.

As organizações devem tomar as seguintes medidas imediatas:

  1. Atualize os pacotes React para uma versão corrigida (19.0.1, 19.1.2 ou 19.2.1) executando o comando npm install react-server-dom-webpack@latest, react-server-dom-parcel@latest ou react-server-dom-turbopack@latest, conforme o caso.
  2. Atualizar as dependências das estruturas - O Next.js, o React Router, o Waku e outras estruturas afetadas lançaram as correções correspondentes. Consulte o comunicado da equipa do React para obter informações sobre os procedimentos de atualização específicos para cada versão.
  3. Não confie exclusivamente nas medidas de mitigação dos fornecedores de alojamento — embora fornecedores como a Vercel tenham implementado regras WAF temporárias após a divulgação, estas são medidas provisórias e não substituem a aplicação de correções aos pacotes subjacentes.
  4. Analise os registos do servidor para detetar pedidos POST com cabeçalhos Next-Action e corpos multipart/form-data que contenham os padrões $@ ou __proto__, e verifique se existem chamadas inesperadas de child_process ou execSync nos registos da aplicação.

Mitigação com OPSWAT

OPSWAT , uma tecnologia proprietária da plataforma MetaDefender™, oferece a visibilidade e o controlo necessários para proteger contra vulnerabilidades como a CVE-2025-55182. Uma vez que esta falha reside em pacotes npm de código aberto (react-server-dom-webpack, react-server-dom-parcel, react-server-dom-turbopack), as organizações devem primeiro estabelecer um inventário completo dos locais onde estes componentes estão implementados na sua infraestrutura, antes de ser possível uma correção eficaz.

Figura 23. O SBOM deteta o CVE-2025-55182

OPSWAT gera um inventário abrangente de todos os componentes de software, bibliotecas, contentores e dependências em uso. Ao analisar aplicações ou imagens de contentores que incluem pacotes React vulneráveis, o sistema assinala automaticamente o CVE-2025-55182 como «Crítico» e fornece orientações sobre as versões corrigidas disponíveis, permitindo que as equipas de segurança estabeleçam prioridades e tomem medidas corretivas antes que ocorra qualquer exploração.

OPSWAT está disponível tanto no MetaDefender — para a análise de aplicações individuais e imagens de contentores — como MetaDefender Software Chain™ — para uma visibilidade ao nível do pipeline ao longo de todo o ciclo de vida do desenvolvimento. Em conjunto, permitem às equipas de segurança:

  • Localize rapidamente os componentes vulneráveis - Identifique imediatamente quais as aplicações e os contentores que incluem os pacotes react-server-dom-* afetados em versões vulneráveis, garantindo que nenhuma implementação seja esquecida.
  • Garantir a aplicação proativa de correções - Monitorizar continuamente as dependências de código aberto para detetar pacotes desatualizados ou inseguros à medida que são publicados novos alertas, reduzindo assim o período de exposição.
  • Garantir a conformidade e a transparência da cadeia de abastecimento - Cumprir os requisitos regulamentares mantendo um registo auditável de todos os componentes de software e das suas vulnerabilidades conhecidas.

A rapidez e a dimensão da exploração da vulnerabilidade CVE-2025-55182 — mais de 582 milhões de tentativas só na primeira semana — evidenciam por que razão a aplicação reativa de correções já não é suficiente. Saber o que se tem, onde é executado e quando fica vulnerável é a base de uma defesa proativa. Essa visibilidade começa com a SBOM.

Referências

Mantenha-se atualizado com OPSWAT!

Inscreva-se hoje para receber as últimas actualizações da empresa, histórias, informações sobre eventos e muito mais.