Vulnerability Analysis

CVE-2026-33032 (MCPwn): nginx-ui Authentication Bypass Enables Full Nginx Server Takeover

Executive Summary

CVE-2026-33032 is a critical authentication bypass vulnerability (CVSS 9.8) in nginx-ui, the widely used open-source web management interface for Nginx servers, discovered and dubbed MCPwn by Pluto Security. A missing authentication middleware call on the /mcp_message HTTP endpoint allows any network-accessible attacker to invoke all 12 of nginx-ui's Model Context Protocol (MCP) tools without any credentials — enabling Nginx configuration rewrites, service reloads, credential harvesting, and full web server takeover in as few as two HTTP requests. With approximately 2,689 instances publicly exposed on the internet and active exploitation confirmed in the wild, organizations running nginx-ui should treat this as an emergency patch event.


1. What Is This Vulnerability?

nginx-ui is a popular open-source dashboard (GitHub: 0xJacky/nginx-ui) that provides a graphical interface for managing Nginx web server configurations, SSL certificates, log streams, and more. In 2025–2026, nginx-ui added support for the Model Context Protocol (MCP), an emerging standard that allows AI tools and automation clients to interact with the application via a structured API.

The MCP integration exposed two new HTTP endpoints:

Endpoint Purpose
/mcp Initiates an MCP session (SSE stream)
/mcp_message Sends MCP tool invocations within a session

The developers correctly protected /mcp with both IP whitelisting and authentication middleware. However, /mcp_message was protected only by IP whitelisting — and critically, when the IP whitelist is empty (the default configuration), the middleware interprets this as "allow all." Authentication middleware was never applied to /mcp_message.

The result: any attacker who can reach nginx-ui over the network can call any MCP tool directly, with no username, password, token, or session validation required.

Attack Vector

The attack is fully unauthenticated and requires only HTTP access to nginx-ui (typically port 80 or 443). The basic exploitation sequence:

  1. Establish a session — Send a GET request to /mcp to receive a sessionId in the Server-Sent Events (SSE) stream. If the IP whitelist is empty (default), this succeeds without auth.
  2. Invoke MCP tools — POST to /mcp_message with the sessionId and the desired tool name + parameters. No authentication headers are checked.

A concrete example of overwriting the default Nginx config:

POST /mcp_message?sessionId=<session_id> HTTP/1.1
Host: target-nginx-ui.example.com
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_nginx_config",
    "arguments": {
      "filename": "default.conf",
      "content": "server { listen 80; root /var/www/attacker; }"
    }
  }
}

After writing the config, a follow-up call to the reload_nginx MCP tool applies the changes immediately.

Chained Exploitation with CVE-2026-27944

An additional vulnerability, CVE-2026-27944, exposes nginx-ui's encryption keys via the unauthenticated /api/backup endpoint. Attackers can extract the node_secret query parameter from the backup response and use it to obtain a valid sessionId directly — bypassing even the need for the SSE handshake. This reduces the full takeover to a deterministic two-request chain against default-configured instances.

Real-World Impact

  • Recorded Future listed CVE-2026-33032 among 31 actively exploited vulnerabilities in March–April 2026.
  • eSentire published a security advisory confirming observed exploitation campaigns targeting exposed nginx-ui instances.
  • Attackers are using the vulnerability to rewrite Nginx configurations to proxy traffic through attacker-controlled infrastructure, enabling traffic interception, credential harvesting, and lateral movement into internal networks.
  • Some intrusions have resulted in webshell deployment by writing malicious PHP/configuration content via the write_nginx_config tool.

2. Who Is Affected?

Affected software: nginx-ui all versions prior to 2.3.4 with the MCP integration present.

Affected configurations:

  • Any deployment where the MCP IP whitelist is empty (the default out-of-the-box setting)
  • Instances accessible from the internet (Shodan shows ~2,689 exposed globally)
  • Instances accessible from an internal network where attackers have already gained a foothold

Exposure by geography (Shodan data): China, United States, Indonesia, Germany, Hong Kong

Not affected:

  • nginx-ui 2.3.4 and later (MCP endpoint authentication enforced)
  • Deployments with MCP disabled entirely
  • Deployments with a properly configured, restrictive IP whitelist (though upgrading is still strongly recommended)

3. How to Detect It (Testing)

Manual Testing Steps

  1. Identify nginx-ui instances on your network by scanning for the nginx-ui login page (default ports 80/443; look for <title>nginx ui</title> or the nginx-ui favicon hash).

  2. Attempt unauthenticated MCP session establishment:

    curl -N -H "Accept: text/event-stream" \
      "http://<target>/mcp"
    

    A vulnerable instance will return an SSE stream containing a sessionId without prompting for credentials.

  3. Attempt an unauthenticated MCP tool invocation using the sessionId from step 2:

    curl -s -X POST "http://<target>/mcp_message?sessionId=<SESSION_ID>" \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
    

    A vulnerable instance returns the list of available MCP tools (12 total) without authentication. A patched or properly secured instance returns a 401 Unauthorized or 403 Forbidden.

  4. Check the version (if you have read access to the filesystem or admin console):

    • Version string shown in nginx-ui admin footer
    • /api/health endpoint may expose version: look for "version" in the JSON response.
    • If version < 2.3.4, the system is vulnerable.

