
Análise técnica detalhada da CVE-2025-55182, uma RCE crítica não autenticada no Next.js + React 19.0.0. Documenta a jornada de pesquisa, análise do patch, travessia de protótipos e o sink de desserialização de Blob que permite a exploração completa sem gadgets específicos da aplicação.
Este documento descreve meu processo pessoal de pesquisa sobre o CVE-2025-55182, incluindo descobertas confirmadas e experimentos que realizei. Este é um relato honesto da minha investigação, incluindo suposições iniciais incorretas e o eventual avanço.
Após uma investigação extensa sobre o CVE-2025-55182 (CVSS 10.0), minha conclusão inicial foi que RCE automático não havia sido demonstrado publicamente e que a exploração exigia gadgets específicos da aplicação. Esta conclusão estava incorreta.
Em 5 de dezembro de 2025, após receber insights adicionais de outro pesquisador independente no X (@maple3142), reproduzi com sucesso RCE completo não autenticado no Next.js vanilla, sem exigir nenhuma vulnerabilidade de código específica da aplicação.
Meu foco inicial foi na primeira alteração do patch do React 19.0.1:
Vulnerável (19.0.0):
return fn.bind.apply(fn, [null].concat(_ref));
Corrigido (19.0.1):
if (Array.isArray(promiseValue)) {
promiseValue = promiseValue.slice(0);
} else {
promiseValue = [];
}
Presumi que o caminho de ataque era através de fn.bind.apply() com objetos maliciosos em vez de arrays. Consegui demonstrar injeção de argumentos em Server Actions usando $ACTION_REF_ com bound controlado pelo atacante:
curl -X POST http://localhost:9000/ \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"<ACTION_ID>","bound":["; id #","/etc/passwd"]}'
Resultado: Os argumentos foram injetados com sucesso na Server Action. No entanto, isso só leva a RCE se a função alvo usar esses argumentos de forma insegura.
Uma análise mais aprofundada do patch revelou outra alteração importante em getOutlinedModel():
Vulnerável:
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
Corrigido:
if (hasOwnProperty.call(value, name)) {
value = value[name];
}
Esse comportamento vulnerável permitia a travessia da cadeia de protótipos usando referências como:
$1:__proto__:constructor:constructor
Durante a pesquisa, testei um objeto thenable contendo uma propriedade .then:
{"then": "$1:__proto__:constructor:constructor"}
Quando o JavaScript processa isso através de await:
.then e trata o objeto como uma Promiseobj.then(resolve, reject)then resolver para Function.constructor, o JavaScript tenta executar Function(resolve, reject)Resultado observado:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>)
Quando Function.constructor é invocado como:
Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Function tenta analisar isso como código → SyntaxError
Os argumentos resolve e reject são sempre as funções nativas da Promise. Function tenta interpretar o primeiro argumento como código-fonte, o que é JavaScript inválido.
Foi aqui que minha pesquisa parou. Concluí que controlar os argumentos de Function.constructor era impossível sem um gadget específico da aplicação.
Após publicar minhas descobertas iniciais, outro pesquisador independente apontou-me uma peça crítica que eu havia deixado passar: o sink de desserialização $B (Blob).
No código compilado do servidor React Flight (não visível nas fontes TypeScript), existe:
case "B":
return response._formData.get(response._prefix + id);
Localização:
[email protected]cjs/react-server-dom-webpack-server.node.unbundled.development.js[email protected]/dist/compiled/react-server-dom-webpack/Esse código permite que o React chame response._formData.get() com valores derivados de entrada controlada pelo atacante, sem validação.
| Abordagem | Argumentos para Function.constructor | Resultado |
|---|---|---|
| Thenable (Fase 3) | resolve, reject (funções nativas) | ❌ SyntaxError |
| Blob + Response Envenenada | _prefix (string controlada pelo atacante) | ✅ RCE |
Ao combinar:
$1:__proto__:then → Chunk.prototype.then)_response envenenado com:
_formData.get definido como Function.constructor_prefix definido como código JavaScript arbitrário$BO manipulador case "B": executa:
Function.constructor("<código do atacante>" + id)
Isso contorna completamente a limitação da ligação de argumentos.
PoCs populares no GitHub que alegam RCE usam Action IDs como:
"child_process#execSync""vm#runInThisContext"Estes são falsos. O Next.js só aceita Action IDs definidos pela aplicação. IDs inválidos produzem:
TypeError: Cannot read properties of undefined (reading 'workers')
No entanto, a exploração real não exige Action IDs falsos. Qualquer ID de Server Action válido funciona.
Testei essa cadeia de exploração em uma aplicação mínima Next.js 15.0.3 + React 19.0.0 com apenas:
async function myAction(data) {
"use server";
console.log("Server Action called with:", data);
return { success: true, received: data };
}
Resultado: RCE completo confirmado. A aplicação não continha código inseguro, nenhum eval, nenhum execSync, nenhum gadget.
| Aspecto | Descoberta |
|---|---|
| Autenticação necessária? | ❌ Não |
| Gadget da aplicação necessário? | ❌ Não |
| Funciona no Next.js vanilla? | ✅ Sim |
| Número de requisições necessárias | 1 POST |
| Versões afetadas | Next.js ≤15.0.4 + React 19.0.0 |
| Pontuação CVSS | 10.0 (justificada) |
Em 5 de dezembro de 2025, o sink $B ainda existe no Next.js 15.0.5 (a versão supostamente corrigida).
Verificação:
$ npm pack [email protected]
$ tar -xzf next-15.0.5.tgz
$ grep -A5 'case "B":' package/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
Resultado:
case "B":
return response._formData.get(response._prefix + obj);
O código é idêntico ao da versão vulnerável.
Devido à descoberta de que a vulnerabilidade pode não estar totalmente corrigida nas versões "corrigidas" declaradas, estou retendo o payload completo de prova de conceito pendente de verificação com as equipes de segurança da Vercel e da Meta.
Os detalhes técnicos fornecidos neste documento são suficientes para entender o mecanismo da vulnerabilidade, mas estão intencionalmente incompletos para prevenir exploração imediata.
| Técnica | Status |
|---|---|
Injeção de argumentos via bound | ✅ Confirmado (impacto limitado) |
| Travessia de protótipo | ✅ Confirmado |
Acesso a Function.constructor via thenable | ✅ Confirmado (mas não explorável sozinho) |
| Detecção de versões vulneráveis | ✅ Confirmado |
| Problema | Impacto |
|---|---|
O sink de desserialização $B (Blob) | ❌ Crítico - permite controle de argumentos |
| Examinar código compilado vs. código-fonte | ❌ O sink só existe na saída compilada |
| Mecanismo de envenenamento do objeto de resposta | ❌ Permite contornar todas as proteções |
O CVE-2025-55182 é explorável para RCE completo não autenticado em aplicações Next.js vanilla.
$B de um pesquisador independente$B ainda aceita objetos _response arbitráriosNext-Action incomuns$@, __proto__, $B$B foi fornecido por um pesquisador no X (@maple3142)Última atualização: 5 de dezembro de 2025