CVE-2025-55182

CVE-2025-55182

This vulnerability allows RCE in React Server Functions, e.g. as offered by Next.js through insecure prototype references.

I'm not an expert in React or Next.js, so take all the information here with a grain of salt. Furthermore, I'm still in the analysis process, so what I depict below as "the vulnerability" might only be a small part of the full chain.

Background

React offers Server Functions1, which can be seen as sort of an RPC- over-HTTP. They can be used to fetch data from adjacent peers to ensure low latency, or perform authenticated requests that the client lacks credentials for.

React uses something called the React Flight Protocol2 for serialization of values passed to Server Functions.

The client passes "chunks" to the server, e.g. via form data:

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

As shown, these can have references in between each other. The above payload deserializes to the following on the server:

{ object: 'fruit', name: 'cherry' }

The format itself is a little more intricate and allows for more complex serialization and deserialization, but this provides a basic understanding for the actual vulnerability.

Vulnerability

Until this commit3, when traversing chunks in reference resolving, such as getting the fruitName from chunk 2 in the above example, React didn't verify whether the requested key was actually set on the object. This allowed us to get the object prototype4.

This can be demonstrated with a payload like this:

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

Which deserializes to the function constructor5:

[Function: Function]

When the chunk with ID 0 is not an array but an object, we can set the then key to the function constructor. The object is then returned by the decodeReplyFromBusboy function and awaited by Next.js:

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