{"title":"Arbitrary File Upload via Unvalidated Flask Multipart Handling","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[7,8],"flow_lines":[7,10,11,8,11],"sink_lines":[11],"vulnerable_code":"from flask import Flask, request\nimport os\n\napp = Flask(__name__)\n\n@app.route('/iot/firmware/update', methods=['POST'])\ndef deploy_firmware_patch():\n    device_id = request.form.get('device_uuid')\n    firmware_blob = request.files['firmware_file']\n    storage_path = f'/opt/iot_hub/devices/{device_id}/'\n    os.makedirs(storage_path, exist_ok=True)\n    firmware_blob.save(os.path.join(storage_path, firmware_blob.filename))\n    return {'status': 'firmware_deployed', 'device': device_id}, 200","explanation":"The application accepts unsanitized user input for both device_id and filename parameters without validation. The device_id is used in path construction and filename is directly used from the uploaded file, allowing path traversal attacks (e.g., '../../../etc/malicious') to write arbitrary files anywhere on the filesystem with web server permissions.","remediation":"The fix validates device_id as a proper UUID to prevent path traversal via that parameter, uses werkzeug's secure_filename() to sanitize the uploaded filename stripping any directory traversal sequences, validates file extensions against an allowlist of firmware formats, and performs realpath checks to ensure both the storage directory and final file path remain within the designated base directory.","secure_code":"from flask import Flask, request, jsonify\nimport os\nimport re\nimport uuid\nfrom werkzeug.utils import secure_filename\n\napp = Flask(__name__)\n\nBASE_STORAGE_PATH = '/opt/iot_hub/devices/'\nALLOWED_EXTENSIONS = {'.bin', '.fw', '.hex', '.img', '.ota'}\nMAX_FIRMWARE_SIZE = 50 * 1024 * 1024  # 50MB\n\ndef is_valid_uuid(value):\n    try:\n        uuid.UUID(str(value))\n        return True\n    except ValueError:\n        return False\n\ndef is_allowed_extension(filename):\n    _, ext = os.path.splitext(filename)\n    return ext.lower() in ALLOWED_EXTENSIONS\n\n@app.route('/iot/firmware/update', methods=['POST'])\ndef deploy_firmware_patch():\n    device_id = request.form.get('device_uuid')\n    \n    if not device_id or not is_valid_uuid(device_id):\n        return {'error': 'Invalid device UUID'}, 400\n    \n    if 'firmware_file' not in request.files:\n        return {'error': 'No firmware file provided'}, 400\n    \n    firmware_blob = request.files['firmware_file']\n    \n    if not firmware_blob.filename:\n        return {'error': 'Empty filename'}, 400\n    \n    safe_filename = secure_filename(firmware_blob.filename)\n    \n    if not safe_filename:\n        return {'error': 'Invalid filename'}, 400\n    \n    if not is_allowed_extension(safe_filename):\n        return {'error': 'Invalid firmware file extension'}, 400\n    \n    storage_path = os.path.join(BASE_STORAGE_PATH, device_id)\n    resolved_storage = os.path.realpath(storage_path)\n    \n    if not resolved_storage.startswith(os.path.realpath(BASE_STORAGE_PATH)):\n        return {'error': 'Invalid storage path'}, 400\n    \n    full_path = os.path.join(resolved_storage, safe_filename)\n    resolved_full_path = os.path.realpath(full_path)\n    \n    if not resolved_full_path.startswith(resolved_storage):\n        return {'error': 'Invalid file path'}, 400\n    \n    os.makedirs(resolved_storage, exist_ok=True)\n    firmware_blob.save(resolved_full_path)\n    \n    return {'status': 'firmware_deployed', 'device': device_id}, 200"}