Automated Scanning

Tool: Nuclei (ProjectDiscovery)

Community templates for CVE-2026-33032 are available:

nuclei -u http://<target> -t cves/2026/CVE-2026-33032.yaml -v

Tool: Shodan CLI (for internet exposure enumeration)

shodan search 'http.title:"nginx ui" country:US' --fields ip_str,port,org

Tool: Nmap NSE (manual script)

nmap -p 80,443 --script http-nginx-ui-mcpwn <target_range>

Tool: Burp Suite — Passive scan will flag the unauthenticated SSE endpoint if the proxy intercepts the MCP handshake. For active scanning, add a custom scan check for /mcp returning 200 without Authorization headers.

Code Review Checklist (for nginx-ui maintainers / contributors)

  • Confirm AuthMiddleware (or equivalent) is applied to both /mcp and /mcp_message route handlers
  • Verify IP whitelist logic returns deny all (not allow all) when the whitelist is empty
  • Audit all new route registrations to confirm security middleware is not accidentally omitted
  • Check whether /api/backup or similar endpoints expose secrets without authentication (CVE-2026-27944 companion fix)

4. How to Fix It (Mitigation)

Step-by-Step Remediation

  1. Upgrade nginx-ui to version 2.3.4 or later — this is the primary fix. Version 2.3.4 was released on March 15, 2026, and applies authentication middleware to /mcp_message.

    # Check current version
    nginx-ui --version   # or check the admin panel footer
    
    # Stop nginx-ui service
    systemctl stop nginx-ui
    
    # Download latest release (adjust URL to your architecture)
    wget https://github.com/0xJacky/nginx-ui/releases/latest/download/nginx-ui-linux-amd64.tar.gz
    tar -xzf nginx-ui-linux-amd64.tar.gz
    mv nginx-ui /usr/local/bin/nginx-ui
    
    # Restart the service
    systemctl start nginx-ui
    
    # Verify new version
    nginx-ui --version
    
  2. If immediate upgrade is not possible — disable MCP or restrict access:

    In your nginx-ui app.ini configuration, disable the MCP integration entirely:

    [mcp]
    enabled = false
    

    Or enforce a restrictive IP whitelist:

    [mcp]
    enabled = true
    ip_whitelist = 127.0.0.1,10.0.1.5
    

    Restart nginx-ui after making this change.

  3. Block the vulnerable endpoints at the network perimeter (belt-and-suspenders):

    If nginx-ui is behind an Nginx reverse proxy (common setup), add location blocks to reject external access to MCP endpoints:

    location ~ ^/mcp(_message)?$ {
        allow 127.0.0.1;
        allow 10.0.0.0/8;   # internal only
        deny all;
    }
    
  4. Rotate credentials and audit Nginx configurations — if you suspect exploitation has occurred:

    • Review all Nginx configuration files for unauthorized changes
    • Check nginx-ui access logs for POST requests to /mcp_message from unexpected IPs
    • Rotate nginx-ui admin passwords and any credentials stored in configurations
    • Check for unexpected processes or webshells in your web root

Code Fix Example

The root cause was a missing middleware application in the route handler. The fixed version adds AuthMiddleware to the /mcp_message route:

Before (vulnerable):

// Route registration in router.go (pre-2.3.4)
mcpGroup := r.Group("/mcp")
{
    mcpGroup.GET("", middleware.IPWhitelist(), api.MCPHandler)
    // Missing: AuthMiddleware on mcp_message
    mcpGroup.POST("_message", middleware.IPWhitelist(), api.MCPMessageHandler)
}

After (patched):

// Route registration in router.go (2.3.4+)
mcpGroup := r.Group("/mcp")
{
    mcpGroup.GET("", middleware.IPWhitelist(), middleware.AuthMiddleware(), api.MCPHandler)
    mcpGroup.POST("_message", middleware.IPWhitelist(), middleware.AuthMiddleware(), api.MCPMessageHandler)
}

Additionally, the IP whitelist logic was corrected to default-deny when the whitelist is empty:

// Fixed IPWhitelist middleware behavior
func IPWhitelist() gin.HandlerFunc {
    return func(c *gin.Context) {
        whitelist := config.GetMCPIPWhitelist()
        if len(whitelist) == 0 {
            // FIXED: deny all when whitelist is empty (was: allow all)
            c.AbortWithStatus(http.StatusForbidden)
            return
        }
        // ... check if client IP is in whitelist
    }
}

Configuration Hardening

