What happened
On 2026-03-15, the nginx-ui maintainers released version 2.3.4. The release fixed a missing authentication check on a single HTTP endpoint. That endpoint is /mcp_message, the delivery path for the Model Context Protocol integration the project had added to let AI tools manage nginx configurations.
The advisory describes the shape of the problem in one paragraph. "The nginx-ui MCP (Model Context Protocol) integration exposes two HTTP endpoints: /mcp and /mcp_message. While /mcp requires both IP whitelisting and authentication (AuthRequired() middleware), the /mcp_message endpoint only applies IP whitelisting — and the default IP whitelist is empty, which the middleware treats as 'allow all'."
The consequence, in the advisory's own words, is that "any network attacker can invoke all MCP tools without authentication, including restarting nginx, creating/modifying/deleting nginx configuration files, and triggering automatic config reloads — achieving complete nginx service takeover."
The CVE is CVE-2026-33032, CVSS 9.8, class: missing authentication. The finder is Yotam Perkal of Pluto Security, who published a technical writeup alongside the fix and codenamed the bug "MCPwn."
What is Confirmed vs Reported vs Claimed
Confirmed (primary source or NVD record):
- The two endpoints, the middleware mismatch, and the "empty allowlist equals allow-all" default — all direct from the maintainers' advisory.
- CVSS 9.8. Missing-authentication class.
- Fix released in version 2.3.4 on 2026-03-15.
- Workarounds: add
middleware.AuthRequired()to/mcp_message, or change the IP allowlist default to deny-all.
Reported (multiple independent secondary outlets — The Hacker News, Security Affairs, Rapid7):
- Exploitation in the wild since March 2026, per Recorded Future's 2026-04-13 report cited by Rapid7.
- Chaining with CVE-2026-27944 (CVSS 9.8, an unauthenticated information-leak on
/api/backupthat exposes thenode_secretrequired to open a session on/mcp). Fixed in 2.3.3. - Approximately 2,600 publicly reachable nginx-ui instances per Pluto Security's scan, ~2,689 per Shodan data cited by The Hacker News, with most located in China, the U.S., Indonesia, Germany, and Hong Kong. These are exposure counts, not compromise counts.
- Affected-version reporting is inconsistent across advisories: the finder's blog lists ≤ 2.3.3 as vulnerable; the NVD record lists ≤ 2.3.5. Rapid7 recommends updating to 2.3.6 to avoid the ambiguity.
Claimed (single-source, explicitly attributed):
- Perkal's structural explanation: "When you bolt MCP onto an existing application, the MCP endpoints inherit the application's full capabilities but not necessarily its security controls. The result is a backdoor that bypasses every authentication mechanism the application was carefully built with." This is his quote, and we cite it as his.
The chain, step by step
Only the hops the sources actually describe.
Step 1 — Leak the node_secret. The attacker issues an unauthenticated request to /api/backup. On nginx-ui versions before 2.3.3, that endpoint returns enough information to recover the node_secret value, the query parameter that authenticates the MCP interface. This step is CVE-2026-27944, reported by The Hacker News and Rapid7 as being chained in active exploitation.
Step 2 — Open the MCP session. The attacker issues GET /mcp?node_secret=xxx to establish a server-sent-events session and receive a sessionId. The advisory confirms this endpoint is protected by both IP allowlist and AuthRequired(). The node_secret obtained in step 1 satisfies the auth side; the empty-allowlist default satisfies the network side.
Step 3 — Invoke tools on /mcp_message. The attacker issues POST /mcp_message?sessionId=xxx carrying the tool-invocation payload. No node_secret, no JWT, no cookies. Because /mcp_message is only gated by the same empty allowlist, the request is accepted. Per the advisory, the invocable tools include restarting nginx, creating / modifying / deleting configuration files, and triggering automatic reloads.
Two HTTP requests in total, if the attacker already holds the node_secret. Three, if they also have to chain the backup leak.
The broken assumption
The design intent reads well. Two endpoints, both behind AuthRequired() in intent, both behind an IP allowlist. Defense in depth. What shipped was different.
Two separate assumptions failed together.
The first failed assumption is that every privileged endpoint in a family would be wired to the same authentication middleware. /mcp was. /mcp_message was not. One line of code separated "authenticated" from "unauthenticated," and that line was absent for the endpoint that carried the write operations. Security Affairs notes that the 2.3.4 fix added that missing line and a regression test so the same kind of omission cannot silently recur.
The second failed assumption is that the IP allowlist would be meaningful at runtime. The allowlist's default was empty. The middleware treated empty as allow-all. So the network control that was supposed to sit beneath the authentication control sat at zero as well. Two defenses, both disabled at their defaults, on the same endpoint.
Neither assumption is unique to this project. Both describe the general shape of what happens when MCP — a protocol whose value is that AI tools can drive application internals — is added to an application that already has authentication, but whose authentication is wired by convention rather than by a single enforcement point.
Perkal states the structural version of this directly: "the MCP endpoints inherit the application's full capabilities but not necessarily its security controls." Treat that as his claim, not ours. But the class of failure matches what the advisory says happened here.
Why detection gets harder
The exposed surface is a management UI that normally sits in an internal or administrative network. The exploit uses two HTTP requests that look, on the wire, like the product's own MCP traffic. There is no malware signature. There is no credential stuffing pattern. A defender watching authentication logs sees a successful session. A defender watching network logs sees traffic to the product's own ports.
The only reliable detection signal is the one the application itself would have to produce: "this /mcp_message invocation was served without a verified identity." On a vulnerable version, that signal does not exist, because the endpoint does not check for identity. Detection has to shift to the outside — to whether the /mcp_message endpoint is reachable from the network at all, and whether the config-change events it produces are consistent with the change-management trail your operators expect.
What to check this week
Operational, in priority order.
- Inventory nginx-ui deployments. Run an internal scan for port 9000 and any known nginx-ui hostnames. The Shodan-derived public-surface count is ~2,600; your internal surface is typically larger.
-
Patch status. If any instance is at 2.3.3 or earlier, the
/api/backupinformation leak (CVE-2026-27944) is usable to harvest thenode_secret. If any instance is at 2.3.4 or earlier per the NVD record's stated coverage (≤ 2.3.5), treat it as exposed to CVE-2026-33032. Rapid7's guidance to go straight to 2.3.6 is the cleanest way to eliminate the version-number ambiguity. -
Exposure cutoff. Confirm the management interface is not reachable from any network segment that carries untrusted users. If the workaround path is the only option, change the IP allowlist default from empty to an explicit deny-all, and add
middleware.AuthRequired()to/mcp_messageper the advisory. -
Change audit on managed nginx configs. Compare the on-disk nginx config files against the last known-good revision. An attacker who succeeded would leave their signal there, not in the UI logs. Pay attention to any additions that introduce new
proxy_passtargets, new upstream blocks, or newlog_formatdefinitions that could capture credentials.
What to change in policy
One policy change has high leverage.
Treat any MCP endpoint as privileged by default — at the same tier as an admin RPC — regardless of what the rest of the application looks like. In this case, the nginx-ui codebase already had AuthRequired(). The bug was that not every MCP endpoint was behind it. The preventive rule is to require, as a code-review gate, that every route registered by the MCP integration passes through a single enforcement point, and that a regression test fails the build if a new route skips it. Security Affairs reports this is exactly what the 2.3.4 release added.
Two secondary rules follow from the same posture.
First, "empty allowlist equals allow-all" is a footgun that only makes sense in development. In production it is an outage waiting to happen. Allowlists should fail closed. A configuration loader that sees an empty list should refuse to start, not accept everything.
Second, when a project adds MCP — or any AI-integration surface that exposes the application's capabilities as callable tools — the threat model should be re-run specifically for that surface, not inherited from the rest of the app. Capability exposure is the part of MCP that is new. Authentication is the part that is assumed.
What this does NOT say
- This post does not name any organization as an exploited victim. No source does.
- It does not count compromised instances. The ~2,600 figure is exposed surface per a Pluto Security scan, not a tally of successful exploits.
- It does not attribute the in-the-wild exploitation to a threat group. None was named.
- It does not claim the allow-all default was an intentional design choice or an implementation bug. The advisory does not say which, and neither does this post.
- It does not generalize from this CVE to claims about MCP as a protocol. The specific failure here is an application-level mis-wiring that happens to be on MCP endpoints. The broader pattern Perkal describes — bolting MCP onto an existing app — is his statement, and we leave it as his.
Sources
- Primary: nginx-ui maintainers' advisory (release 2.3.4, 2026-03-15); Pluto Security technical blog (Yotam Perkal, 2026-03-15); NVD CVE-2026-33032.
- Secondary: Rapid7 Emerging Threat Response (2026-04-16, updated 2026-04-17); The Hacker News (2026-04-15); Security Affairs (2026-04-15); Cyber Security Agency of Singapore alert AL-2026-039.
Top comments (0)