MCPwn: The nginx-ui Flaw That Exposes a Bigger Problem With MCP Adoption (CVE-2026-33032)

A 27-character omission gave any network-adjacent attacker full control over 2,689+ publicly exposed nginx servers. The real story: every other app bolting MCP onto existing auth stacks.

MCPwn: The nginx-ui Flaw That Exposes a Bigger Problem With MCP Adoption (CVE-2026-33032)

TL;DR

A 27-character omission in nginx-ui's router gave any network-adjacent attacker full control over 2,689+ publicly exposed nginx servers. The patch is easy. The real story is what it tells us about every other application that added Model Context Protocol (MCP) support in the last six months.

If your organization runs nginx-ui, patch to v2.3.4 immediately. If your organization runs anything else with MCP bolted on — and the list is growing fast — read on. The same mistake is almost certainly hiding somewhere.

What happened

On March 4, 2026, researcher Yotam Perkal at Pluto Security reported an authentication bypass in nginx-ui's MCP integration. The maintainer (0xJacky) shipped a fix the next day, and v2.3.4 was released on March 15. The CVE (CVE-2026-33032, CVSS 9.8) was assigned March 28.

Within weeks, active exploitation was confirmed. Recorded Future's Insikt Group flagged it as one of 31 high-impact CVEs exploited by threat actors during March. VulnCheck added it to its Known Exploited Vulnerabilities catalog on April 13. The codename that stuck: MCPwn.

Shodan data shows 2,689 publicly exposed nginx-ui instances, concentrated in China, the United States, Indonesia, Germany, and Hong Kong. Most sit on the default port 9000, on Alibaba Cloud, Oracle, Tencent, and DigitalOcean infrastructure. Every one of them was, until mid-March, a fully unauthenticated takeover away from becoming someone else's reverse proxy.

The technical root cause, in one diff

nginx-ui added MCP support so that AI assistants could manage nginx configurations through natural language. Under the hood, MCP uses Server-Sent Events (SSE) with two HTTP endpoints:

  • GET /mcp — opens the listening stream, returns a session ID
  • POST /mcp_message — the action channel where every tool call (config writes, restarts, file reads) actually happens

In the vulnerable version (v2.3.3 and earlier), the router looked like this:

r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(),
    func(c *gin.Context) { mcp.ServeHTTP(c) })

r.Any("/mcp_message", middleware.IPWhiteList(),
    func(c *gin.Context) { mcp.ServeHTTP(c) })

Look at what's missing on the second route. No AuthRequired(). The action endpoint — the one that every destructive operation flows through — had no authentication.

The only remaining safety net was the IP whitelist. And that had a fatal default:

if len(settings.AuthSettings.IPWhiteList) == 0 {
    c.Next()  // empty list = allow everyone
    return
}

Empty whitelist means allow-all. Default behavior on every fresh install.

Two layers of defense, both disabled by omission. The fix in v2.3.4 was literally 27 characters: , middleware.AuthRequired() added to the second route. The maintainer also added a regression test that would have caught the original flaw.

What an attacker gets

Two requests, both unauthenticated:

  1. GET /mcp — establish an SSE session, receive a session ID
  2. POST /mcp_message?sessionId=xxx — invoke any of 12 tools exposed by the MCP integration

Those tools include seven destructive operations:

ToolWhat it does
nginx_config_addWrites a new config file and auto-reloads nginx
nginx_config_modifyRewrites any existing config
nginx_config_enableToggles sites on or off
nginx_config_renameRenames config files
nginx_config_mkdirCreates directories
reload_nginxReloads the configuration
restart_nginxRestarts the process

Plus five read-only reconnaissance tools that let an attacker map the entire deployment before touching anything.

The nginx_config_add tool is the kill shot: write a server block, reload nginx, done. One API call turns a public-facing nginx into whatever the attacker wants it to be — a traffic interceptor, a credential harvester, a phishing page host, or simply a brick when an invalid config gets loaded.

What this actually enables

Forget the abstract "full takeover" phrasing. Here is what a real incident looks like:

Traffic interception. Inject a server block that proxies legitimate requests through an attacker-controlled upstream, logging every request and response in transit. Every token, every session cookie, every API key passing through the compromised nginx — captured.

Credential harvesting via log format. Add an access_log directive with a custom log_format that includes $http_authorization. Admins log in to nginx-ui. Their JWTs end up in the attacker's log file.

Persistent access. With a captured admin JWT, query the nginx-ui settings API to extract the JwtSecret. Forge admin tokens for any user. Even after the config is cleaned up, the attacker still has valid credentials.

Architecture mapping. Read every config file. Now the attacker knows every upstream server, every TLS cert path, every internal service. This is pre-work for lateral movement.

Denial of service. Push an invalid config, trigger a reload. nginx drops. Everything behind nginx is now unreachable.

None of this requires credentials. None of it requires being on the same host. The Pluto PoC runs the full chain from a separate container on the same network — 172.21.0.3 taking over 172.21.0.2 — with zero authentication headers.

The bigger problem: this is not a nginx-ui bug

This is where most of the coverage stops. "Bug found, patched, move on." That misses the more important story.

