flawopen.com/path-traversal/Python
How os.path.join silently discards the root directory when input starts with a slash, and how to use pathlib.Path.resolve() and is_relative_to().
Представьте электронный замок в отеле, запрограммированный открывать только номера на втором этаже. Если гость вводит на панели '../../master-safe', уязвимый замок выходит в коридор и открывает главный сейф управляющего.
Web Application SecurityCWE-918.CWE-918CWE-918): Standard Common Weakness Enumeration classification for path-traversal-python.Defense-in-DepthКонечная точка принимает имя файла или путь к документу через параметр HTTP-запроса.
Злоумышленник передает последовательности '../', '..%2f' или абсолютные пути для выхода из базового каталога.
Бэкенд конкатенирует путь без канонизации и проверки того, что результирующий путь находится внутри базовой папки.
Сервер считывает и передает клиенту конфиденциальные файлы операционной системы (например, /etc/passwd).
# VULNERABLE: os.path.join discards BASE_DIR if filename starts with '/'
import os
from flask import Flask, request, abort, send_file
app = Flask(__name__)
BASE_DIR = '/var/app/public/user_uploads'
@app.route('/download')
def download_file():
filename = request.args.get('file')
# If filename is "/etc/passwd", os.path.join returns "/etc/passwd"!
# If filename is "../../etc/passwd", it traverses outside BASE_DIR
target_path = os.path.join(BASE_DIR, filename)
if not os.path.exists(target_path):
abort(404)
return send_file(target_path)
# HARDENED: Resolve canonical path and enforce strict boundary containment
from pathlib import Path
from flask import Flask, request, abort, send_file
app = Flask(__name__)
BASE_DIR = Path('/var/app/public/user_uploads').resolve()
@app.route('/download')
def download_file():
filename = request.args.get('file', '')
# 1. Strip leading slashes and resolve relative components
clean_name = filename.lstrip('/\\')
target_path = (BASE_DIR / clean_name).resolve()
# 2. Strict containment check: target MUST be inside BASE_DIR
if not target_path.is_relative_to(BASE_DIR) or not target_path.is_file():
abort(403)
return send_file(target_path)
pathlib.Path.resolve() rather than os.path.abspath().