Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Many file operations are intended to take place within a restricted directory. By using special elements such as '..' and '/' separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system.
Como corrigir esta vulnerabilidade
Estratégias de prevenção para Path Traversal baseadas em 6 regras de detecção do Shoulder.
Resolve the full path and verify it stays within the intended base directory
package main import ( "net/http" "os" - ) - - func handler(w http.ResponseWriter, r *http.Request) { - filename := r.URL.Query().Get("file") - // Vulnerable: user input directly in file path - data, err := os.ReadFile("/uploads/" + filename) + "path/filepath" + "strings" + ) + + const baseDir = "/uploads" + + func handler(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("file") + // Clean and resolve the path + cleanName := filepath.Clean(filename) + fullPath := filepath.Join(baseDir, cleanName) + absPath, _ := filepath.Abs(fullPath) + absBase, _ := filepath.Abs(baseDir) + // Verify path is within base directory + if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) { + http.Error(w, "Forbidden", 403) + return + } + data, err := os.ReadFile(absPath) if err != nil { http.Error(w, "Not found", 404) return } w.Write(data) }
Validate that extracted archive paths resolve within the target directory
package main import ( "archive/zip" - "io" - "os" - ) - - func extractZip(zipPath, destDir string) error { - r, _ := zip.OpenReader(zipPath) - defer r.Close() - for _, f := range r.File { - // Vulnerable: using archive filename directly - outFile, _ := os.Create(f.Name) + "errors" + "io" + "os" + "path/filepath" + "strings" + ) + + func extractZip(zipPath, destDir string) error { + r, _ := zip.OpenReader(zipPath) + defer r.Close() + destDir = filepath.Clean(destDir) + string(os.PathSeparator) + for _, f := range r.File { + // Safe: validate path is within destination + destPath := filepath.Join(destDir, filepath.Clean(f.Name)) + if !strings.HasPrefix(destPath, destDir) { + return errors.New("illegal file path in archive: " + f.Name) + } + if f.FileInfo().IsDir() { + os.MkdirAll(destPath, 0755) + continue + } + outFile, _ := os.Create(destPath) rc, _ := f.Open() io.Copy(outFile, rc) rc.Close() outFile.Close() } return nil }
Use path.basename() to strip directory components or validate resolved paths stay within allowed directories
const express = require('express'); const fs = require('fs'); const path = require('path'); const app = express(); app.get('/files/:filename', (req, res) => { - const filePath = path.join(__dirname, 'uploads', req.params.filename); + const safeName = path.basename(req.params.filename); + const filePath = path.join(__dirname, 'uploads', safeName); fs.readFile(filePath, (err, data) => { if (err) return res.status(404).send('Not found'); res.send(data); }); });
Validate that extracted archive entry paths resolve within the target directory before writing
const express = require('express'); const AdmZip = require('adm-zip'); const path = require('path'); const app = express(); - app.post('/upload', (req, res) => { - const zip = new AdmZip(req.file.path); - const entries = zip.getEntries(); - for (const entry of entries) { - zip.extractEntryTo(entry, './uploads/', false, true); + function isPathSafe(baseDir, targetPath) { + const resolvedBase = path.resolve(baseDir); + const resolvedTarget = path.resolve(baseDir, targetPath); + return resolvedTarget.startsWith(resolvedBase + path.sep); + } + + app.post('/upload', (req, res) => { + const targetDir = './uploads/'; + const zip = new AdmZip(req.file.path); + const entries = zip.getEntries(); + for (const entry of entries) { + if (!isPathSafe(targetDir, entry.entryName)) { + return res.status(400).json({ error: 'Path traversal detected' }); + } + zip.extractEntryTo(entry, targetDir, false, true); } res.json({ success: true }); });
Use os.path.basename() or pathlib to restrict file access to intended directories
- from flask import request - - @app.route('/read') - def read_file(): - filename = request.args.get('file') - with open(f'/var/uploads/{filename}', 'r') as f: + import os + from flask import request, abort + + UPLOAD_DIR = '/var/uploads' + + @app.route('/read') + def read_file(): + filename = request.args.get('file', '') + safe_name = os.path.basename(filename) + filepath = os.path.join(UPLOAD_DIR, safe_name) + if not os.path.isfile(filepath): + abort(404) + with open(filepath, 'r') as f: return f.read()
Validate extracted file paths stay within the target directory before writing
import zipfile - - def extract_archive(zip_path, dest): - with zipfile.ZipFile(zip_path, 'r') as zf: - zf.extractall(dest) + import os + + def safe_extract(zip_path, dest): + with zipfile.ZipFile(zip_path, 'r') as zf: + for member in zf.namelist(): + target = os.path.normpath(os.path.join(dest, member)) + if not target.startswith(os.path.abspath(dest)): + raise ValueError(f"Path traversal: {member}") + zf.extract(member, dest)
Encontre vulnerabilidades no seu código
Use o Shoulder para escanear seu código em busca de padrões Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). 6 regras.
# Scan with Shoulder CLI npx @shoulderdev/cli trust --cwe=22 # Or scan entire project npx @shoulderdev/cli trust .
Regras de Detecção (6)
O que observar nas revisões de código
Estes padrões indicam vulnerabilidades potenciais de Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). Procure-os durante revisões de código e auditorias de segurança.
Escaneie seu código para Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
O Shoulder CLI encontra padrões vulneráveis em todo o seu código.