Skip to content

Commit 3ff608f

Browse files
committed
feat: implement safe path extraction for zip file entries
1 parent 692589e commit 3ff608f

2 files changed

Lines changed: 43 additions & 8 deletions

File tree

main.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,23 @@ func UnZip(distDirPath, zipPath string) error {
100100

101101
defer zipFile.Close()
102102

103-
prefix := ""
104-
if distDirPath != "" && distDirPath != "." {
105-
prefix = distDirPath + "/"
106-
}
107-
108103
for _, f := range zipFile.File {
109104
nameReader := bytes.NewReader([]byte(f.Name))
110105
decoder := transform.NewReader(nameReader, getDecoderByCoder(encode))
111106
content, _ := ioutil.ReadAll(decoder)
112107
filePath := string(content)
108+
destPath, err := safeExtractPath(distDirPath, filePath)
109+
if err != nil {
110+
return err
111+
}
113112
if f.FileInfo().IsDir() {
114-
_ = os.MkdirAll(prefix+filePath, os.ModePerm)
113+
_ = os.MkdirAll(destPath, os.ModePerm)
115114
continue
116115
}
117-
if err := os.MkdirAll(prefix+filepath.Dir(filePath), os.ModePerm); err != nil {
116+
if err := os.MkdirAll(filepath.Dir(destPath), os.ModePerm); err != nil {
118117
return err
119118
}
120-
dstFile, err := os.OpenFile(prefix+filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
119+
dstFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
121120
if err != nil {
122121
return err
123122
}

utils.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package main
22

33
import (
44
"archive/zip"
5+
"fmt"
56
"io"
67
"os"
8+
"path"
9+
"path/filepath"
710
"strings"
811

912
"golang.org/x/text/encoding"
@@ -32,6 +35,39 @@ func getDecoderByCoder(code string) *encoding.Decoder {
3235
return decoder
3336
}
3437

38+
// safeExtractPath resolves a zip entry name under baseDir and rejects path traversal.
39+
func safeExtractPath(baseDir, name string) (string, error) {
40+
name = path.Clean(strings.ReplaceAll(name, "\\", "/"))
41+
if name == "." || name == ".." {
42+
return "", fmt.Errorf("illegal path: %q", name)
43+
}
44+
if strings.HasPrefix(name, "/") {
45+
return "", fmt.Errorf("illegal absolute path: %q", name)
46+
}
47+
for _, part := range strings.Split(name, "/") {
48+
if part == ".." {
49+
return "", fmt.Errorf("illegal path traversal: %q", name)
50+
}
51+
}
52+
53+
if baseDir == "" || baseDir == "." {
54+
baseDir = "."
55+
}
56+
baseAbs, err := filepath.Abs(baseDir)
57+
if err != nil {
58+
return "", err
59+
}
60+
destAbs, err := filepath.Abs(filepath.Join(baseAbs, filepath.FromSlash(name)))
61+
if err != nil {
62+
return "", err
63+
}
64+
baseWithSep := baseAbs + string(filepath.Separator)
65+
if destAbs != baseAbs && !strings.HasPrefix(destAbs, baseWithSep) {
66+
return "", fmt.Errorf("illegal path escapes destination: %q", name)
67+
}
68+
return destAbs, nil
69+
}
70+
3571
func fileToZipWriter(file *os.File, prefix string, zw *zip.Writer) error {
3672
info, err := file.Stat()
3773
if err != nil {

0 commit comments

Comments
 (0)