From 022fbf633b9bfbbab44c041f53261e5ebc9bccbe Mon Sep 17 00:00:00 2001 From: sysy <2772196789@qq.com> Date: Wed, 16 Sep 2026 23:31:35 -0400 Subject: [PATCH] fix: sanitize archive entry paths before extraction to prevent path traversal Move path sanitization before directory handling in extractFile so directory entries with "../" prefixes are rejected the same way file entries already are. Add the same sanitization to extractSevenZipMultiPart and both zip extraction loops in the Windows updater, which had no path validation at all. --- cmd/updater/updater_windows.go | 14 ++++++++++---- pkg/download/extract.go | 12 ++++-------- pkg/download/extract_7z.go | 8 ++++++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/cmd/updater/updater_windows.go b/cmd/updater/updater_windows.go index 03dd6b42..6f603ca5 100644 --- a/cmd/updater/updater_windows.go +++ b/cmd/updater/updater_windows.go @@ -37,15 +37,17 @@ func installByInstaller(killSignalChan chan<- any, packagePath, destDir string) } defer reader.Close() - // Find the installer file var installerPath string for _, file := range reader.File { + cleanName := filepath.Clean(file.Name) + if strings.HasPrefix(cleanName, "..") || filepath.IsAbs(cleanName) { + continue + } if file.FileInfo().IsDir() { continue } - // Extract file - path := filepath.Join(tempDir, file.Name) + path := filepath.Join(tempDir, cleanName) if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { return err @@ -101,7 +103,11 @@ func installByPortable(killSignalChan chan<- any, packagePath, destDir string) e defer reader.Close() for _, file := range reader.File { - path := filepath.Join(destDir, file.Name) + cleanName := filepath.Clean(file.Name) + if strings.HasPrefix(cleanName, "..") || filepath.IsAbs(cleanName) { + continue + } + path := filepath.Join(destDir, cleanName) if file.FileInfo().IsDir() { os.MkdirAll(path, file.Mode()) diff --git a/pkg/download/extract.go b/pkg/download/extract.go index f092d2f5..2f6ef03c 100644 --- a/pkg/download/extract.go +++ b/pkg/download/extract.go @@ -282,19 +282,15 @@ func countArchiveFiles(archivePath string, password string) (int, error) { // extractFile handles extracting a single file from an archive func extractFile(ctx context.Context, fileInfo archives.FileInfo, destDir string) error { - // Skip directories, they will be created when extracting files - if fileInfo.IsDir() { - destPath := filepath.Join(destDir, fileInfo.NameInArchive) - return os.MkdirAll(destPath, fileInfo.Mode()) - } - - // Sanitize the path to prevent path traversal attacks cleanPath := filepath.Clean(fileInfo.NameInArchive) if strings.HasPrefix(cleanPath, "..") || filepath.IsAbs(cleanPath) { - // Skip files with suspicious paths return nil } + if fileInfo.IsDir() { + return os.MkdirAll(filepath.Join(destDir, cleanPath), fileInfo.Mode()) + } + destPath := filepath.Join(destDir, cleanPath) // Create parent directories diff --git a/pkg/download/extract_7z.go b/pkg/download/extract_7z.go index 9f8365e5..b8886034 100644 --- a/pkg/download/extract_7z.go +++ b/pkg/download/extract_7z.go @@ -4,6 +4,7 @@ import ( "io" "os" "path/filepath" + "strings" "github.com/bodgit/sevenzip" ) @@ -43,7 +44,11 @@ func extractSevenZipMultiPart(firstPartPath string, destDir string, password str // Extract files with progress tracking extractedFiles := 0 for _, f := range reader.File { - destPath := filepath.Join(destDir, f.Name) + cleanName := filepath.Clean(f.Name) + if strings.HasPrefix(cleanName, "..") || filepath.IsAbs(cleanName) { + continue + } + destPath := filepath.Join(destDir, cleanName) if f.FileInfo().IsDir() { if err := os.MkdirAll(destPath, f.Mode()); err != nil { @@ -52,7 +57,6 @@ func extractSevenZipMultiPart(firstPartPath string, destDir string, password str continue } - // Ensure parent directory exists if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil { return err }