Description
Path traversal uses input to manipulate filesystem paths and access files outside the intended file or directory scope. Attackers may use relative paths such as ../ to reach sensitive system files.
Potential impact
- Information disclosure: An attacker may read sensitive system files and obtain important information.
- Data damage: An attacker may overwrite or delete system files.
- Privilege escalation: Modifying important files may enable an attacker to gain additional privileges.
Remediation
- Validate filenames and paths. Do not rely only on blocking the string
../; check that the actual accessed path remains inside the permitted directory. - Restrict access to files within an allow-listed directory.
- In Flask/Werkzeug, prefer APIs designed to help prevent path traversal, such as
secure_filename(),safe_join()andsend_from_directory(). - If only a filename is needed, extract the final component with
os.path.basename()orpathlib.PurePath(...).name, and always combine it with a trusted base directory.
Examples
open
Before
python
# Unsafe Flask code
from flask import Flask, request
app = Flask(__name__)
@app.route('/read')
def unsafe_read():
filename = request.args.get('filename')
with open(filename, 'r') as file:
content = file.read()
return content
After
python
# Safe Flask code
from flask import Flask, request, abort
from pathlib import Path
from werkzeug.security import safe_join
from werkzeug.utils import secure_filename
app = Flask(__name__)
# Server-managed directory that attackers cannot modify
BASE_DIR = Path("/safe/directory").resolve(strict=True)
@app.route('/read')
def safe_read():
filename = secure_filename(request.args.get('filename', ''))
if not filename:
return abort(400, description="Invalid file path")
candidate = safe_join(str(BASE_DIR), filename)
if candidate is None:
return abort(400, description="Invalid file path")
try:
fullpath = Path(candidate).resolve(strict=True)
fullpath.relative_to(BASE_DIR)
except FileNotFoundError:
return abort(404, description="File not found")
except ValueError:
return abort(400, description="Invalid file path")
if not fullpath.is_file():
return abort(404, description="File not found")
with open(fullpath, 'r') as file:
content = file.read()
return content
Explanation:
- Before: Uses unvalidated input as a path, allowing an attacker to reach sensitive files with relative paths such as
../. - After: Rejects empty names and restricts path components with
secure_filename()andsafe_join(). It then usesPath.resolve(strict=True)to resolve symbolic links and checks that the actual file is inside a base directory that attackers cannot modify.
Using only the filename component
python
import os
from pathlib import PurePath
def filename_only(raw_name):
if not isinstance(raw_name, str):
raise TypeError("filename must be a string")
# Remove directory components using either method
name = os.path.basename(raw_name)
# name = PurePath(raw_name).name
if name in {"", ".", ".."}:
raise ValueError("invalid filename")
return name
basename and PurePath.name extract only the last path component. When opening or sending a file, combine this value with a trusted base directory too.
Flask send_file
Before
python
# Unsafe Flask send_file
from flask import Flask, request, send_file
app = Flask(__name__)
@app.route('/read')
def unsafe_read():
filename = request.args.get('filename')
return send_file(filename)
After
python
# Safe Flask send_from_directory
from flask import Flask, request, send_from_directory
from pathlib import Path
from werkzeug.utils import secure_filename
app = Flask(__name__)
BASE_DIR = Path("/safe/directory").resolve(strict=True)
@app.route('/read')
def safe_read():
filename = secure_filename(request.args.get('filename', ''))
if not filename:
return "Invalid file path", 400
return send_from_directory(BASE_DIR, filename, as_attachment=True)
Explanation:
- Before: Uses unvalidated input as a path, allowing an attacker to reach sensitive files with relative paths such as
../. - After: Uses
send_from_directoryto restrict access to the specified directory, rejects empty names and restricts the filename withsecure_filename(). The server must manage the base directory so attackers cannot add files or symbolic links.