Even after patching, apply these hardening steps:

  • Restrict nginx-ui to non-public networks. The admin interface should never be directly internet-accessible. Place it behind a VPN or bastion host.
  • Enable Two-Factor Authentication (2FA) in nginx-ui admin settings.
  • Use a reverse proxy with mTLS for nginx-ui access in high-security environments.
  • Set a restrictive MCP IP whitelist even on patched versions, as defense-in-depth.
  • Enable audit logging to capture all configuration changes with timestamps and actor information.

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: After upgrading to 2.3.4+, repeat the unauthenticated MCP session probe — confirm /mcp returns 401 Unauthorized without a valid session token.
  • Scenario B: Confirm that a legitimate authenticated user can still use MCP tools normally after the upgrade (no broken functionality).
  • Scenario C: Verify that disabling MCP in app.ini causes both /mcp and /mcp_message to return 404 Not Found.
  • Scenario D: Verify Nginx configuration files have not been tampered with by comparing file hashes against a known-good baseline.

Security Test Cases

Test Case 1: Verify unauthenticated MCP session is rejected

  • Precondition: nginx-ui upgraded to 2.3.4+; no session cookie / auth token in request
  • Steps:
    curl -N -H "Accept: text/event-stream" "http://<target>/mcp"
    
  • Expected Result: HTTP 401 or 403 — no sessionId returned

Test Case 2: Verify unauthenticated tool invocation is rejected

  • Precondition: nginx-ui 2.3.4+; attempt to POST to /mcp_message with a fabricated/expired sessionId
  • Steps:
    curl -X POST "http://<target>/mcp_message?sessionId=fake123" \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
    
  • Expected Result: HTTP 401 or 403 — tool list not returned

Test Case 3: Verify authenticated access still works

  • Precondition: Valid admin credentials available; nginx-ui 2.3.4+
  • Steps: Log in via the nginx-ui web interface and navigate to MCP-enabled features
  • Expected Result: Full functionality accessible; no regressions

Automated Tests

import requests

NGINX_UI_URL = "http://your-nginx-ui-host"

def test_mcp_unauthenticated_blocked():
    """CVE-2026-33032 regression: unauthenticated /mcp access must be rejected."""
    response = requests.get(
        f"{NGINX_UI_URL}/mcp",
        headers={"Accept": "text/event-stream"},
        timeout=5,
        stream=True
    )
    assert response.status_code in (401, 403), (
        f"FAIL: /mcp returned {response.status_code} — unauthenticated access may be allowed"
    )
    print(f"PASS: /mcp returned {response.status_code} (authentication required)")

def test_mcp_message_unauthenticated_blocked():
    """CVE-2026-33032 regression: unauthenticated /mcp_message must be rejected."""
    payload = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
    response = requests.post(
        f"{NGINX_UI_URL}/mcp_message?sessionId=invalid",
        json=payload,
        timeout=5
    )
    assert response.status_code in (401, 403), (
        f"FAIL: /mcp_message returned {response.status_code} — unauthenticated access may be allowed"
    )
    print(f"PASS: /mcp_message returned {response.status_code} (authentication required)")

if __name__ == "__main__":
    test_mcp_unauthenticated_blocked()
    test_mcp_message_unauthenticated_blocked()

6. Prevention & Hardening

Best Practices

  • Never expose management interfaces to the internet. nginx-ui, like all admin dashboards, should be accessible only from trusted networks or via VPN. This single practice would eliminate exposure for the majority of the ~2,689 currently internet-facing instances.
  • Apply the principle of least privilege to MCP integrations. When integrating AI/automation protocols like MCP into existing applications, treat each new endpoint as a fresh attack surface — not as an extension of the existing session model. Every endpoint needs its own explicit auth checks.
  • Pin dependency versions and monitor upstream security advisories. Subscribe to the nginx-ui GitHub repository's security advisories and set up automated alerts for new releases.
  • Implement Web Application Firewall (WAF) rules for /mcp and /mcp_message paths, requiring session tokens and blocking unexpected request patterns.
  • Run regular vulnerability scans (monthly minimum) against all internet-facing services. CVE-2026-33032 was publicly known in March 2026; unpatched instances in April 2026 represent over a month of exposure.

Monitoring & Detection

Set up the following detection rules in your SIEM or log analysis platform:

Nginx access log detection — alert on POST requests to /mcp_message from non-whitelisted IPs:

# Example: grep for suspicious MCP hits in nginx access logs
grep 'POST /mcp_message' /var/log/nginx/access.log | \
  awk '{print $1, $7, $9}' | \
  grep -v '200\|204'

nginx-ui application log — look for configuration write events without a preceding authenticated session:

grep -E "(write_nginx_config|reload_nginx)" /var/log/nginx-ui/access.log

Network-level detection — create an IDS/IPS rule (Suricata example):

alert http any any -> $HTTP_SERVERS any (
  msg:"CVE-2026-33032 nginx-ui MCP unauthenticated tool invocation attempt";
  flow:established,to_server;
  http.method; content:"POST";
  http.uri; content:"/mcp_message"; startswith;
  threshold:type limit,track by_src,count 1,seconds 60;
  classtype:web-application-attack;
  sid:2026033201;
  rev:1;
)

Shodan/Censys monitoring — set up alerts to notify you if nginx-ui admin interfaces on your IP ranges become internet-accessible unexpectedly.


References

Latest from the blog

See all →