VYPR
High severityNVD Advisory· Published Jan 11, 2024· Updated Jun 17, 2025

Authenticated (user role) remote command execution by modifying `nginx` settings (GHSL-2023-269)

CVE-2024-22197

Description

Nginx-ui is online statistics for Server Indicators​​ Monitor CPU usage, memory usage, load average, and disk usage in real-time. 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. This issue may lead to authenticated Remote Code Execution, Privilege Escalation, and Information Disclosure. This issue has been patched in version 2.0.0.beta.9.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Nginx-UI exposes sensitive API settings that let any authenticated user overwrite commands, leading to remote code execution, privilege escalation, and info disclosure.

Root

Cause

Nginx-UI, a web interface for Nginx, exposes an API endpoint to save settings (POST /api/settings). While the UI only shows benign options like log paths, the API also allows modifying test_config_cmd, reload_cmd, and restart_cmd fields. These fields are not restricted in the SaveSettings handler, so any authenticated user can change them [3].

Exploitation

An attacker with a valid user account can send a crafted POST request to /api/settings containing a malicious test_config_cmd value. The backend then passes this value directly to /bin/sh -c via the execShell function without sanitization, enabling arbitrary command execution [3]. No special privileges beyond authentication are needed; the endpoint is protected only by a JWT token or node secret, not by role-based access control [3].

Impact

Successful exploitation allows authenticated attackers to execute arbitrary operating system commands with the privileges of the Nginx-UI process. This can lead to full compromise of the server, including data exfiltration, installation of backdoors, or lateral movement within the network. Additionally, the attacker could escalate their own privileges or extract sensitive configuration data [1][2].

Mitigation

The vulnerability has been patched in Nginx-UI version 2.0.0.beta.9. The fix introduces a fillSettings function that omits protected fields (such as command settings) during save operations, preventing unauthorized modification [4]. Users are strongly advised to upgrade immediately. No workarounds are documented.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/0xJacky/Nginx-UIGo
< 2.0.0.beta.92.0.0.beta.9

Affected products

2

Patches

1
827e76c46e63

fix: add protected fields to settings to mitigate high-severity vulnerability

