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.
Cómo corregir esta vulnerabilidad
Estrategias de prevención para Path Traversal basadas en 6 reglas de detección de 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)
Encuentra vulnerabilidades en tu código
Usa Shoulder para escanear tu código en busca de patrones Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). 6 reglas.
# Scan with Shoulder CLI npx @shoulderdev/cli trust --cwe=22 # Or scan entire project npx @shoulderdev/cli trust .
Reglas de Detección (6)
Qué buscar en las revisiones de código
Estos patrones indican vulnerabilidades potenciales de Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'). Búscalos durante las revisiones de código y auditorías de seguridad.
Escanea tu base de código para Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Shoulder CLI encuentra patrones vulnerables en toda tu base de código.