
One missing function call on the route registration was enough to turn the MCP interface into an unauthenticated RCE gateway.
Non-destructive detection tooling for the nginx-ui MCP authentication bypass known publicly as MCPwn. Ship a two-HTTP-request unauthenticated takeover of any nginx-ui instance running a vulnerable version.
nginx-ui exposes a Model Context Protocol (MCP) interface that lets an authenticated operator drive destructive tools such as nginx configuration edits, restart commands and backup operations through JSON-RPC.
The /mcp endpoint is guarded by the AuthRequired() middleware, while
its paired /mcp_message endpoint, which actually receives the tool
invocations, was deployed without the middleware. Any client who can
reach the UI over the network can:
/mcp and receive a fresh
sessionID without presenting any credential.sessionID to invoke any registered MCP tool by POSTing to
/mcp_message, still unauthenticated.The attacker gains complete control of the nginx process: edit server blocks to redirect traffic, extract TLS private keys via path reads, reload or stop nginx, and implant persistent backdoors.
// vulnerable (pre-2.3.4)
r.GET("/mcp", AuthRequired(), mcpHandler)
r.POST("/mcp_message", mcpMessageHandler) // missing middleware
// fixed in 2.3.4
r.GET("/mcp", AuthRequired(), mcpHandler)
r.POST("/mcp_message", AuthRequired(), mcpMessageHandler)
One missing function call on the route registration was enough to turn the MCP interface into an unauthenticated RCE gateway.
Attacker nginx-ui (<2.3.4)
| 1) GET /mcp (no auth) |
| --------------------------------------> |
| 2) event: endpoint data: /mcp_message?sessionID=XYZ
| <-------------------------------------- |
| 3) POST /mcp_message?sessionID=XYZ |
| body: JSON-RPC call_tool "nginx_reload" or
| "edit_config {...}" |
| --------------------------------------> |
| 4) 200 OK; tool executed with full privileges
| <-------------------------------------- |
This repository intentionally avoids any destructive action. The
detector only uses read-only MCP methods (tools/list). The logic:
> Step 1 Fingerprint nginx-ui via
> GET / (HTML title, JS bundles)
> GET /api/settings (JSON referencing nginx-ui keys)
> Extract version via header / body regex
> Step 2 Open SSE stream to /mcp
> Parse the first sessionID from the "endpoint" event
> Step 3 POST /mcp_message?sessionID=<id>
> Body: {"jsonrpc":"2.0","method":"tools/list","params":{}}
> No Authorization header
> Step 4 Vulnerable if status 200 and body contains a tool manifest
> Patched if status 401 / 403 / 404
> Inconclusive otherwise
The tools/list JSON-RPC method is read-only. It enumerates which
tools the MCP server knows about but invokes none of them. The script
refuses to POST any other method and never constructs payloads for
destructive tools.
git clone https://github.com/your-org/CVE-2026-33032-detector.git
cd CVE-2026-33032-detector
python3 --version # 3.9 or newer
# No third-party packages required.
For the Nmap NSE component, copy nginx-ui-mcpwn.nse into your local
scripts directory and refresh the script database:
cp nginx-ui-mcpwn.nse /usr/share/nmap/scripts/
sudo nmap --script-updatedb
# Single target
python3 detect_nginx_ui_mcpwn.py --target https://nginx-ui.internal
# Bulk scan from file
python3 detect_nginx_ui_mcpwn.py --targets targets.txt --workers 20
# JSON (NDJSON) output suited for piping to jq
python3 detect_nginx_ui_mcpwn.py --target 10.0.0.5:9000 --json | jq .
Command line reference:
nmap -p 80,443,9000 --script nginx-ui-mcpwn 10.0.0.0/24
nmap -p 443 --script nginx-ui-mcpwn --script-args "nginx-ui-mcpwn.timeout=8" host.example.com
_ _ _ _ _ ___ __ __ ____ ____
| \ | | __ _(_)_ __ __ __ | | | ||_ _| | \/ |/ ___| _ \__ ___ __
| \| |/ _` | | '_ \\ \/ /_____| | | | | | | |\/| | | | |_) \ \ /\ / / '_ \
| |\ | (_| | | | | |> <|_____| |__| | | | | | | | |___| __/ \ V V /| | | |
|_| \_|\__, |_|_| |_/_/\_\ |_____|_||___| |_| |_|\____|_| \_/\_/ |_| |_|
|___/
CVE-2026-33032 nginx-ui MCPwn Detector > non-destructive
[!] VULNERABLE https://nginx-ui.internal version=2.3.2 evidence=status=200; body_preview={"jsonrpc":"2.0","id":"...","result":{"tools":[{"name":"edit_config",...
[+] patched https://nginx-ui-patched:9000 version=2.3.4 evidence=status=401; body_preview={"error":"unauthorized"}
[.] not nginx-ui https://example.com evidence=fingerprint negative
Summary: 1/3 targets flagged vulnerable
{"target":"https://nginx-ui.internal","reachable":true,"is_nginx_ui":true,"mcp_endpoint_present":true,"session_id_obtained":true,"mcp_message_unauthenticated":true,"vulnerable":true,"nginx_ui_version":"2.3.2","evidence":"status=200; body_preview=...","error":null}
PORT STATE SERVICE
9000/tcp open http
| nginx-ui-mcpwn:
| status: VULNERABLE
| version: 2.3.2
| session_id: abc123
|_ evidence: tools/list accepted without credentials
CVE-2026-27944 (the unauthenticated /api/backup download that leaks
the AES key in the X-Backup-Security header) chains cleanly with
MCPwn: the backup archive exposes the node_secret, which can be
used to authenticate to /mcp even after the fix for 33032 lands,
until operators rotate the secret.
The companion detector for 27944 lives in a sibling directory of this research project.
node_secret, admin password hashes and any TLS private
keys that were accessible to the nginx-ui process./mcp_message route
is reachable.This tooling is published to help defenders identify unpatched instances inside environments they are authorised to test. Do not use it against systems you do not own or have explicit permission to assess. The authors decline all responsibility for misuse.
Released under the MIT License. See the individual script headers for per-file notices.
| Field | Value |
|---|
| CVE ID | CVE-2026-33032 |
| Alias | MCPwn |
| CVSS 3.1 | 9.8 / Critical (AV:N / AC:L / PR:N / UI:N / S:U / C:H/I:H/A:H) |
| CWE | CWE-306 Missing Authentication for Critical Function |
| Affected product | nginx-ui (github.com/0xjacky/nginx-ui) |
| Affected versions | All versions prior to 2.3.4 |
| Fixed version | 2.3.4 |
| Exploitation status | Active in the wild (Picus Security, Recorded Future) |
| Shodan exposure | ~2,689 internet-facing instances |
| Publication date | 2026-04-15 |
| MCP tool | Effect on nginx server |
|---|
edit_config | Rewrite arbitrary server blocks, insert attacker upstream |
reload_nginx | Apply attacker configuration without manual action |
stop_nginx | Denial of service |
read_file | Exfiltrate TLS private keys, credentials from mounted paths |
create_cert | Re-issue TLS certificates under attacker control |
list_backups / download | Pull entire nginx-ui backup including node_secret and hashes |
| Flag | Purpose |
|---|
--target | Single URL or host[:port] |
--targets | Newline separated target file |
--timeout | HTTP timeout, default 10 seconds |
--workers | Concurrent workers for bulk scans, default 10 |
--verify-tls | Enforce TLS certificate verification (off by default) |
--json | Emit NDJSON, one record per target |
--no-banner | Suppress the ASCII banner |