Two weeks before MCPwn, Pluto published MCPwnfluence — a pair of vulnerabilities (CVE-2026-27825 CVSS 9.1, CVE-2026-27826 CVSS 8.2) in the official Atlassian MCP server. Chained, they gave any attacker on the local network unauthenticated RCE on the host machine.

Same research team. Different product. Same class of bug.

The pattern is clear enough to name: when a team bolts MCP onto an existing application, the MCP endpoints inherit the application's full operational capabilities — config writes, file reads, service restarts — but not its authentication stack. The web API is authenticated. The WebSocket endpoints are authenticated. Then someone ships an MCP integration and exposes those same capabilities through a new HTTP surface that was never wired through the existing auth middleware.

This is not a theoretical concern. MCP adoption exploded through late 2025 and early 2026. Hundreds of new MCP servers appear on GitHub every week. Many wrap critical infrastructure — databases, cloud consoles, CI/CD pipelines, security tools. Each one is a new attack surface that many teams are treating as "just a thin wrapper" when it is actually a privileged API gateway into the underlying system.

The specific mistake in nginx-ui was especially easy to make because MCP's SSE transport uses two endpoints. Developers intuitively think of the SSE stream as "the connection" and the message endpoint as "just the data pipe." But the message endpoint is where the power lives. Protecting the stream but not the message endpoint is locking the front door while leaving the back door wide open.

We expect more CVEs in this class. If your organization is adopting MCP, the nginx-ui postmortem is a free lesson about what to check in your own integrations.

Detection and response

Patching v2.3.4 is step one. Step two is finding out whether you were already hit during the exposure window.

Check if you are running a vulnerable version

# Docker
docker exec <container> nginx-ui --version

# Binary
nginx-ui --version

Anything through v2.3.3 is vulnerable. v2.3.4 and above are patched.

Hunt for post-exploitation artifacts

The attack leaves traces in three places. Look at all of them before declaring yourself clean.

1. Unexpected config files. An attacker using nginx_config_add writes new files under the nginx-ui-managed config directory (typically /etc/nginx/conf.d/ or a sites-enabled/ path depending on distribution). Run a listing sorted by modification time:

find /etc/nginx/conf.d -type f -newer /tmp/nginx-ui-v234-installed -printf '%T@ %p\n' | sort -n

Any file you did not deploy is suspect. Compare against your configuration management inventory (Ansible, Puppet, Git).

2. Modified log_format directives. Attackers commonly add a custom log format that captures $http_authorization or $http_cookie. Search every nginx config for suspicious format strings:

grep -rn "log_format" /etc/nginx/

Any log_format that includes $http_authorization, $http_cookie, or references to request bodies should be treated as hostile unless you explicitly authored it.

3. Unfamiliar proxy_pass targets. Attackers redirect traffic through attacker-controlled upstreams. Inventory every proxy_pass directive:

grep -rn "proxy_pass" /etc/nginx/ | grep -v "# "

Any upstream pointing to an IP or domain you don't recognize — especially cloud-provider addresses in regions you do not operate in — is a red flag.

4. nginx-ui access logs. If you kept nginx-ui's own logs, search them for direct requests to /mcp_message from source IPs that are not your administrators:

grep "mcp_message" /var/log/nginx-ui/access.log

A session establishment followed by a POST /mcp_message?sessionId=... from an unexpected source is the exploitation signature.

If you find any of the above

Treat the host as compromised.

  • Rotate every secret that was reachable from nginx: TLS private keys, upstream API credentials, OAuth client secrets, JwtSecret values from nginx-ui.
  • Assume administrator JWTs were stolen — invalidate all active sessions.
  • Rebuild the nginx-ui instance from clean artifacts. Do not simply patch and restart.
  • Review logs from upstreams behind nginx for signs of lateral movement using harvested credentials.

What to do if you are building MCP integrations

The nginx-ui fix is a template for anyone adding MCP to an existing application. Three rules that would have prevented MCPwn:

  1. Audit every endpoint in your MCP transport, not just the stream. If you use SSE, that is two endpoints. If you use WebSocket, it is one endpoint plus the upgrade handshake. Every one of them needs the same authentication middleware as your most privileged internal API.
  2. Test authentication on the message endpoint explicitly. The nginx-ui patch added a regression test that hits both /mcp and /mcp_message without credentials and asserts HTTP 403 from each. Replicate this pattern. If your test suite does not verify the action endpoint rejects unauthenticated requests, you cannot claim MCP is authenticated.
  3. Default fail-closed on IP allowlists. An empty allowlist should mean deny-all, not allow-all. The opposite behavior is one of the most common sources of default-insecure configurations in application middleware.

Timeline

DateEvent
2026-03-04Pluto Security discovers and reports the flaw
2026-03-14Fix committed to nginx-ui
2026-03-15nginx-ui v2.3.4 released
2026-03-28CVE-2026-33032 published
2026-03Active exploitation detected (per Recorded Future Insikt Group)
2026-04-13VulnCheck adds CVE-2026-33032 to its KEV list
2026-04-15Pluto publishes technical write-up; press coverage begins

The bigger lesson

Vulnerability management programs that only track traditional asset types (Microsoft, Fortinet, Cisco) are blind to the rapidly expanding surface of AI-integrated infrastructure. MCP servers, LLM gateways, agent runtimes, and the tools they wrap are already showing up in KEV catalogs. They need to be in your inventory before the next MCPwn-class CVE lands.