What if a single missing function call – one middleware reference, 27 characters – could give any attacker on your network complete control over your nginx web server? No credentials needed. No exploitation complexity. Just a plain HTTP request to a URL that should have been protected but wasn’t.
That’s CVE-2026-33032, a critical vulnerability (CVSS 9.8) we discovered in nginx-ui, a popular web-based nginx management tool with over 11K GitHub stars and 430,000+ Docker pulls. Since publication, active exploitation in the wild has been confirmed: VulnCheck added it to their Known Exploited Vulnerabilities (KEV) list, and Recorded Future’s Insikt Group identified it as one of 31 high-impact CVEs actively exploited in March 2026, assigning it a Risk Score of 94/100 alongside vulnerabilities in Cisco, Microsoft, Google, and Citrix.
TL;DR: The MCP message endpoint (/mcp_message) in nginx-ui is missing authentication middleware. This exposes 12 MCP tools – including config writes with automatic nginx reload – to any host on the network. One unauthenticated API call is all it takes to inject a config and take over nginx.

The end result: an attacker-injected page served directly by nginx, created and activated via a single unauthenticated MCP tool call.
The Growing Attack Surface of MCP
This is the second post in our series on MCP security. In the first installment, we uncovered MCPwnfluence – a critical SSRF-to-RCE chain in the most widely deployed Atlassian MCP server – demonstrating how MCP servers act as privileged bridges between AI agents and critical infrastructure, and how a single flaw in that bridge can compromise everything behind it.
CVE-2026-33032 takes that theme further. Where MCPwnfluence showed how an MCP server’s own vulnerabilities can be chained for RCE, this finding reveals something more fundamental: 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.
Both findings are part of a broader research effort mapping the risk landscape of the MCP ecosystem – an ecosystem growing fast, with hundreds of new MCP servers appearing every week, many of them wrapping critical infrastructure with varying degrees of security rigor.
nginx-ui: Where AI Meets YourWeb Server
nginx-ui is exactly what it sounds like – a web interface for managing nginx. Edit configs, manage SSL certificates, monitor status, restart the server. Over 10,600 GitHub stars worth of developers chose it over SSHing into boxes and hand-editing config files.
Its Docker image (uozi/nginx-ui) has been pulled over 430,000 times – meaning hundreds of thousands of nginx instances could be managed through this tool, each one sitting in front of production traffic, APIs, and internal services.
And it’s not just a development tool – nginx-ui has a significant public footprint. Using [Shodan], we searched for nginx-ui’s favicon hash (`http.favicon.hash:-1565173320`), a technique that identifies specific web applications by the unique fingerprint of their icon file. The result: **over 2,600 publicly exposed nginx-ui instances**, spread across cloud providers like Alibaba Cloud, Oracle, Tencent, and DigitalOcean, with the majority of instances on port 9000 (the default nginx-ui backend port).

Β
Like many modern applications, nginx-ui recently added MCP support. The promise: ask your AI assistant to “add a reverse proxy for my new API” and it creates the nginx configuration directly. Powerful. Convenient. And, as we’ll see, potentially devastating when the MCP integration doesn’t carry over the application’s existing security model.
Because MCP support means exposing your application’s most sensitive operations – config writes, service restarts, file reads – through a new set of HTTP endpoints. If those endpoints aren’t protected with the same rigor as the rest of the application, they don’t just become a weakness. They become the weakness.
Under the Hood: Two Endpoints, One Problem
How the MCP Transport Works
nginx-ui uses the SSE (Server-Sent Events) transport from the mcp-go library, which splits communication across two HTTP endpoints:
GET /mcp– Opens a persistent SSE stream. The client connects here to receive responses. Think of this as the “listening” channel.POST /mcp_message– Receives JSON-RPC tool invocations. Every config write, every nginx restart, every file read goes through here. This is the “action” channel.
When a client connects, it GETs /mcp to open the stream. The server responds with a session ID:
event: endpoint data: /mcp_message?sessionId=4f4cdb82-152b-4c10-8f63-1df90e1e061f
From that point on, tool calls are POSTed to /mcp_message?sessionId=xxx, and responses flow back through the SSE stream. nginx-ui authenticates MCP connections using a node_secret query parameter, as documented in their MCP guide.
Spot the Difference
Here’s the MCP router in its entirety. Read it carefully.
// mcp/router.go (vulnerable version - v2.3.3 and earlier)
func InitRouter(r *gin.Engine) {
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)
})
}
/mcp has IPWhiteList() and AuthRequired().
/mcp_message has IPWhiteList() only.
Both route to the exact same handler. But /mcp_message – the endpoint where every destructive operation happens – skips authentication entirely.

