Path Traversal via filepath.Clean Misuse in Go

High Risk Path Traversal
gopath-traversalfilepathdirectory-traversalfile-access

What it is

Path traversal vulnerabilities occur when applications use filepath.Clean() as a security control, mistakenly believing it prevents directory traversal. filepath.Clean() only normalizes paths but does NOT enforce directory boundaries, allowing attackers to access files outside intended directories using '../' sequences or absolute paths.

package main

import (
    "io/ioutil"
    "net/http"
    "path/filepath"
)

func fileHandler(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    baseDir := "/var/www/uploads/"
    
    // VULNERABLE: filepath.Clean does NOT prevent traversal!
    cleanPath := filepath.Clean(filename)
    fullPath := baseDir + cleanPath
    
    content, err := ioutil.ReadFile(fullPath)
    if err != nil {
        http.Error(w, "File not found", 404)
        return
    }
    w.Write(content)
}

// Attack: file=../../../etc/passwd
// Clean result: ../../../etc/passwd (still traverses!)
// Full path: /var/www/uploads/../../../etc/passwd
// Resolves to: /etc/passwd
package main

import (
    "errors"
    "io/ioutil"
    "net/http"
    "path/filepath"
    "strings"
)

func fileHandler(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    baseDir := "/var/www/uploads/"
    
    // SECURE: Validate path stays within base directory
    safePath, err := secureJoin(baseDir, filename)
    if err != nil {
        http.Error(w, "Invalid file path", 400)
        return
    }
    
    content, err := ioutil.ReadFile(safePath)
    if err != nil {
        http.Error(w, "File not found", 404)
        return
    }
    w.Write(content)
}

func secureJoin(base, userPath string) (string, error) {
    // Get absolute path of base
    absBase, err := filepath.Abs(base)
    if err != nil {
        return "", err
    }
    
    // Join and get absolute path
    joined := filepath.Join(absBase, userPath)
    absJoined, err := filepath.Abs(joined)
    if err != nil {
        return "", err
    }
    
    // Validate it stays within base directory
    if !strings.HasPrefix(absJoined, absBase) {
        return "", errors.New("path traversal detected")
    }
    
    return absJoined, nil
}

💡 Why This Fix Works

The vulnerable code uses filepath.Clean() thinking it prevents path traversal, but Clean only normalizes paths and does NOT enforce directory boundaries. The secure version validates that the resolved absolute path starts with the base directory, rejecting any path that escapes it.

Why it happens

Using filepath.Clean() believing it prevents path traversal attacks.

Root causes

Misusing filepath.Clean for Security

Using filepath.Clean() believing it prevents path traversal attacks.

No Base Directory Validation

Not validating that resolved paths stay within the intended base directory.

Direct Path Concatenation

Concatenating cleaned paths with base directory without validation.

Fixes

1

Validate Against Base Directory

After joining paths, verify the result starts with the base directory.

2

Use filepath-securejoin Library

Use cyphar/filepath-securejoin for safe path joining with automatic validation.

3

Implement File Allowlists

Use allowlists of permitted filenames instead of accepting arbitrary paths.

Detect This Vulnerability in Your Code

Sourcery automatically identifies path traversal via filepath.clean misuse in go and many other security issues in your codebase.