VYPR
High severityNVD Advisory· Published Mar 19, 2026· Updated Mar 20, 2026

SiYuan importSY/importZipMd: Path Traversal via multipart filename enables arbitrary file write

CVE-2026-32749

Description

SiYuan is a personal knowledge management system. In versions 3.6.0 and below, POST /api/import/importSY and POST /api/import/importZipMd write uploaded archives to a path derived from the multipart filename field without sanitization, allowing an admin to write files to arbitrary locations outside the temp directory - including system paths that enable RCE. This can lead to aata destruction by overwriting workspace or application files, and for Docker containers running as root (common default), this grants full container compromise. This issue has been fixed in version 3.6.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/siyuan-note/siyuan/kernelGo
<= 0.0.0-20260313024916-fd6526133bb3

Affected products

1

Patches

1
5ee00907f0b0

:lock: https://github.com/siyuan-note/siyuan/security/advisories/GHSA-qvvf-q994-x79v

https://github.com/siyuan-note/siyuanDanielMar 14, 2026via ghsa
3 files changed · +67 28
  • kernel/api/import.go+22 4 modified
    @@ -68,8 +68,17 @@ func importSY(c *gin.Context) {
     		ret.Msg = err.Error()
     		return
     	}
    -	writePath := filepath.Join(util.TempDir, "import", file.Filename)
    +
    +	writePath := filepath.Join(importDir, file.Filename)
    +	if !util.IsSubPath(importDir, writePath) {
    +		logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
    +		ret.Code = -1
    +		ret.Msg = "import path is not sub path of import dir"
    +		return
    +	}
    +
     	defer os.RemoveAll(writePath)
    +
     	writer, err := os.OpenFile(writePath, os.O_RDWR|os.O_CREATE, 0644)
     	if err != nil {
     		logging.LogErrorf("open import .sy.zip [%s] failed: %s", writePath, err)
    @@ -119,14 +128,14 @@ func importData(c *gin.Context) {
     		return
     	}
     
    -	tmpImport := filepath.Join(util.TempDir, "import")
    -	err = os.MkdirAll(tmpImport, 0755)
    +	importDir := filepath.Join(util.TempDir, "import")
    +	err = os.MkdirAll(importDir, 0755)
     	if err != nil {
     		ret.Code = -1
     		ret.Msg = "create temp import dir failed"
     		return
     	}
    -	dataZipPath := filepath.Join(tmpImport, util.CurrentTimeSecondsStr()+".zip")
    +	dataZipPath := filepath.Join(importDir, util.CurrentTimeSecondsStr()+".zip")
     	defer os.RemoveAll(dataZipPath)
     	dataZipFile, err := os.Create(dataZipPath)
     	if err != nil {
    @@ -225,8 +234,17 @@ func importZipMd(c *gin.Context) {
     		ret.Msg = err.Error()
     		return
     	}
    +
     	writePath := filepath.Join(util.TempDir, "import", file.Filename)
    +	if !util.IsSubPath(importDir, writePath) {
    +		logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
    +		ret.Code = -1
    +		ret.Msg = "import path is not sub path of import dir"
    +		return
    +	}
    +
     	defer os.RemoveAll(writePath)
    +
     	writer, err := os.OpenFile(writePath, os.O_RDWR|os.O_CREATE, 0644)
     	if err != nil {
     		logging.LogErrorf("open import .zip [%s] failed: %s", writePath, err)
    
  • kernel/api/sync.go+30 16 modified
    @@ -79,8 +79,15 @@ func importSyncProviderWebDAV(c *gin.Context) {
     		return
     	}
     
    -	tmp := filepath.Join(importDir, f.Filename)
    -	if err = os.WriteFile(tmp, data, 0644); err != nil {
    +	writePath := filepath.Join(importDir, f.Filename)
    +	if !util.IsSubPath(importDir, writePath) {
    +		logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
    +		ret.Code = -1
    +		ret.Msg = "import path is not sub path of import dir"
    +		return
    +	}
    +
    +	if err = os.WriteFile(writePath, data, 0644); err != nil {
     		logging.LogErrorf("import WebDAV provider failed: %s", err)
     		ret.Code = -1
     		ret.Msg = err.Error()
    @@ -89,15 +96,15 @@ func importSyncProviderWebDAV(c *gin.Context) {
     
     	tmpDir := filepath.Join(importDir, "webdav")
     	os.RemoveAll(tmpDir)
    -	if strings.HasSuffix(strings.ToLower(tmp), ".zip") {
    -		if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil {
    +	if strings.HasSuffix(strings.ToLower(writePath), ".zip") {
    +		if err = gulu.Zip.Unzip(writePath, tmpDir); err != nil {
     			logging.LogErrorf("import WebDAV provider failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
     			return
     		}
    -	} else if strings.HasSuffix(strings.ToLower(tmp), ".json") {
    -		if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil {
    +	} else if strings.HasSuffix(strings.ToLower(writePath), ".json") {
    +		if err = gulu.File.CopyFile(writePath, filepath.Join(tmpDir, f.Filename)); err != nil {
     			logging.LogErrorf("import WebDAV provider failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
    @@ -124,8 +131,8 @@ func importSyncProviderWebDAV(c *gin.Context) {
     		return
     	}
     
    -	tmp = filepath.Join(tmpDir, entries[0].Name())
    -	data, err = os.ReadFile(tmp)
    +	writePath = filepath.Join(tmpDir, entries[0].Name())
    +	data, err = os.ReadFile(writePath)
     	if err != nil {
     		logging.LogErrorf("import WebDAV provider failed: %s", err)
     		ret.Code = -1
    @@ -265,8 +272,15 @@ func importSyncProviderS3(c *gin.Context) {
     		return
     	}
     
    -	tmp := filepath.Join(importDir, f.Filename)
    -	if err = os.WriteFile(tmp, data, 0644); err != nil {
    +	writePath := filepath.Join(importDir, f.Filename)
    +	if !util.IsSubPath(importDir, writePath) {
    +		logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
    +		ret.Code = -1
    +		ret.Msg = "import path is not sub path of import dir"
    +		return
    +	}
    +
    +	if err = os.WriteFile(writePath, data, 0644); err != nil {
     		logging.LogErrorf("import S3 provider failed: %s", err)
     		ret.Code = -1
     		ret.Msg = err.Error()
    @@ -275,15 +289,15 @@ func importSyncProviderS3(c *gin.Context) {
     
     	tmpDir := filepath.Join(importDir, "s3")
     	os.RemoveAll(tmpDir)
    -	if strings.HasSuffix(strings.ToLower(tmp), ".zip") {
    -		if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil {
    +	if strings.HasSuffix(strings.ToLower(writePath), ".zip") {
    +		if err = gulu.Zip.Unzip(writePath, tmpDir); err != nil {
     			logging.LogErrorf("import S3 provider failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
     			return
     		}
    -	} else if strings.HasSuffix(strings.ToLower(tmp), ".json") {
    -		if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil {
    +	} else if strings.HasSuffix(strings.ToLower(writePath), ".json") {
    +		if err = gulu.File.CopyFile(writePath, filepath.Join(tmpDir, f.Filename)); err != nil {
     			logging.LogErrorf("import S3 provider failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
    @@ -310,8 +324,8 @@ func importSyncProviderS3(c *gin.Context) {
     		return
     	}
     
    -	tmp = filepath.Join(tmpDir, entries[0].Name())
    -	data, err = os.ReadFile(tmp)
    +	writePath = filepath.Join(tmpDir, entries[0].Name())
    +	data, err = os.ReadFile(writePath)
     	if err != nil {
     		logging.LogErrorf("import S3 provider failed: %s", err)
     		ret.Code = -1
    
  • kernel/api/system.go+15 8 modified
    @@ -441,8 +441,15 @@ func importConf(c *gin.Context) {
     		return
     	}
     
    -	tmp := filepath.Join(importDir, f.Filename)
    -	if err = os.WriteFile(tmp, data, 0644); err != nil {
    +	writePath := filepath.Join(importDir, f.Filename)
    +	if !util.IsSubPath(importDir, writePath) {
    +		logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
    +		ret.Code = -1
    +		ret.Msg = "import path is not sub path of import dir"
    +		return
    +	}
    +
    +	if err = os.WriteFile(writePath, data, 0644); err != nil {
     		logging.LogErrorf("import conf failed: %s", err)
     		ret.Code = -1
     		ret.Msg = err.Error()
    @@ -451,15 +458,15 @@ func importConf(c *gin.Context) {
     
     	tmpDir := filepath.Join(importDir, "conf")
     	os.RemoveAll(tmpDir)
    -	if strings.HasSuffix(strings.ToLower(tmp), ".zip") {
    -		if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil {
    +	if strings.HasSuffix(strings.ToLower(writePath), ".zip") {
    +		if err = gulu.Zip.Unzip(writePath, tmpDir); err != nil {
     			logging.LogErrorf("import conf failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
     			return
     		}
    -	} else if strings.HasSuffix(strings.ToLower(tmp), ".json") {
    -		if err = gulu.File.CopyFile(tmp, filepath.Join(tmpDir, f.Filename)); err != nil {
    +	} else if strings.HasSuffix(strings.ToLower(writePath), ".json") {
    +		if err = gulu.File.CopyFile(writePath, filepath.Join(tmpDir, f.Filename)); err != nil {
     			logging.LogErrorf("import conf failed: %s", err)
     			ret.Code = -1
     			ret.Msg = err.Error()
    @@ -486,8 +493,8 @@ func importConf(c *gin.Context) {
     		return
     	}
     
    -	tmp = filepath.Join(tmpDir, entries[0].Name())
    -	data, err = os.ReadFile(tmp)
    +	writePath = filepath.Join(tmpDir, entries[0].Name())
    +	data, err = os.ReadFile(writePath)
     	if err != nil {
     		logging.LogErrorf("import conf failed: %s", err)
     		ret.Code = -1
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.