Share
## https://sploitus.com/exploit?id=7522912E-3A59-5820-B05E-8EFC734A977B
# Node.js RCE Mitigation: DevOps as the Last Line of Defense

This project demonstrates a critical Remote Code Execution (RCE) vulnerability in a Next.js application (specifically via Server Actions) and how **infrastructure hardening** effectively neutralizes the attack even when the code vulnerability remains.

It contrasts a standard "Unsafe" deployment with a hardened "Safe" deployment using **Distroless** images and **Read-Only Filesystems**.

## ๐Ÿ›ก๏ธ The Concept: "Defense in Depth"

Software vulnerabilities are inevitable. When code fails, your infrastructure must prevent the attacker from expanding their foothold.

### The Vulnerability
A **Critical RCE (CVE-2025-55182, aka React2Shell)** exists in the React Server Components (RSC) implementation used by Next.js.
*   **CVSS**: 10.0 (Critical)
*   **Root Cause**: Insecure deserialization of the "Flight" protocol allows an attacker to manipulate internal objects (via prototype pollution or similar mechanisms) during Server Action processing.
*   **Impact**: This allows arbitrary code execution (like `spawnSync`) without authentication.

### The Attack Vectors
1.  **Living off the Land (LotL)**: Using tools already present in the OS (`curl`, `wget`, `ls`, `cat`) to steal secrets or download malware.
    *   *Mechanism*: The exploit uses Node.js `child_process.spawnSync()`. This executes binaries **directly**, without needing a shell (`/bin/sh`).
2.  **Bring Your Own Land (BYOL)**: If standard tools are missing, the attacker uploads their own binary (e.g., a compiled Go executable), marks it executable (`chmod +x`), and runs it.

---

## ๐Ÿ—๏ธ Architecture Comparison

| Feature | โŒ Unsafe Environment (Port 3001) | โœ… Safe Environment (Port 3000) |
| :--- | :--- | :--- |
| **Base Image** | `node:20-alpine` (Contains `ls`, `curl`, `wget`, etc.) | `gcr.io/distroless/nodejs20-debian12` (No shell, no tools) |
| **Filesystem** | Writable (Standard Docker default) | **Read-Only** (`read_only: true`) |
| **Secrets** | `.env` file on disk (Vulnerable to `cat .env`) | **Environment Variables** (Injected at runtime) |
| **User** | `root` (Default) | Non-root (Enforced by Distroless) |

---

## ๐Ÿ“ Application Logs Analysis

The following logs demonstrate what the attack attempts look like from the application's perspective. This contrast vividly highlights the effectiveness of the security measures.

### Safe App Logs (`logs/server.safe.log`)
The logs show repeated failures (`ENOENT`).
*   **Why?** `spawnSync` tries to run `ls`, `id`, `curl`. The Distroless image simply **does not have these binaries**. It is not just about a missing shell; the tools themselves are gone.

```log
[Instrumentation] Logging initialized. Writing to: /app/logs/server.safe.log
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"step":"2. Verify Binary was Written","verification":{...},"success":true}`'
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"step":"4. Execute Binary","stdout":"","stderr":"","error_obj":{"message":"spawnSync /tmp/hello_test EACCES","code":"EACCES"},"success":true}`'
```

### Unsafe App Logs (`logs/server.unsafe.log`)
The logs confirm successful command execution and file system manipulation.

```log
[Instrumentation] Logging initialized. Writing to: /app/logs/server.unsafe.log
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"step":"4. Execute Binary","stdout":"Hello from Go binary!\\n","stderr":"","error_obj":null,"success":true}`'
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"command":"id","args":[],"stdout":"uid=0(root) gid=0(root) ...","stderr":"","status":0,"signal":null}`'
 โจฏ Error: NEXT_REDIRECT
    ... digest: '`{"command":"cat","args":["/app/.env"],"stdout":"","stderr":"cat: can\'t open \'/app/.env\': No such file or directory\n","status":1,"signal":null}`'
```
*(Note: In the unsafe logs, `cat /app/.env` fails above because the file is named `.env` in the root, but `ls -la` in the full logs would reveal the directory structure.)*

---