The authentication gap: both endpoints reach the same handler, but only one checks credentials.
The Safety Net That Isn’t
The IP whitelist is the only remaining protection. But it has a fatal default:
// internal/middleware/ip_whitelist.go
if len(settings.AuthSettings.IPWhiteList) == 0 {
c.Next() // Empty whitelist = allow everyone
return
}
Empty whitelist. Fail-open behavior. Every fresh installation allows all IPs through. Two security mechanisms, both fail. The result: any host on the network can invoke any MCP tool, including ones that rewrite your nginx configuration and reload the server.
12 Tools, Zero Authentication
What exactly does an unauthenticated attacker get access to? Everything.
7 destructive tools that can modify your nginx:
| Tool | Capability |
|---|---|
nginx_config_add |
Create config files + auto-reload nginx |
nginx_config_modify |
Modify any existing config |
nginx_config_enable |
Enable/disable sites |
nginx_config_rename |
Rename config files |
nginx_config_mkdir |
Create directories |
reload_nginx |
Reload configuration |
restart_nginx |
Restart the process |
5 read-only tools for reconnaissance:
| Tool | Capability |
|---|---|
nginx_config_get |
Read any config file |
nginx_config_list |
Enumerate all configurations |
nginx_config_base_path |
Get config directory path |
nginx_config_history |
View config change history |
nginx_status |
Read server status |
Pay special attention to nginx_config_add – it automatically reloads nginx after writing the config. Config injection and activation in a single, unauthenticated API call.
The Attack: Network Access to Full Takeover in Seconds

The attack flow: step 1 authenticates to get a session, step 2 uses that session to invoke destructive tools with zero authentication.
An attacker on the same network as the nginx-ui instance needs just two requests:
GET /mcp?node_secret=xxx– Establish an SSE session, get a sessionIdPOST /mcp_message?sessionId=xxx– Invoke any tool. Nonode_secret. No JWT. No cookies. Nothing.
From that single unauthenticated request, the attacker can:
Intercept all traffic – Rewrite server blocks to proxy everything through an attacker-controlled endpoint. Every request, every response, every credential – captured.
Harvest admin credentials – Inject access_log directives with custom log_format patterns that capture Authorization headers from administrators accessing nginx-ui.
Escalate to permanent access – With a captured admin JWT, access the nginx-ui settings API to extract the JwtSecret. Forge admin tokens for any user. Maintain persistent access that survives config cleanup.
Exfiltrate the architecture – Read every nginx config file, mapping backend topology, upstream servers, TLS paths, and internal service addresses.
Kill the service – Write an invalid config, trigger a reload. nginx goes down, and everything behind it goes with it.
Seeing Is Believing
We built a PoC that demonstrates the full attack from a separate machine on the network – not localhost, not the same container, but a completely different host with its own IP address.
The attacker container (172.21.0.3) connects to the nginx-ui target (172.21.0.2), enumerates all 12 tools, exfiltrates the nginx configuration, and injects a new server block – all without any authentication:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ATTACKER MACHINE β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ£
β Hostname: 29c4a25ad0ae β
β IP: 172.21.0.3 β
β Target: http://172.21.0.2:9000 β
β Auth sent: NONE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β POST http://172.21.0.2:9000/mcp_message?sessionId=e336a0ed-...
β From attacker IP: 172.21.0.3
β Authorization: *** NONE ***
β 12 tools accessible from attacker machine
β nginx_config_add (DESTRUCTIVE)
β nginx_config_modify (DESTRUCTIVE)
β restart_nginx (DESTRUCTIVE)
...
β nginx.conf exfiltrated from attacker machine!
β Config injected from attacker machine!
β Nginx auto-reloaded on target
The injected config creates a new server block that immediately begins serving attacker-controlled content – confirming write + reload, all from a remote machine with zero credentials.
This is what the end-to-end exploitation flow looks like – from establishing the SSE session through tool enumeration, config exfiltration, and finally injecting a malicious server block that nginx loads immediately:

