VNX-GO-007 – Go Path Traversal
Overview
This rule detects Go HTTP handlers that pass values from r.FormValue() or r.URL.Query() directly to file system operations such as os.Open, os.ReadFile, filepath.Join, or http.ServeFile. Without sanitization, an attacker can include ../ sequences in the input to escape the intended directory and access or overwrite arbitrary files on the server. This maps to CWE-22: Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’).
Severity: High | CWE: CWE-22 – Path Traversal
Why This Matters
Path traversal gives attackers read (and sometimes write) access to files outside the intended directory. Common targets include /etc/passwd, /etc/shadow, application configuration files containing database credentials, private keys, environment files (.env), and application source code. In write-path scenarios, an attacker who can specify the destination path can overwrite binaries, inject malicious content into configuration files, or plant web shells. The attack is simple and requires no special tooling — just URL-encoding ../ sequences or using absolute paths. In containerized deployments where multiple tenants share a pod, path traversal can cross tenant boundaries.
What Gets Flagged
The rule fires when os.Open, os.ReadFile, os.Create, filepath.Join, http.ServeFile, or similar file system functions are called with a path argument sourced directly from r.FormValue() or r.URL.Query().
// FLAGGED: user input used directly as file path
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("file")
data, err := os.ReadFile("/uploads/" + filename)
// Attacker sends: file=../../etc/passwd
// Server reads: /uploads/../../etc/passwd => /etc/passwd
if err == nil {
w.Write(data)
}
}
// FLAGGED: filepath.Join does not prevent traversal on its own
func serveAsset(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("asset")
path := filepath.Join("/static", name)
http.ServeFile(w, r, path)
// Attacker sends: asset=../../../../etc/ssh/id_rsa
}
Remediation
- Canonicalize the path with
filepath.Cleanand verify it stays within the allowed base directory. After joining the base directory with user input and cleaning the result, check that the cleaned path still begins with the base directory prefix.
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
)
const uploadsBase = "/uploads"
func safeFilePath(base, userInput string) (string, error) {
// Resolve the joined path to its canonical form
joined := filepath.Join(base, userInput)
clean := filepath.Clean(joined)
// Ensure the cleaned path is still within the base directory
if !strings.HasPrefix(clean, filepath.Clean(base)+string(filepath.Separator)) {
return "", fmt.Errorf("path traversal detected")
}
return clean, nil
}
// SAFE: path validated against base directory before use
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.FormValue("file")
safePath, err := safeFilePath(uploadsBase, filename)
if err != nil {
http.Error(w, "invalid file path", http.StatusBadRequest)
return
}
data, err := os.ReadFile(safePath)
if err != nil {
http.Error(w, "file not found", http.StatusNotFound)
return
}
w.Write(data)
}
- Validate the filename against an allowlist pattern. If filenames follow a predictable format (alphanumeric, limited extension), reject anything that does not match before even constructing the path.
import "regexp"
var safeFilenameRE = regexp.MustCompile(`^[a-zA-Z0-9_-]+\.(pdf|png|jpg)$`)
func validateFilename(name string) bool {
return safeFilenameRE.MatchString(name)
}
- Use
http.FileServerwithhttp.Dirfor static file serving. Go’shttp.FileServerwith a sandboxedhttp.Dirsafely resolves paths and prevents traversal without any manual checking.
// SAFE: http.FileServer restricts access to the specified directory
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/app/static"))))
- Reject path separators in user input. If the input should be a simple filename with no subdirectory, reject any input containing
/,\, or..before processing it.