## ๐Ÿ’ฅ POC Results

### 1. Standard RCE (Living off the Land)
Attempting to run standard shell commands.

-   **Unsafe**: โœ… Success. Attacker can run `id`, `ls`, `cat .env`, and access sensitive data.
-   **Safe**: โŒ Blocked. `spawnSync /bin/sh ENOENT`. There is no shell to execute commands.

### 2. Advanced Attack (Bring Your Own Land)
Attempting to bypass "missing tools" by uploading a custom binary.

-   **Unsafe**: โœ… Success.
    1.  Attacker chunks a binary (to bypass payload limits).
    2.  Writes it to `/tmp/malware`.
    3.  Runs `chmod +x`.
    4.  Executes the binary.
-   **Safe**: โŒ Blocked.
    -   **Write Failed**: `EROFS: read-only file system`.
    -   The attacker cannot drop files anywhere, effectively neutralizing the BYOL attack.

### 3. "True Fileless" Execution Analysis
Can an attacker load a binary into a variable and execute it directly from memory?

-   **Concept**: Concatenate chunks of a binary into a global JavaScript variable (e.g., `global.payload = "..."`), then execute it.
-   **Reality**: **Failed**.
    -   Node.js `child_process` functions (`spawn`, `exec`) **require a file path**. They cannot execute a buffer or string directly.
    -   To bypass this on Linux, one needs `memfd_create` (a syscall to create an anonymous file in RAM).
    -   **The Barrier**: Node.js does not expose `memfd_create` natively. Accessing it would require a C++ addon (like `ffi-napi`) to be pre-installed in `node_modules`.
    -   **Distroless Impact**: Since the image lacks compilers (`gcc`, `make`), an attacker cannot build this addon on the fly.

---

## ๐Ÿ” Best Practices Demonstrated

### 1. Use Distroless Images
"Distroless" images contain *only* your application and its runtime dependencies. They do not contain package managers, shells, or standard UNIX tools.
*   **Why?** If an attacker gets RCE, they cannot look around (`ls`), download files (`curl`), or escalate privileges easily.

### 2. Read-Only Filesystems
Configure your container runtime to mount the root filesystem as read-only.
*   **Why?** It prevents attackers from downloading (BYOL) or modifying your application code (persistence).
*   **How?** In `docker-compose.yml`:
    ```yaml
    read_only: true
    tmpfs:
      - /tmp:noexec # CRITICAL: explicitly block execution!
    ```
    *Observation*: With this setup, our POC shows that the attacker *can* write the binary to `/tmp` (write success), but execution fails with `EACCES` (Permission Denied) because of the `noexec` flag. This balances functionality (writable tmp) with security.

### 3. Native Environment Variables (The "Export" Space)
Do **not** ship `.env` files in your container images. If an attacker can read files (e.g., `cat .env`), your secrets are compromised.
*   **Secure Approach**: Inject variables directly into the process environment at runtime (e.g., via Kubernetes Secrets, AWS Parameter Store, or Docker's `environment` key).
*   **Why?** It makes it much harder for an attacker to dump all secrets at once compared to reading a single file.

---

## ๐Ÿš€ How to Run

1.  **Start the Environment**:
    Both the safe and unsafe applications are defined in a single `docker-compose.yml` file.
    ```bash
    docker compose up --build -d
    ```

2.  **Run the Exploits**:
    You can run the exploits against the specific ports to see the difference.

    *   **Targeting Unsafe App (Port 3001)**:
        ```bash
        # 1. Standard RCE (LotL) - SUCCEEDS
        python exploit/poc.py http://localhost:3001

        # 2. Advanced Attack (BYOL) - SUCCEEDS
        python exploit/poc_advanced.py http://localhost:3001
        ```

    *   **Targeting Safe App (Port 3000)**:
        ```bash
        # 1. Standard RCE (LotL) - FAILS (ENOENT)
        python exploit/poc.py http://localhost:3000

        # 2. Advanced Attack (BYOL) - FAILS (EACCES/EROFS)
        python exploit/poc_advanced.py http://localhost:3000
        ```

3.  **Clean Up**:
    ```bash
    docker compose down
    ```