Repository navigation
Loosen up security on unzip to support the next route file pattern #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
730a477
8e6a439
ce143e4
da16c36
084e652
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -385,8 +385,15 @@ func Unzip(src, dest string, flatten bool) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer r.Close() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, f := range r.File { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.Contains(f.Name, "..") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for directory traversal attacks by examining path components | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Split the path and check each component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pathParts := strings.Split(filepath.ToSlash(f.Name), "/") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, part := range pathParts { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Block path components that could be used for directory traversal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ".." is obvious, but also block pure dot sequences (3+ dots) that might be interpreted as ".." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if part == ".." || (len(part) >= 3 && strings.Trim(part, ".") == "") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rc, err := f.Open() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -399,7 +406,33 @@ func Unzip(src, dest string, flatten bool) error { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f.Name = filepath.Base(f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Validate file name before path construction to catch absolute paths and other attacks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if filepath.IsAbs(f.Name) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path, absolute path not allowed: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for Windows-style absolute paths and UNC paths (platform-independent) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(f.Name) >= 3 && f.Name[1] == ':' && (f.Name[2] == '\\' || f.Name[2] == '/') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path, Windows drive path not allowed: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if strings.HasPrefix(f.Name, "\\\\") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path, UNC path not allowed: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fpath := filepath.Join(dest, f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Canonicalize paths and validate against directory traversal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| destRoot := filepath.Clean(dest) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !strings.HasSuffix(destRoot, string(os.PathSeparator)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| destRoot += string(os.PathSeparator) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanedFpath := filepath.Clean(fpath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Ensure the cleaned file path is within the destination directory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !strings.HasPrefix(cleanedFpath+string(os.PathSeparator), destRoot) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid file path, outside destination directory: %s", f.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+424
to
+435
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Symlink-aware containment: resolve dest and paths before prefix compare. Case-insensitive volumes and symlinks can bypass a pure lexical check. Normalize with Abs and resolve symlinks where possible. - // Canonicalize paths and validate against directory traversal
- destRoot := filepath.Clean(dest)
+ // Canonicalize paths and validate against directory traversal
+ destRoot := filepath.Clean(dest)
+ if abs, err := filepath.Abs(destRoot); err == nil {
+ destRoot = abs
+ }
+ if resolved, err := filepath.EvalSymlinks(destRoot); err == nil {
+ destRoot = resolved
+ }
if !strings.HasSuffix(destRoot, string(os.PathSeparator)) {
destRoot += string(os.PathSeparator)
}
- cleanedFpath := filepath.Clean(fpath)
+ cleanedFpath := filepath.Clean(fpath)
+ if abs, err := filepath.Abs(cleanedFpath); err == nil {
+ cleanedFpath = abs
+ }
// Ensure the cleaned file path is within the destination directory
if !strings.HasPrefix(cleanedFpath+string(os.PathSeparator), destRoot) {
return fmt.Errorf("invalid file path, outside destination directory: %s", f.Name)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if f.FileInfo().IsDir() && !flatten { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| os.MkdirAll(fpath, os.ModePerm) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden Windows path detection: block drive-relative “C:foo” and UNC with // too.
Current check misses
C:foo\bar(drive-relative). Also catch UNC prefixed by//.📝 Committable suggestion
🤖 Prompt for AI Agents