go.lang.security.filepath-clean-misuse.filepath-clean-misuse

Author
unknown
Download Count*
License
Clean
is not intended to sanitize against path traversal attacks. This function is for finding the shortest path name equivalent to the given input. Using Clean
to sanitize file reads may expose this application to path traversal attacks, where an attacker could access arbitrary files on the server. To fix this easily, write this: filepath.FromSlash(path.Clean("/"+strings.Trim(req.URL.Path, "/")))
However, a better solution is using the SecureJoin
function in the package filepath-securejoin
. See https://pkg.go.dev/github.com/cyphar/filepath-securejoin#section-readme.
Run Locally
Run in CI
Defintion
rules:
- id: filepath-clean-misuse
message: '`Clean` is not intended to sanitize against path traversal attacks.
This function is for finding the shortest path name equivalent to the
given input. Using `Clean` to sanitize file reads may expose this
application to path traversal attacks, where an attacker could access
arbitrary files on the server. To fix this easily, write this:
`filepath.FromSlash(path.Clean("/"+strings.Trim(req.URL.Path, "/")))`
However, a better solution is using the `SecureJoin` function in the
package `filepath-securejoin`. See
https://pkg.go.dev/github.com/cyphar/filepath-securejoin#section-readme.'
severity: ERROR
languages:
- go
mode: taint
pattern-sources:
- patterns:
- pattern-either:
- pattern: |
($REQUEST : *http.Request).$ANYTHING
- pattern: |
($REQUEST : http.Request).$ANYTHING
- metavariable-regex:
metavariable: $ANYTHING
regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
pattern-sinks:
- patterns:
- pattern-either:
- pattern: filepath.Clean($...INNER)
- pattern: path.Clean($...INNER)
pattern-sanitizers:
- pattern-either:
- pattern: |
"/" + ...
fix: filepath.FromSlash(filepath.Clean("/"+strings.Trim($...INNER, "/")))
metadata:
references:
- https://pkg.go.dev/path#Clean
- http://technosophos.com/2016/03/31/go-quickly-cleaning-filepaths.html
- https://labs.detectify.com/2021/12/15/zero-day-path-traversal-grafana/
- https://dzx.cz/2021/04/02/go_path_traversal/
- https://pkg.go.dev/github.com/cyphar/filepath-securejoin#section-readme
cwe:
- "CWE-22: Improper Limitation of a Pathname to a Restricted Directory
('Path Traversal')"
owasp:
- A05:2017 - Broken Access Control
- A01:2021 - Broken Access Control
category: security
technology:
- go
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
likelihood: MEDIUM
impact: MEDIUM
confidence: MEDIUM
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
Examples
filepath-clean-misuse.go
package main
import (
"io/ioutil"
"log"
"net/http"
"path/filepath"
"path"
"strings"
)
const root = "/tmp"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/bad1", func(w http.ResponseWriter, r *http.Request) {
// ruleid: filepath-clean-misuse
filename := filepath.Clean(r.URL.Path)
filename := filepath.Join(root, strings.Trim(filename, "/"))
contents, err := ioutil.ReadFile(filename)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(contents)
})
mux.HandleFunc("/bad2", func(w http.ResponseWriter, r *http.Request) {
// ruleid: filepath-clean-misuse
filename := path.Clean(r.URL.Path)
filename := filepath.Join(root, strings.Trim(filename, "/"))
contents, err := ioutil.ReadFile(filename)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(contents)
})
mux.HandleFunc("/ok", func(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Path
// ok: filepath-clean-misuse
filename := filepath.Join(root, strings.Trim(filename, "/"))
contents, err := ioutil.ReadFile(filename)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(contents)
})
mux.HandleFunc("/ok2", func(w http.ResponseWriter, r *http.Request) {
// ok: filepath-clean-misuse
filename := path.Clean("/" + r.URL.Path)
filename := filepath.Join(root, strings.Trim(filename, "/"))
contents, err := ioutil.ReadFile(filename)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(contents)
})
server := &http.Server{
Addr: "127.0.0.1:50000",
Handler: mux,
}
log.Fatal(server.ListenAndServe())
}
// TODO
// func NewHandlerWithDefault(root http.FileSystem, handler http.Handler, defaultPath string, gatewayDomains []string) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// if isGatewayRequest(r) {
// // s3 signed request reaching the ui handler, return an error response instead of the default path
// o := operations.Operation{}
// err := errors.Codes[errors.ERRLakeFSWrongEndpoint]
// err.Description = fmt.Sprintf("%s (%v)", err.Description, gatewayDomains)
// o.EncodeError(w, r, err)
// return
// }
// urlPath := r.URL.Path
// // We want this rule to only fire when urlPath does not have
// // a slash in front. This if condition ensures there is a slash,
// // so the line marked 'ok' below should not fire.
// if !strings.HasPrefix(urlPath, "/") {
// urlPath = "/" + urlPath
// r.URL.Path = urlPath
// }
// // ok: filepath-clean-misuse
// _, err := root.Open(path.Clean(urlPath))
// if err != nil && os.IsNotExist(err) {
// r.URL.Path = defaultPath
// }
// // consistent content-type
// contentType := gomime.TypeByExtension(filepath.Ext(r.URL.Path))
// if contentType != "" {
// w.Header().Set("Content-Type", contentType)
// }
// handler.ServeHTTP(w, r)
// })
// }
Short Link: https://sg.run/ZKzw