flawopen.com/path-traversal/Go
How filepath.Join in Go fails to restrict file boundaries, and how to enforce containment using filepath.Clean and filepath.Rel.
ホテルの客室カードリーダーが2階の部屋のみを開けるよう制限されている場面を例えに考えてみてください。もし宿泊客がドアのキーパッドに「../../master-safe」と入力すると、不備のある鍵が廊下を抜け出して支配人の金庫を直接解錠してしまいます。
Web Application SecurityCWE-918.CWE-918CWE-918):Standard Common Weakness Enumeration classification for path-traversal-go.Defense-in-DepthエンドポイントがHTTPリクエストパラメータからユーザー指定のファイル名やパスを受け取ります。
攻撃者が '../' や '..%2f' などの相対パス記号や絶対パス指定を挿入します。
バックエンドが正規化パスの検証を行わずに文字列を連結し、公開フォルダ外へのアクセスを許容します。
OSが /etc/passwd や設定ファイルなどの重要ファイルを読み取り、攻撃者へ返却します。
// VULNERABLE: filepath.Join does not prevent directory breakout
package main
import (
"net/http"
"os"
"path/filepath"
)
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file")
baseDir := "/var/app/public/files"
// Attacker input: "../../etc/shadow"
targetPath := filepath.Join(baseDir, filename)
data, err := os.ReadFile(targetPath)
if err != nil {
http.NotFound(w, r)
return
}
w.Write(data)
}
// HARDENED: Use filepath.Rel to verify target is strictly inside baseDir
package main
import (
"net/http"
"os"
"path/filepath"
"strings"
)
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file")
baseDir := "/var/app/public/files"
// 1. Resolve absolute paths
absBase, err := filepath.Abs(baseDir)
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}
absTarget := filepath.Join(absBase, filepath.Clean("/"+filename))
// 2. Verify relative containment
rel, err := filepath.Rel(absBase, absTarget)
if err != nil || strings.HasPrefix(rel, "..") || rel == "." {
http.Error(w, "Access denied: Path Traversal detected", http.StatusForbidden)
return
}
data, err := os.ReadFile(absTarget)
if err != nil {
http.NotFound(w, r)
return
}
w.Write(data)
}
filepath.Clean()。filepath.Rel() を厳格に検証してください。filepath.EvalSymlinks() where necessary。os.DirFS() which enforce chroot-like root boundaries を使用してください。