https://github.com/0xJacky/nginx-uiHintayDec 19, 2023via ghsa
3 files changed · +61 46
  • api/system/settings.go+46 31 modified
    @@ -1,42 +1,57 @@
     package system
     
     import (
    -    "github.com/0xJacky/Nginx-UI/api"
    -    "github.com/0xJacky/Nginx-UI/settings"
    -    "github.com/gin-gonic/gin"
    -    "net/http"
    +	"github.com/0xJacky/Nginx-UI/api"
    +	"github.com/0xJacky/Nginx-UI/settings"
    +	"github.com/gin-gonic/gin"
    +	"net/http"
    +	"reflect"
     )
     
     func GetSettings(c *gin.Context) {
    -    c.JSON(http.StatusOK, gin.H{
    -        "server": settings.ServerSettings,
    -        "nginx":  settings.NginxSettings,
    -        "openai": settings.OpenAISettings,
    -    })
    +	c.JSON(http.StatusOK, gin.H{
    +		"server": settings.ServerSettings,
    +		"nginx":  settings.NginxSettings,
    +		"openai": settings.OpenAISettings,
    +	})
     }
     
     func SaveSettings(c *gin.Context) {
    -    var json struct {
    -        Server settings.Server `json:"server"`
    -        Nginx  settings.Nginx  `json:"nginx"`
    -        Openai settings.OpenAI `json:"openai"`
    -    }
    -
    -    if !api.BindAndValid(c, &json) {
    -        return
    -    }
    -
    -    settings.ServerSettings = json.Server
    -    settings.NginxSettings = json.Nginx
    -    settings.OpenAISettings = json.Openai
    -
    -    settings.ReflectFrom()
    -
    -    err := settings.Save()
    -    if err != nil {
    -        api.ErrHandler(c, err)
    -        return
    -    }
    +	var json struct {
    +		Server settings.Server `json:"server"`
    +		Nginx  settings.Nginx  `json:"nginx"`
    +		Openai settings.OpenAI `json:"openai"`
    +	}
    +
    +	if !api.BindAndValid(c, &json) {
    +		return
    +	}
    +
    +	// todo: omit protected fields when binding
    +	fillSettings(&settings.ServerSettings, &json.Server)
    +	fillSettings(&settings.NginxSettings, &json.Nginx)
    +	fillSettings(&settings.OpenAISettings, &json.Openai)
    +
    +	settings.ReflectFrom()
    +
    +	err := settings.Save()
    +	if err != nil {
    +		api.ErrHandler(c, err)
    +		return
    +	}
    +
    +	GetSettings(c)
    +}
     
    -    GetSettings(c)
    +func fillSettings(targetSettings interface{}, newSettings interface{}) {
    +	s := reflect.TypeOf(targetSettings).Elem()
    +	vt := reflect.ValueOf(targetSettings).Elem()
    +	vn := reflect.ValueOf(newSettings).Elem()
    +
    +	// copy the values from new to target settings if it is not protected
    +	for i := 0; i < s.NumField(); i++ {
    +		if s.Field(i).Tag.Get("protected") != "true" {
    +			vt.Field(i).Set(vn.Field(i))
    +		}
    +	}
     }
    
  • settings/nginx.go+5 5 modified
    @@ -3,11 +3,11 @@ package settings
     type Nginx struct {
     	AccessLogPath string `json:"access_log_path"`
     	ErrorLogPath  string `json:"error_log_path"`
    -	ConfigDir     string `json:"config_dir"`
    -	PIDPath       string `json:"pid_path"`
    -	TestConfigCmd string `json:"test_config_cmd"`
    -	ReloadCmd     string `json:"reload_cmd"`
    -	RestartCmd    string `json:"restart_cmd"`
    +	ConfigDir     string `json:"config_dir" protected:"true"`
    +	PIDPath       string `json:"pid_path" protected:"true"`
    +	TestConfigCmd string `json:"test_config_cmd" protected:"true"`
    +	ReloadCmd     string `json:"reload_cmd" protected:"true"`
    +	RestartCmd    string `json:"restart_cmd" protected:"true"`
     }
     
     var NginxSettings = Nginx{
    
  • settings/server.go+10 10 modified
    @@ -1,18 +1,18 @@
     package settings
     
     type Server struct {
    -	HttpHost          string `json:"http_host"`
    -	HttpPort          string `json:"http_port"`
    -	RunMode           string `json:"run_mode"`
    -	JwtSecret         string `json:"jwt_secret"`
    -	NodeSecret        string `json:"node_secret"`
    +	HttpHost          string `json:"http_host" protected:"true"`
    +	HttpPort          string `json:"http_port" protected:"true"`
    +	RunMode           string `json:"run_mode" protected:"true"`
    +	JwtSecret         string `json:"jwt_secret" protected:"true"`
    +	NodeSecret        string `json:"node_secret" protected:"true"`
     	HTTPChallengePort string `json:"http_challenge_port"`
    -	Email             string `json:"email"`
    -	Database          string `json:"database"`
    -	StartCmd          string `json:"start_cmd"`
    +	Email             string `json:"email" protected:"true"`
    +	Database          string `json:"database" protected:"true"`
    +	StartCmd          string `json:"start_cmd" protected:"true"`
     	CADir             string `json:"ca_dir"`
    -	Demo              bool   `json:"demo"`
    -	PageSize          int    `json:"page_size"`
    +	Demo              bool   `json:"demo" protected:"true"`
    +	PageSize          int    `json:"page_size" protected:"true"`
     	GithubProxy       string `json:"github_proxy"`
     }
     
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

7

News mentions

0

No linked articles in our index yet.