# CVE-2026-72573: Command Injection in pm2panel /restart Endpoint

## Summary

The `4xmen/pm2panel` web application contains an OS command injection vulnerability in the authenticated `/restart` HTTP endpoint. The handler at line 188 of `pm2panel.js` concatenates the attacker-controlled `req.query.id` query parameter directly into a `child_process.exec()` shell command (`exec("pm2 restart " + req.query.id)`). Because `exec` invokes `/bin/sh`, shell metacharacters such as `;`, `&&`, `|`, backticks, or `$()` in the `id` parameter are interpreted by the shell, allowing an authenticated attacker to execute arbitrary commands on the host with the privileges of the pm2panel process.

## Impact

- **Package/component affected:** `4xmen/pm2panel` (Node.js web application, `pm2panel.js`)
- **Affected versions:** All versions (commit `dd2a7d2e8cb4dacefb618ab54b0a8f7dc6742fa0`, latest on `master`)
- **Risk level:** High — authenticated remote code execution
- **Consequences:** An attacker with valid panel credentials (default: `admin`/`admin`) can execute arbitrary shell commands on the host server, leading to full system compromise, data exfiltration, lateral movement, or service disruption.

## Impact Parity

- **Disclosed/claimed maximum impact:** Code execution (CWE-78, OS command injection)
- **Reproduced impact from this run:** Full arbitrary command execution — a marker file was created on the host filesystem via an injected `touch` command through the authenticated `/restart` endpoint
- **Parity:** `full`
- **Not demonstrated:** N/A — the full claimed impact was demonstrated

## Root Cause

The `/restart` route handler in `pm2panel.js` (line 188) constructs a shell command by directly string-concatenating the user-supplied `req.query.id` query parameter:

```js
app.get('/restart', function (req, res) {
    if (!req.session.islogin) {
        // redirect to login...
    } else {
        if (req.query.id) {
            exec("pm2 restart " + req.query.id, (error, stdout, stderr) => {
                // ...
            });
        }
    }
});
```

`child_process.exec()` spawns a shell (`/bin/sh -c`) to run the command. The `req.query.id` value is never validated, sanitized, or shell-escaped. When an attacker sends a request like `GET /restart?id=0; touch /tmp/pm2panel_pwned`, the shell interprets the `;` as a command separator and executes `touch /tmp/pm2panel_pwned` in addition to `pm2 restart 0`.

The same vulnerable pattern exists in four other handlers:
- `/start` (line 218): `exec("pm2 start " + req.query.id)`
- `/stop` (line 248): `exec("pm2 stop " + req.query.id)`
- `/delete` (line 278): `exec("pm2 delete " + req.query.id)`
- `/addProccess` (line 149): `exec('pm2 start "' + req.body.path + '"')`

No fix commit has been identified; the vulnerability is present in the latest commit on `master`.

## Reproduction Steps

1. **Reference script:** `bundle/repro/reproduction_steps.sh`
2. **What the script does:**
   - Clones/reuses the `4xmen/pm2panel` repository from the project cache
   - Installs system dependencies (`libpam0g-dev`) and npm dependencies (including native `node-linux-pam` module)
   - Installs and starts PM2 with a demo process (id 0)
   - Starts the pm2panel Express web application on port 3001
   - Authenticates via `POST /loginCheck` with default credentials (`admin`/`admin`)
   - Sends an authenticated `GET /restart?id=0; touch /tmp/pm2panel_pwned_<pid>` request
   - Verifies the marker file was created (proving arbitrary command execution)
   - Runs negative controls: unauthenticated request is rejected (302 redirect), safe request without injection does not create a marker
3. **Expected evidence of reproduction:**
   - Marker file exists at `/tmp/pm2panel_pwned_*` after the exploit request
   - HTTP 302 response from the exploit endpoint (normal redirect behavior)
   - Unauthenticated requests return 302 redirect to `/login`
   - Safe restart requests (no injection) do not create marker files

## Evidence

- **Log files:**
  - `bundle/logs/reproduction_steps.log` — full script execution log
  - `bundle/logs/pm2panel_service.log` — pm2panel application server log
  - `bundle/logs/artifacts/http/response_login.txt` — login response with session cookie
  - `bundle/logs/artifacts/http/request_exploit.txt` — exploit request details
  - `bundle/logs/artifacts/http/response_exploit.txt` — exploit HTTP response (302)
  - `bundle/logs/artifacts/http/marker_evidence.txt` — marker file existence and stat output
  - `bundle/logs/artifacts/http/response_unauth.txt` — unauthenticated request response (302 redirect)
  - `bundle/logs/artifacts/http/safe_restart_status.txt` — safe restart response code
  - `bundle/repro/runtime_manifest.json` — structured runtime evidence manifest

- **Key excerpts:**
  - Exploit request: `GET /restart?id=0;%20touch%20/tmp/pm2panel_pwned_3850 HTTP/1.1`
  - Exploit response: `HTTP/1.1 302 Found` with `Location: /`
  - Marker evidence: `MARKER_FILE_EXISTS=true` with `stat` output showing file creation timestamp
  - Negative control (unauthenticated): `302` redirect to `/login`
  - Negative control (safe): no marker file created

- **Environment:**
  - Node.js v24.18.0, npm 11.16.0
  - PM2 v7.0.3
  - pm2panel commit `dd2a7d2e8cb4dacefb618ab54b0a8f7dc6742fa0`
  - Linux x86_64, Express 4.x, express-session

## Recommendations / Next Steps

1. **Fix:** Replace `child_process.exec` with `child_process.execFile` (which does not invoke a shell) and pass `req.query.id` as a separate argument array, or validate `req.query.id` against a strict numeric regex before use.
2. **Defense in depth:** Implement input validation on all endpoints that accept process IDs (`/start`, `/stop`, `/delete`, `/addProccess`).
3. **Authentication:** Change default credentials from `admin`/`admin` and enforce strong password policies.
4. **Upgrade guidance:** No patched version exists. Users should apply the fix manually or discontinue use of the panel.
5. **Testing:** Add integration tests that send shell metacharacters in query parameters and assert they are not interpreted by the shell.

## Additional Notes

- **Idempotency:** The script uses process-specific marker file names (with `$$` PID suffix) and cleans up PM2 processes and the pm2panel server on each run. It was verified to pass on two consecutive executions.
- **Authentication requirement:** The vulnerability requires authentication. The script performs a proper login flow with `POST /loginCheck` and session cookie extraction before sending the exploit request.
- **Multiple vulnerable endpoints:** The same command injection pattern affects `/start`, `/stop`, `/delete`, and `/addProccess` in addition to `/restart`. The reproduction focuses on `/restart` as specified in the claim.
