
Prova de conceito de exploit para CVE-2025-55182, demonstrando RCE não autenticada em Componentes de Servidor React via desserialização insegura de Ações do Servidor, com etapas de reprodução detalhadas e orientações de mitigação.
Este repositório contém uma reprodução de Prova de Conceito (PoC) da CVE-2025-55182, uma vulnerabilidade crítica de segurança em React Server Components (RSC) que permite execução arbitrária de código não autenticada.
A vulnerabilidade existe na forma como o React Server Components desserializa "Server Actions" a partir de solicitações do cliente. Especificamente, a função requireModule não validava se o nome de exportação solicitado era uma propriedade direta do módulo. Isso permitia que atacantes acessassem a propriedade constructor de funções exportadas, obtendo uma referência ao construtor global Function, que pode ser usado para executar código arbitrário.
Esta PoC usa um ambiente Node.js mínimo para isolar a vulnerabilidade na biblioteca react-server-dom-webpack, apenas para garantir que o exploit demonstre o bug na biblioteca em si, NÃO uma má configuração em um framework.
npmnpm install
[!NOTE] O
package.jsonestá fixado na versão vulnerável19.0.0.
Este script configura um servidor HTTP bruto que usa o runtime React vulnerável para decodificar requisições.
# tty1
node --conditions react-server server.js
Em um terminal separado, execute o exploit. Isso envia um payload Flight malicioso para o servidor.
# tty2
node exploit.js id
Você deve ver a saída do comando retornada na resposta:
Saída Esperada:
Response: uid=0(root) gid=0(root) groups=0(root)
Por que a vulnerabilidade aconteceu?
A função requireModule em ReactFlightDOMServerNode.js basicamente confiava em qualquer name que o cliente enviasse. Ela fazia moduleExports[metadata[NAME]] sem verificar se aquela propriedade era realmente destinada a ser exposta. Então, se o cliente dissesse "cara, quero essa propriedade", o servidor simplesmente respondia "claro! aqui está, amigão".
Por que permitir que as pessoas acessem qualquer propriedade é uma má ideia?
Porque basicamente permite que qualquer pessoa alcance a cadeia de protótipos, até mesmo o constructor, o que é super perigoso. Se o módulo exporta uma função (como module.exports = () => {}), então seu constructor é literalmente o construtor global Function.
Por que obter o construtor Function significa RCE?
Uma vez que um atacante obtém o construtor Function, ele pode abusar do recurso "Bound Server Action". Ele associa uma string contendo JavaScript malicioso a ele (basicamente transformando-o em new Function("código maligno")). E quando isso é executado, o servidor executa qualquer código que ele colocar.
Por que o React realmente executaria essa função maliciosa?
Porque Server Actions podem ser acionadas por ID. Se o atacante criar um payload com um Action ID que aponte para sua referência module#constructor, o React resolve como uma ação normal e a executa. Essa "ação" é na verdade sua função maliciosa.
Por que nada disso foi validado?
O sistema simplesmente assumia que id e name dos metadados da Server Reference sempre se refeririam a exports válidos definidos pelo desenvolvedor. Não havia nenhuma verificação de segurança como hasOwnProperty para garantir que a propriedade solicitada era realmente um export real e não algo herdado da cadeia de protótipos.
server.js em vez de Next.js?Eu uso um server.js bruto (e um helper webpack-runtime.js) para configurar manualmente o runtime React Server Components. Isso nos permite:
module.exports = fn). Um bundler real pode mudar como os exports são encapsulados, dependendo de sua configuração.react-server-dom-webpack, não no Next.js.react-server-dom-webpack assume que está rodando dentro de um bundle Webpack. Nosso webpack-runtime.js fornece os globais que ele espera (__webpack_require__, __webpack_chunk_load__).
Isso não é uma simulação da vulnerabilidade, é apenas fornecer o runtime mínimo que a biblioteca precisa para funcionar.Houve discussão sobre "PoCs inválidas" que só funcionam se o desenvolvedor expõe propositalmente coisas perigosas como child_process.exec.
Esta PoC não é uma dessas. Ela funciona em uma configuração normal e segura.
A função exposta é inofensiva
O aplicativo expõe uma simples função updateProfile que apenas retorna uma string e nada suspeito, sem comandos shell.
O exploit escapa completamente dessa função
A vulnerabilidade permite que o atacante ignore o export seguro e vá direto para updateProfile.constructor, que é o construtor global Function.
O problema central é o acesso à propriedade
O React NÃO deveria ter permitido o acesso a .constructor. O desenvolvedor não pretendia expor o construtor Function, em vez disso, a desserialização insegura fez isso por ele.
O único requisito real é que o módulo exporte uma função diretamente (module.exports = fn), o que é super comum em CommonJS e muitas configurações de bundler.
O payload em exploit.js cria uma mensagem React Flight com três chunks:
id: "user-profile-action#constructor", significando "me dê o constructor".bound: aponta para o Chunk 2, que contém os argumentos.["console.log('nice try, diddy!')"]: a string de código malicioso.Quando o React desserializa isso:
user-profile-action..constructor => obtém o Function global.new Function("console.log('nice try, diddy!')")E isso é RCE!
Atualize imediatamente para as versões corrigidas:
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1O patch introduz verificações de hasOwnProperty para impedir o acesso a propriedades herdadas e restringe uploads de arquivos base64.
Se você executar esta PoC contra uma versão corrigida, o servidor irá travar ou gerar erro com:
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
Isso confirma que o exploit falhou ao acessar a propriedade constructor (ela retornou undefined em vez de Function), e portanto a chamada subsequente .bind falhou.
Este código é apenas para fins educacionais e de teste. Não use este exploit contra sistemas que você não possui ou sem permissão explícita para testar.
Publicado sob DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.