Share
## 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