diff --git a/internal/web/handlers/git/dumb.go b/internal/web/handlers/git/dumb.go index 9a9f345..1b1dbab 100644 --- a/internal/web/handlers/git/dumb.go +++ b/internal/web/handlers/git/dumb.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" "os" - "path" + "path/filepath" "strings" "time" @@ -37,8 +37,13 @@ func idxFile(ctx *context.Context) error { } func sendFile(ctx *context.Context, contentType string) error { - gitFile := "/" + strings.Join(strings.Split(ctx.Request().URL.Path, "/")[3:], "/") - gitFile = path.Join(ctx.GetData("repositoryPath").(string), gitFile) + repoPath := ctx.GetData("repositoryPath").(string) + relPath := strings.Join(strings.Split(ctx.Request().URL.Path, "/")[3:], "/") + gitFile, err := containedPath(repoPath, relPath) + if err != nil { + return ctx.ErrorRes(404, "File not found", nil) + } + fi, err := os.Stat(gitFile) if os.IsNotExist(err) { return ctx.ErrorRes(404, "File not found", nil) @@ -49,6 +54,19 @@ func sendFile(ctx *context.Context, contentType string) error { return ctx.File(gitFile) } +func containedPath(baseDir, relPath string) (string, error) { + base := filepath.Clean(baseDir) + joined := filepath.Clean(filepath.Join(base, relPath)) + + if joined == base { + return "", fmt.Errorf("path %q resolves to the repository root", relPath) + } + if !strings.HasPrefix(joined, base+string(os.PathSeparator)) { + return "", fmt.Errorf("path %q escapes repository %q", relPath, base) + } + return joined, nil +} + func noCacheHeaders(ctx *context.Context) { ctx.Response().Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 UTC") ctx.Response().Header().Set("Pragma", "no-cache")