{"title":"Path Traversal via os.path.join with User-Controlled Absolute Path Override","language":"Python","severity":"High","cwe":"CWE-22","source_lines":[6],"flow_lines":[6,7],"sink_lines":[7],"vulnerable_code":"import os\nfrom flask import request, send_file\n\ndef retrieve_iot_device_logs(device_id):\n    log_directory = \"/var/iot/device_logs/\"\n    requested_log = request.args.get('log_file', 'system.log')\n    log_path = os.path.join(log_directory, device_id, requested_log)\n    if os.path.exists(log_path):\n        return send_file(log_path, as_attachment=True)\n    return {\"error\": \"Log file not found\"}, 404","explanation":"The vulnerability exists because os.path.join() allows an absolute path in the 'requested_log' parameter to override the intended base directory. When a user provides an absolute path like '/etc/passwd', os.path.join ignores all preceding path components, allowing arbitrary file access. The tainted input from request.args.get() flows directly into os.path.join() and then to send_file() without validation.","remediation":"The fix applies multiple layers of defense: first, os.path.basename() strips any directory components (including absolute paths and '../' sequences) from both device_id and requested_log, ensuring only filenames are used. Second, os.path.realpath() resolves the final path and a prefix check confirms the resolved path remains within the allowed log directory, preventing any bypass through symlinks or edge cases.","secure_code":"import os\nfrom flask import request, send_file\n\ndef retrieve_iot_device_logs(device_id):\n    log_directory = \"/var/iot/device_logs/\"\n    requested_log = request.args.get('log_file', 'system.log')\n    \n    # Sanitize device_id and requested_log to prevent path traversal\n    safe_device_id = os.path.basename(device_id)\n    safe_log_file = os.path.basename(requested_log)\n    \n    # Construct the path using sanitized components\n    log_path = os.path.join(log_directory, safe_device_id, safe_log_file)\n    \n    # Resolve to absolute path and verify it stays within the allowed directory\n    log_path = os.path.realpath(log_path)\n    allowed_directory = os.path.realpath(log_directory)\n    \n    if not log_path.startswith(allowed_directory + os.sep):\n        return {\"error\": \"Access denied\"}, 403\n    \n    if os.path.exists(log_path) and os.path.isfile(log_path):\n        return send_file(log_path, as_attachment=True)\n    return {\"error\": \"Log file not found\"}, 404"}