CVE-2018-1002207 (published 2018-07-25T17:29Z)

mholt/archiver golang package before e4ef56d48eb029648b0e895bb0b6a393ef0829c3 is vulnerable to directory traversal, allowing attackers to write to arbitrary files via a ../ (dot dot slash) in an archive entry that is mishandled during extraction. This vulnerability is also known as 'Zip-Slip'.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/mholt/archiver
commit_details: repo_url: https://github.com/mholt/archiver
author: aviadatsnyk
author_date: 2018-04-18 01:02:35+03:00
msg: fix: prevent extraction of archived files outside target path (#65)

* fix: prevent extraction of archived files outside target path

* CR: consolidate the path sanitaiton logic
repository: repo_url: https://github.com/mholt/archiver
repo_name: mholt/archiver
description: Easily create & extract archives, and compress & decompress files of various formats
repo_language: Go
owner: mholt
file_changes: filename: archiver.go
change_type: ModificationType.MODIFY
diff: @@ -7,6 +7,7 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 )
 
 // Archiver represent a archive format
@@ -105,3 +106,14 @@ func mkdir(dirPath string) error {
 	}
 	return nil
 }
+
+func sanitizeExtractPath(filePath string, destination string) error {
+	// to avoid zip slip (writing outside of the destination), we resolve
+	// the target path, and make sure it's nested in the intended
+	// destination, or bail otherwise.
+	destpath := filepath.Join(destination, filePath)
+	if !strings.HasPrefix(destpath, destination) {
+		return fmt.Errorf("%s: illegal file path", filePath)
+	}
+	return nil
+}

programming_language: Go
filename: main.go
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,10 @@ func main() {
 		}
 		err = ff.Make(filename, os.Args[3:])
 	case "open":
-		dest := ""
+		dest, osErr := os.Getwd()
+		if osErr != nil {
+			fatal(err)
+		}
 		if len(os.Args) == 4 {
 			dest = os.Args[3]
 		} else if len(os.Args) > 4 {

programming_language: Go
filename: rar.go
change_type: ModificationType.MODIFY
diff: @@ -72,8 +72,15 @@ func (rarFormat) Read(input io.Reader, destination string) error {
 			return err
 		}
 
+		err = sanitizeExtractPath(header.Name, destination)
+		if err != nil {
+			return err
+		}
+
+		destpath := filepath.Join(destination, header.Name)
+
 		if header.IsDir {
-			err = mkdir(filepath.Join(destination, header.Name))
+			err = mkdir(destpath)
 			if err != nil {
 				return err
 			}
@@ -82,12 +89,12 @@ func (rarFormat) Read(input io.Reader, destination string) error {
 
 		// if files come before their containing folders, then we must
 		// create their folders before writing the file
-		err = mkdir(filepath.Dir(filepath.Join(destination, header.Name)))
+		err = mkdir(filepath.Dir(destpath))
 		if err != nil {
 			return err
 		}
 
-		err = writeNewFile(filepath.Join(destination, header.Name), rr, header.Mode())
+		err = writeNewFile(destpath, rr, header.Mode())
 		if err != nil {
 			return err
 		}

programming_language: Go
filename: tar.go
change_type: ModificationType.MODIFY
diff: @@ -219,15 +219,22 @@ func untar(tr *tar.Reader, destination string) error {
 
 // untarFile untars a single file from tr with header header into destination.
 func untarFile(tr *tar.Reader, header *tar.Header, destination string) error {
+	err := sanitizeExtractPath(header.Name, destination)
+	if err != nil {
+		return err
+	}
+
+	destpath := filepath.Join(destination, header.Name)
+
 	switch header.Typeflag {
 	case tar.TypeDir:
-		return mkdir(filepath.Join(destination, header.Name))
+		return mkdir(destpath)
 	case tar.TypeReg, tar.TypeRegA, tar.TypeChar, tar.TypeBlock, tar.TypeFifo:
-		return writeNewFile(filepath.Join(destination, header.Name), tr, header.FileInfo().Mode())
+		return writeNewFile(destpath, tr, header.FileInfo().Mode())
 	case tar.TypeSymlink:
-		return writeNewSymbolicLink(filepath.Join(destination, header.Name), header.Linkname)
+		return writeNewSymbolicLink(destpath, header.Linkname)
 	case tar.TypeLink:
-		return writeNewHardLink(filepath.Join(destination, header.Name), filepath.Join(destination, header.Linkname))
+		return writeNewHardLink(destpath, filepath.Join(destination, header.Linkname))
 	default:
 		return fmt.Errorf("%s: unknown type flag: %c", header.Name, header.Typeflag)
 	}

programming_language: Go
filename: zip.go
change_type: ModificationType.MODIFY
diff: @@ -187,6 +187,11 @@ func unzipAll(r *zip.Reader, destination string) error {
 }
 
 func unzipFile(zf *zip.File, destination string) error {
+	err := sanitizeExtractPath(zf.Name, destination)
+	if err != nil {
+		return err
+	}
+
 	if strings.HasSuffix(zf.Name, "/") {
 		return mkdir(filepath.Join(destination, zf.Name))
 	}

programming_language: Go