CVE-2023-28105: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: go-used-util has commonly used utility functions for Go. Versions prior to 0.0.34 have a ZipSlip issue when using fsutil package to unzip files. When users use `zip.Unzip` to unzip zip files from a malicious attacker, they may be vulnerable to path traversal. The issue has been fixed in version 0.0.34. There are no known workarounds.
CWE 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.
CVSS v3 base score: 8.8
Published: 2023-03-16T17:15Z
Repository: https://github.com/dablelv/go-huge-util
Language: Go
Files changed: README.md, file/file.go, zip/unzip.go, zip/zip.go
Commit date: 2023-03-14 16:14:22 +0800
Commit message: fix zip.Unzip path traversal vulnerability and add some new file utility functions

Diff: diff --git a/README.md b/README.md
index 5a786f6..ffad2d9 100644
--- a/README.md
+++ b/README.md
@@ -19,10 +19,28 @@ Some useful functions can be used to handle conversion of different character en
 ```go
 gbkStr := []byte{0xC4, 0xE3, 0xBA, 0xC3}    // 你好 in gbk
 utf8Str, _ := GbkToUtf8(gbkStr)             // 你好 in utf8
 gbkStrRes, _ := Utf8ToGbk(utf8Str)          // [196 227 186 195]
 ```
+Some converting function to json.
+```go
+student := struct {
+    Hobby   string
+    Age     int32
+}{
+    Hobby: "pingpopng",
+    Age:   28,
+}
+encoding.ToIndentJSON(&student)
+/*
+output:
+{
+    "Hobby": "pingpopng",
+    "Age": 28
+}
+*/
+```
 # Net
 Some useful functions can be used to handle network. For example you can use `IPv4StrToU32()` transform ipv4 string to uint32 value.
 
 ```go
 IsReservedIP("0.0.0.256")    // -1 invalid ip
@@ -327,30 +345,42 @@ math.GetRandByteSlice(3)        // [241 16 101]
 math.GetRandStr(3)              // dAt
 math.GetRandLowerStr(3)         // lts
 math.GetRandUpperStr(3)         // YUT
 ```
 
-# JSON
-
-Some converting function to json.
+# File
 
 ```go
-student := struct {
-    Hobby   string
-    Age     int32
-}{
-    Hobby: "pingpopng",
-    Age:   28,
-}
-encoding.ToIndentJSON(&student)
-/*
-output:
-{
-    "Hobby": "pingpopng",
-    "Age": 28
-}
-*/
+// ListDir lists all the file or directory names in the specified directory.
+ListDir()
+
+// IsExist checks whether a file/dir exists.
+IsExist()
+
+// IsDir checks whether a path is a directory.
+IsDir()
+IsDirE()
+
+// IsFile checks whether a path is a file.
+IsFile()
+IsFileE()
+
+// IsSymlink checks a file whether is a symbolic link file on Linux.
+IsSymlink()
+IsSymlinkE()
+
+// IsShortcut checks a file whether is a shortcut on Windows. 
+IsShortcutFile()
+
+// Create a file.
+Create()
+CreateFile()
+
+// ClearFile clears a file content.
+ClearFile()
+
+// ...
 ```
 
 # Comparison
 
 Some useful functions to compare.
diff --git a/file/file.go b/file/file.go
index 9a9a8a2..78ac5d6 100644
--- a/file/file.go
+++ b/file/file.go
@@ -53,11 +53,11 @@ func ReadLinesV2(path string) ([]string, int, error) {
 			return lines, lineCount, err
 		}
 	}
 }
 
