diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6992bdd8..ef772da9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,9 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v4 with: - go-version: ${{ matrix.go_version }} + go-version-file: './go.mod' + check-latest: true + - run: go version - name: Test run: go test -v ./... diff --git a/context/context.go b/context/context.go index 460f4c3e..fca114a2 100644 --- a/context/context.go +++ b/context/context.go @@ -2380,6 +2380,15 @@ func (ctx *Context) FormFiles(key string, before ...func(*Context, *multipart.Fi return nil, nil, http.ErrMissingFile } +var ( + // ValidFileNameRegexp is used to validate the user input by using a regular expression. + // See `Context.UploadFormFiles` method. + ValidFilenameRegexp = regexp.MustCompile(`^[a-zA-Z0-9_\-\.]+$`) + // ValidExtensionRegexp acts as an allowlist of valid extensions. It's optional. Defaults to nil (all file extensions are allowed to be uploaded). + // See `Context.UploadFormFiles` method. + ValidExtensionRegexp *regexp.Regexp +) + // UploadFormFiles uploads any received file(s) from the client // to the system physical location "destDirectory". // @@ -2418,24 +2427,35 @@ func (ctx *Context) UploadFormFiles(destDirectory string, before ...func(*Contex for _, files := range fhs { innerLoop: for _, file := range files { - // Security fix for go < 1.17.5: - // Reported by Kirill Efimov (snyk.io) through security reports. - file.Filename = filepath.Base(file.Filename) - for _, b := range before { if !b(ctx, file) { continue innerLoop } } + // Security fix for go < 1.17.5: + // Reported by Kirill Efimov (snyk.io) through security reports. + filename := filepath.Base(filepath.ToSlash(file.Filename)) + // CWE-99. - // Sanitize the user input by removing any path separators. - sanitizedInput := strings.ReplaceAll(file.Filename, string(os.PathSeparator), "") + + // Sanitize the user input by using a regular expression + // and an allowlist of valid extensions + isValidFilename := ValidFilenameRegexp.MatchString(filename) + if !isValidFilename { + // Reject the input as it is invalid or unsafe. + continue innerLoop + } + + if ValidExtensionRegexp != nil && !ValidExtensionRegexp.MatchString(filename) { + // Reject the input as it is invalid or unsafe. + continue innerLoop + } // Join the sanitized input with the destination directory. - destPath := filepath.Join(destDirectory, sanitizedInput) + destPath := filepath.Join(destDirectory, filename) - // Get the canonical path of the destination. + // Get the canonical path of the destination canonicalDestPath, err := filepath.EvalSymlinks(destPath) if err != nil { return nil, 0, err