CVE-2024-22197 (published 2024-01-11T18:15Z)

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.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-77
cwe_name: Improper Neutralization of Special Elements used in a Command ('Command Injection')
description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
extended_description: 
            Many protocols and products have their own custom command language. While OS or shell command strings are frequently discovered and targeted, developers may not realize that these other command languages might also be vulnerable to attacks.
         
         

Fix information:
repo_url: https://github.com/0xJacky/nginx-ui
commit_details: repo_url: https://github.com/0xJacky/nginx-ui
author: Hintay
author_date: 2023-12-20 03:44:14+09:00
msg: fix: add protected fields to settings to mitigate high-severity vulnerability

Credits to @jorgectf for the advisories.
repository: repo_url: https://github.com/0xJacky/nginx-ui
repo_name: 0xJacky/nginx-ui
description: Yet another WebUI for Nginx
repo_language: Vue
owner: 0xJacky
file_changes: filename: settings.go
change_type: ModificationType.MODIFY
diff: @@ -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))
+		}
+	}
 }

programming_language: Go
filename: nginx.go
change_type: ModificationType.MODIFY
diff: @@ -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{

programming_language: Go
filename: server.go
change_type: ModificationType.MODIFY
diff: @@ -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"`
 }
 

programming_language: Go