-// ListDir lists all the file or dir names in the specified directory.
+// ListDir lists all the file or directory names in the specified directory.
 // Note that ListDir don't traverse recursively.
 func ListDir(dirname string) ([]string, error) {
 	infos, err := ioutil.ReadDir(dirname)
 	if err != nil {
 		return nil, err
@@ -67,16 +67,16 @@ func ListDir(dirname string) ([]string, error) {
 		names[i] = info.Name()
 	}
 	return names, nil
 }
 
-// IsPathExist checks whether a file/dir exists.
+// IsExist checks whether a file/dir exists.
 // Use os.Stat to get the info of the target file or dir to check whether exists.
 // If os.Stat returns nil err, the target exists.
 // If os.Stat returns a os.ErrNotExist err, the target does not exist.
 // If the error returned is another type, the target is uncertain whether exists.
-func IsPathExist(path string) (bool, error) {
+func IsExist(path string) (bool, error) {
 	_, err := os.Stat(path)
 	if err == nil {
 		return true, nil
 	}
 	if os.IsNotExist(err) {
@@ -121,50 +121,62 @@ func IsFileE(path string) (bool, error) {
 		return true, nil
 	}
 	return false, err
 }
 
-// IsSymlink checks a file whether is a symbolic link.
+// IsSymlink checks a file whether is a symbolic link on Linux.
 // Note that this doesn't work for the shortcut file on windows.
+// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
 func IsSymlink(path string) bool {
 	if info, err := os.Lstat(path); err == nil && info.Mode()&os.ModeSymlink != 0 {
 		return true
 	}
 	return false
 }
 
-// IsSymlinkE checks a file whether is a symbolic link.
+// IsSymlinkE checks a file whether is a symbolic link on Linux.
 // Note that this doesn't work for the shortcut file on windows.
+// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
 func IsSymlinkE(path string) (bool, error) {
 	info, err := os.Lstat(path)
 	if err == nil && info.Mode()&os.ModeSymlink != 0 {
 		return true, nil
 	}
 	return false, err
 }
 
+// IsShortcut checks a file whether is a shortcut on Windows.
+func IsShortcut(path string) bool {
+	ext := filepath.Ext(path)
+	if ext == ".lnk" {
+		return true
+	}
+	return false
+}
+
 // RemoveFile removes the named file or empty directory.
-// https://gist.github.com/novalagung/13c5c8f4d30e0c4bff27
+// If there is an error, it will be of type *PathError.
 func RemoveFile(path string) error {
-	err := os.Remove(path)
-	return err
+	return os.Remove(path)
 }
 
 // Create creates or truncates the target file specified by path.
-// If the parent directory does not exist, it will be created with mode os.ModePerm.is cr truncated.
+// If the parent directory does not exist, it will be created with mode os.ModePerm.
 // If the file does not exist, it is created with mode 0666.
-// If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR.
-func Create(filePath string) (*os.File, error) {
-	if exist, err := IsPathExist(filePath); err != nil {
+// If successful, methods on the returned file can be used for I/O; the associated file descriptor has mode O_RDWR.
+func Create(path string) (*os.File, error) {
+	exist, err := IsExist(path)
+	if err != nil {
 		return nil, err
-	} else if exist {
-		return os.Create(filePath)
 	}
-	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
+	if exist {
+		return os.Create(path)
+	}
+	if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
 		return nil, err
 	}
-	return os.Create(filePath)
+	return os.Create(path)
 }
 
 // CreateFile creates a file specified by path.
 func CreateFile(filePath string) error {
 	file, err := Create(filePath)
@@ -179,19 +191,20 @@ func CreateFile(filePath string) error {
 func FileToBytes(path string) []byte {
 	byteStream, _ := ioutil.ReadFile(path)
 	return byteStream
 }
 
-// BytesToFile writes data to a file. If the file does not exist it will be created with permission mode 0644.
-func BytesToFile(filePath string, data []byte) error {
-	exist, _ := IsPathExist(filePath)
+// BytesToFile writes data to a file.
+// If the file does not exist it will be created with permission mode 0644.
+func BytesToFile(path string, data []byte) error {
+	exist, _ := IsExist(path)
 	if !exist {
-		if err := CreateFile(filePath); err != nil {
+		if err := CreateFile(path); err != nil {
 			return err
 		}
 	}
-	return ioutil.WriteFile(filePath, data, 0644)
+	return ioutil.WriteFile(path, data, 0644)
 }
 
 // GetDirAllEntryPaths gets all the file or dir paths in the specified directory recursively.
 // If the incl is true result will include current dir.
 // Note that GetDirAllEntryPaths won't follow symlink if the subdir is a symbolic link.
@@ -258,5 +271,15 @@ func GetDirAllEntryPathsFollowSymlink(dirname string, incl bool) ([]string, erro
 		}
 		paths = append(paths, path)
 	}
 	return paths, nil
 }
+
+// ClearFile clears a file content.
+func ClearFile(path string) error {
+	f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0777)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return nil
+}
diff --git a/zip/unzip.go b/zip/unzip.go
index 9aad92d..09efc69 100644
--- a/zip/unzip.go
+++ b/zip/unzip.go
@@ -4,10 +4,11 @@ import (
 	"archive/zip"
 	"io"
 	"os"
 	"path"
 	"path/filepath"
+	"strings"
 )
 
 // Unzip decompresses a zip file to specified directory.
 // Note that the destination directory don't need to specify the trailing path separator.
 func Unzip(zipPath, dstDir string) error {
@@ -24,35 +25,39 @@ func Unzip(zipPath, dstDir string) error {
 	}
 	return nil
 }
 
 func unzipFile(file *zip.File, dstDir string) error {
-	// create the directory of file
-	filePath := path.Join(dstDir, file.Name)
+	// Prevent path traversal vulnerability.
+	// Such as if the file name is "../../../path/to/file.txt" which will be cleaned to "path/to/file.txt".
+	name := strings.TrimPrefix(filepath.Join(string(filepath.Separator), file.Name), string(filepath.Separator))
+	filePath := path.Join(dstDir, name)
+
+	// Create the directory of file.
 	if file.FileInfo().IsDir() {
 		if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
 			return err
 		}
 		return nil
 	}
 	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
 		return err
 	}
 
-	// open the file
+	// Open the file.
 	r, err := file.Open()
 	if err != nil {
 		return err
 	}
 	defer r.Close()
 
-	// create the file
+	// Create the file.
 	w, err := os.Create(filePath)
 	if err != nil {
 		return err
 	}
 	defer w.Close()
 
-	// save the decompressed file content
+	// Save the decompressed file content.
 	_, err = io.Copy(w, r)
 	return err
 }
diff --git a/zip/zip.go b/zip/zip.go
index cf6abb6..da5028e 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -20,11 +20,12 @@ import (
 // For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
 // baz.csv
 // dir
 // ├── bar.txt
 // └── foo.txt
-// Note that if a file is a symbolic link it will be skipped.
+// Note that if a file is a symbolic link on Linux it will be skipped.
+// If you want to follow a symbolic link please use the function ZipFollowSymlink.
 func Zip(zipPath string, paths ...string) error {
 	// Create zip file and it's parent dir.
 	if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
 		return err
 	}

Vulnerable code: # JSON

Some converting function to json.
student := struct {
    Hobby   string
    Age     int32
}{
    Hobby: "pingpopng",
    Age:   28,
}
encoding.ToIndentJSON(&student)
/*
output:
{
    "Hobby": "pingpopng",
    "Age": 28
}
*/
// ListDir lists all the file or dir names in the specified directory.
// IsPathExist checks whether a file/dir exists.
func IsPathExist(path string) (bool, error) {
// IsSymlink checks a file whether is a symbolic link.
// IsSymlinkE checks a file whether is a symbolic link.
// https://gist.github.com/novalagung/13c5c8f4d30e0c4bff27
	err := os.Remove(path)
	return err
// If the parent directory does not exist, it will be created with mode os.ModePerm.is cr truncated.
// If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR.
func Create(filePath string) (*os.File, error) {
	if exist, err := IsPathExist(filePath); err != nil {
	} else if exist {
		return os.Create(filePath)
	if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
	return os.Create(filePath)
// BytesToFile writes data to a file. If the file does not exist it will be created with permission mode 0644.
func BytesToFile(filePath string, data []byte) error {
	exist, _ := IsPathExist(filePath)
		if err := CreateFile(filePath); err != nil {
	return ioutil.WriteFile(filePath, data, 0644)
	// create the directory of file
	filePath := path.Join(dstDir, file.Name)
	// open the file
	// create the file
	// save the decompressed file content
// Note that if a file is a symbolic link it will be skipped.
Fixed code: Some converting function to json.
```go
student := struct {
    Hobby   string
    Age     int32
}{
    Hobby: "pingpopng",
    Age:   28,
}
encoding.ToIndentJSON(&student)
/*
output:
{
    "Hobby": "pingpopng",
    "Age": 28
}
*/
```
# File
// ListDir lists all the file or directory names in the specified directory.
ListDir()

// IsExist checks whether a file/dir exists.
IsExist()

// IsDir checks whether a path is a directory.
IsDir()
IsDirE()

// IsFile checks whether a path is a file.
IsFile()
IsFileE()

// IsSymlink checks a file whether is a symbolic link file on Linux.
IsSymlink()
IsSymlinkE()

// IsShortcut checks a file whether is a shortcut on Windows. 
IsShortcutFile()

// Create a file.
Create()
CreateFile()

// ClearFile clears a file content.
ClearFile()

// ...
// ListDir lists all the file or directory names in the specified directory.
// IsExist checks whether a file/dir exists.
func IsExist(path string) (bool, error) {
// IsSymlink checks a file whether is a symbolic link on Linux.
// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
// IsSymlinkE checks a file whether is a symbolic link on Linux.
// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
// IsShortcut checks a file whether is a shortcut on Windows.
func IsShortcut(path string) bool {
	ext := filepath.Ext(path)
	if ext == ".lnk" {
		return true
	}
	return false
}

// If there is an error, it will be of type *PathError.
	return os.Remove(path)
// If the parent directory does not exist, it will be created with mode os.ModePerm.
// If successful, methods on the returned file can be used for I/O; the associated file descriptor has mode O_RDWR.
func Create(path string) (*os.File, error) {
	exist, err := IsExist(path)
	if err != nil {
	if exist {
		return os.Create(path)
	}
	if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
	return os.Create(path)
// BytesToFile writes data to a file.
// If the file does not exist it will be created with permission mode 0644.
func BytesToFile(path string, data []byte) error {
	exist, _ := IsExist(path)
		if err := CreateFile(path); err != nil {
	return ioutil.WriteFile(path, data, 0644)

// ClearFile clears a file content.
func ClearFile(path string) error {
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0777)
	if err != nil {
		return err
	}
	defer f.Close()
	return nil
}
	"strings"
	// Prevent path traversal vulnerability.
	// Such as if the file name is "../../../path/to/file.txt" which will be cleaned to "path/to/file.txt".
	name := strings.TrimPrefix(filepath.Join(string(filepath.Separator), file.Name), string(filepath.Separator))
	filePath := path.Join(dstDir, name)

	// Create the directory of file.
	// Open the file.
	// Create the file.
	// Save the decompressed file content.
// Note that if a file is a symbolic link on Linux it will be skipped.
// If you want to follow a symbolic link please use the function ZipFollowSymlink.