{"title":"Path Traversal via Unsafe os.path.join File Access","language":"Python","severity":"High","cwe":"CWE-22","source_lines":[9],"flow_lines":[9,10,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request, send_file\nimport os\n\napp = Flask(__name__)\nCLOUD_BACKUP_DIR = '/var/backups/aws_snapshots'\n\n@app.route('/api/v2/restore-snapshot', methods=['GET'])\ndef retrieve_aws_snapshot():\n    snapshot_id = request.args.get('snapshot_name', 'default.tar.gz')\n    backup_path = os.path.join(CLOUD_BACKUP_DIR, snapshot_id)\n    if os.path.exists(backup_path):\n        return send_file(backup_path, as_attachment=True)\n    return {'error': 'Snapshot not found'}, 404","explanation":"The application retrieves user-controlled input from the 'snapshot_name' query parameter without validation and directly passes it to os.path.join(). An attacker can use path traversal sequences (e.g., '../') to escape the intended CLOUD_BACKUP_DIR directory and access arbitrary files on the filesystem, which are then served via send_file().","remediation":"The fix validates the user-supplied snapshot_name by first rejecting any input containing path separators or '..' sequences, then resolves the constructed path to its canonical absolute form using os.path.realpath() and verifies it remains within the intended CLOUD_BACKUP_DIR directory. This dual-layer approach prevents path traversal attacks even if an attacker uses encoded or alternative traversal techniques.","secure_code":"from flask import Flask, request, send_file, abort\nimport os\n\napp = Flask(__name__)\nCLOUD_BACKUP_DIR = '/var/backups/aws_snapshots'\n\n@app.route('/api/v2/restore-snapshot', methods=['GET'])\ndef retrieve_aws_snapshot():\n    snapshot_id = request.args.get('snapshot_name', 'default.tar.gz')\n    # Reject any path separators or traversal sequences in the input\n    if os.path.sep in snapshot_id or '/' in snapshot_id or '\\\\' in snapshot_id or '..' in snapshot_id:\n        abort(400, description='Invalid snapshot name')\n    backup_path = os.path.join(CLOUD_BACKUP_DIR, snapshot_id)\n    # Resolve to absolute path and verify it stays within the allowed directory\n    backup_path = os.path.realpath(backup_path)\n    if not backup_path.startswith(os.path.realpath(CLOUD_BACKUP_DIR) + os.sep):\n        abort(403, description='Access denied')\n    if os.path.exists(backup_path):\n        return send_file(backup_path, as_attachment=True)\n    return {'error': 'Snapshot not found'}, 404"}