GHSA-h6c2-x2m2-mwhf nginx-ui's Unauthenticated MCP Endpoint Allows Remote Nginx Takeover
SummaryThe 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". This means 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.DetailsVulnerable Code**`mcp/router.go:9-17` - Auth asymmetry between endpoints**The `/mcp` endpoint has `middleware.AuthRequired()`, but `/mcp_message` does not. Both endpoints route to the same `mcp.ServeHTTP()` handler, which processes all MCP tool invocations.**`internal/middleware/ip_whitelist.go:11-26` - Empty whitelist allows all**When `IPWhiteList` is empty (the default - `settings/auth.go` initializes `Auth{}` with no whitelist), the middleware allows all requests through. This is a fail-open design.Available MCP Tools (all invocable without auth)From `mcp/nginx/`:`restart_nginx` - restart the nginx process`reload_nginx` - reload nginx configuration`nginx_status` - read nginx statusFrom `mcp/config/`:`nginx_config_add` - create new nginx config files`nginx_config_modify` - modify existing config files`nginx_config_list` - list all configurations`nginx_config_get` - read config file contents`nginx_config_enable` - enable/disable sites`nginx_config_rename` - rename config files`nginx_config_mkdir` - create directories`nginx_config_history` - view config history`nginx_config_base_path` - get nginx config directory pathAttack ScenarioAttacker sends HTTP requests to `http://target:9000/mcp_message` (default port)No authentication is required - IP whitelist is empty by defaultAttacker invokes `nginx_config_modify` with `relative_path="nginx.conf"` to rewrite the main nginx configuration (e.g., inject a reverse proxy that logs `Authorization` headers)`nginx_config_add` auto-reloads nginx (`config_add.go:74`), or attacker calls `reload_nginx` directlyAll traffic through nginx is now under attacker control - requests intercepted, redirected, or deniedPoC**1. The auth asymmetry** is visible by comparing the two route registrations in `mcp/router.go`:Both call the same `mcp.ServeHTTP(c)` handler, which dispatches all tool invocations.**2. The IP whitelist defaults to empty**, allowing all IPs. From `settings/auth.go`:And the middleware at `internal/middleware/ip_whitelist.go:14` passes all requests when the list is empty:**3. Config writes auto-reload nginx.** From `mcp/config/config_add.go`:**4. Exploit request.** An attacker with network access to port 9000 can invoke any MCP tool via the SSE message endpoint. For example, to create a malicious nginx config that logs authorization headers:No `Authorization` header is needed. The config is written and nginx reloads immediately.Impact**Complete nginx service takeover**: An unauthenticated attacker can create, modify, and delete any nginx configuration file within the config directory, then trigger immediate reload/restart**Traffic interception**: Attacker can rewrite server blocks to proxy all traffic through an attacker-controlled endpoint, capturing credentials, session tokens, and sensitive data in transit**Service disruption**: Writing an invalid config and triggering reload takes nginx offline, affecting all proxied services**Configuration exfiltration**: All existing nginx configs are readable via `nginx_config_get`, revealing backend topology, upstream servers, TLS certificate paths, and authentication headers**Credential harvesting**: By injecting `access_log` directives with custom `log_format` patterns, the attacker can capture `Authorization` headers from administrators accessing nginx-ui, enabling escalation to the REST APIRemediationAdd `middleware.AuthRequired()` to the `/mcp_message` route:Additionally, consider changing the IP whitelist default behavior to deny-all when unconfigured, rather than allow-all.
CVSS v3.1 score: 9.8