Full exploitation: the PoC connects from a separate machine, enumerates 12 unauthenticated tools, reads nginx.conf, injects a malicious config, and nginx auto-reloads – all without any credentials.
Dissecting the Fix
The vulnerability was fixed in v2.3.4 (released March 15, 2026, one day after our report). The fix is exactly what you’d expect – adding the missing AuthRequired() middleware:
r.Any("/mcp_message", middleware.IPWhiteList(),
+ r.Any("/mcp_message", middleware.IPWhiteList(),
middleware.AuthRequired(),
func(c *gin.Context) {
mcp.ServeHTTP(c)
})
One line. 27 characters added: , middleware.AuthRequired(). The same middleware that /mcp already had.
What’s notable is what came alongside the fix in commit 413dc63: the maintainers also added a regression test (mcp/router_test.go) that explicitly verifies both MCP endpoints return 403 when accessed without authentication:
func TestMCPEndpointsRequireAuthentication(t *testing.T) {
settings.AuthSettings.IPWhiteList = nil
router := gin.New()
InitRouter(router)
for _, endpoint := range []string{"/mcp", "/mcp_message"} {
req := httptest.NewRequest(http.MethodPost, endpoint, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusForbidden, w.Code)
}
}
This test would have caught the original vulnerability. Its absence is a reminder that security-critical middleware chains need explicit test coverage – especially when a protocol uses multiple endpoints.
A note on version tracking: The OSV entry lists v2.3.5 as the last affected version, while the GHSA lists <=1.99. Both appear to be incorrect. Based on our source code verification: v2.3.3 is the last vulnerable version, and v2.3.4 contains the fix.
The Pattern: MCP as the Weakest Link
This vulnerability is not an isolated case. It’s a pattern we keep encountering across the MCP ecosystem.
When developers add MCP to an existing application, they’re exposing the app’s most powerful operations through new HTTP endpoints. The web API is authenticated. The WebSocket connections are authenticated. But the MCP endpoints? Those are new, and it’s easy to miss one.
The SSE transport makes this especially tricky because it uses two endpoints. Developers intuitively think of the SSE stream as the “connection” that needs protecting, and the message endpoint as just a data pipe. But in MCP’s architecture, the message endpoint is where the power lives. Protecting the stream but not the message endpoint is like locking the front door while leaving the back door wide open.
We’ve seen variations of this across multiple MCP server implementations:
– Authentication on the SSE endpoint but not the message endpoint
– IP whitelists that default to allow-all
– Security flags documented in READMEs but never checked in code
– OAuth scopes advertised but never enforced
The takeaway for anyone integrating MCP into an existing application: every endpoint in the MCP transport must inherit your full authentication stack. The message endpoint processes all tool invocations, including destructive ones. There is no passive MCP endpoint.
What to Do
Active exploitation of CVE-2026-33032 has been confirmed in the wild. If you are running a vulnerable version, update immediately – do not wait.
If you’re running nginx-ui with MCP enabled:
– Update to v2.3.4 or later immediately
– If you cannot update right now, disable MCP entirely or lock the IP whitelist to trusted hosts as an emergency stopgap – do not leave the default fail-open configuration in place
– Review nginx access logs for unexpected configuration changes
– Check conf.d/ and sites-enabled/ for unfamiliar config files
If you’re building MCP integrations:
– Audit both SSE endpoints – the stream and the message handler
– Test authentication on the message endpoint explicitly (the nginx-ui fix includes a great test template)
– Default fail-closed on IP allowlists
– Treat MCP endpoints with the same security rigor as your most privileged API
Timeline
| Date | Event |
|---|---|
| 2026-03-04 | Vulnerability discovered during MCP ecosystem research |
| 2026-03-04 | Reported via GitHub Private Vulnerability Reporting |
| 2026-03-14 | Fix committed (413dc63) |
| 2026-03-15 | v2.3.4 released with fix |
| 2026-03-28 | CVE-2026-33032 published |
| 2026-03 | CVE-2026-33032 actively exploited in the wild (per Recorded Future Insikt Group March 2026 CVE Landscape report) |
| 2026-04-13 | VulnCheck adds CVE-2026-33032 to its Known Exploited Vulnerabilities (KEV) list; active exploitation publicly confirmed |
What’s Next
This is the second in a series of posts from our MCP security research. We’ve identified and responsibly disclosed vulnerabilities across multiple popular MCP server implementations – from official MCP servers to community projects with thousands of stars. Follow along to stay informed as we release further findings.