diff --git a/cmd/root.go b/cmd/root.go index 03969aa..72ea29a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -184,7 +184,7 @@ var rootCmd = &cobra.Command{ for _, file := range index.Files { ok, err := util.PathIsSubpath(file.Path, serverDir) if err != nil { - log.Println(err) + log.Println(err.Error()) } if err != nil || !ok { log.Fatalln("File path is not safe: " + file.Path) diff --git a/cmd/update.go b/cmd/update.go index d327eb6..c68d76f 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -151,7 +151,7 @@ var updateCmd = &cobra.Command{ for path, _ := range newModPackInfo.File { ok, err := util.PathIsSubpath(string(path), serverDir) if err != nil { - log.Println(err) + log.Println(err.Error()) } if err != nil || !ok { log.Fatalln("File path is not safe: " + path) diff --git a/util/file.go b/util/file.go index 1f952a0..f3dd9e8 100644 --- a/util/file.go +++ b/util/file.go @@ -1,6 +1,7 @@ package util import ( + "errors" "fmt" "os" "path/filepath" @@ -31,18 +32,38 @@ func PathIsDir(path string) bool { return info.Mode().IsDir() } -func PathIsSubpath(path string, basePath string) (bool, error) { - absBasePath, err := filepath.Abs(basePath) +func ResolvePath(path string) (string, error) { + // resolve absolute path + path, err := filepath.Abs(path) + if err != nil { + return "", err + } + + if _, err := os.Stat(path); err == nil { + // resolve symlinks + path, err = filepath.EvalSymlinks(path) + if err != nil { + return "", err + } + } else if !errors.Is(err, os.ErrNotExist) { + return "", err + } + + return path, nil +} + +func PathIsSubpath(subPath string, basePath string) (bool, error) { + subPath, err := ResolvePath(subPath) if err != nil { return false, err } - _, err = filepath.Rel(absBasePath, path) + basePath, err = ResolvePath(basePath) if err != nil { return false, err } - return true, nil + return strings.HasPrefix(subPath, basePath), nil } func FileDetection(hash string, path string) DetectType { diff --git a/util/file_test.go b/util/file_test.go new file mode 100644 index 0000000..69ebded --- /dev/null +++ b/util/file_test.go @@ -0,0 +1,32 @@ +package util + +import ( + "testing" +) + +func testPathIsSubpath(t *testing.T, path string, basePath string) bool { + ok, err := PathIsSubpath(path, basePath) + if err != nil { + t.Log(err) + } + t.Logf("subpath=%-5v path=%s base=%s", ok, path, basePath) + return ok +} + +func TestPathTraversalAbsolute(t *testing.T) { + if testPathIsSubpath(t, "/bin/file", "/tmp/") { + t.FailNow() + } + if !testPathIsSubpath(t, "/tmp/file", "/tmp/") { + t.FailNow() + } +} + +func TestPathTraversalRelative(t *testing.T) { + if testPathIsSubpath(t, "../../../../../../../bin/file", "/tmp/") { + t.FailNow() + } + if !testPathIsSubpath(t, "../../../../../../../tmp/file", "/tmp/") { + t.FailNow() + } +}