GitHub Security Advisory: GHSA-h6c2-x2m2-mwhf
Package: github.com/0xJacky/Nginx-UI (GO)
Severity: CRITICAL (CVSS 9.8)
CWE: CWE-306: Missing Authentication for Critical Function
Vulnerable versions: <= 1.99
Patched in: unpatched

Summary: nginx-ui's Unauthenticated MCP Endpoint Allows Remote Nginx Takeover

Details:
### Summary
The 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.

### Details
#### Vulnerable Code

**`mcp/router.go:9-17` - Auth asymmetry between endpoints**

```go
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)
		})
}
```

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**

```go
func IPWhiteList() gin.HandlerFunc {
	return func(c *gin.Context) {
		clientIP := c.ClientIP()
		if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "" || clientIP == "127.0.0.1" || clientIP == "::1" {
			c.Next()
			return
		}
		// ...
	}
}
```

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 status

From `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 path

#### Attack Scenario

1. Attacker sends HTTP requests to `http://target:9000/mcp_message` (default port)
2. No authentication is required - IP whitelist is empty by default
3. Attacker 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)
4. `nginx_config_add` auto-reloads nginx (`config_add.go:74`), or attacker calls `reload_nginx` directly
5. All traffic through nginx is now under attacker control - requests intercepted, redirected, or denied


### PoC
**1. The auth asymmetry** is visible by comparing the two route registrations in `mcp