{"title":"Zip Slip via zipfile.extractall()","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[8],"flow_lines":[8,13,17],"sink_lines":[17],"vulnerable_code":"import zipfile\nimport os\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/api/v1/firmware/upload', methods=['POST'])\ndef process_iot_firmware_package():\n    if 'firmware_bundle' not in request.files:\n        return jsonify({'error': 'No firmware file provided'}), 400\n    \n    fw_archive = request.files['firmware_bundle']\n    device_id = request.form.get('device_id', 'unknown')\n    staging_path = f'/var/iot/firmware_staging/{device_id}'\n    \n    os.makedirs(staging_path, exist_ok=True)\n    archive_path = os.path.join(staging_path, fw_archive.filename)\n    fw_archive.save(archive_path)\n    \n    with zipfile.ZipFile(archive_path, 'r') as zip_ref:\n        zip_ref.extractall(staging_path)\n    \n    manifest_file = os.path.join(staging_path, 'manifest.json')\n    if os.path.exists(manifest_file):\n        return jsonify({'status': 'Firmware extracted successfully', 'device': device_id}), 200\n    return jsonify({'error': 'Invalid firmware package'}), 400","explanation":"The application accepts an uploaded ZIP file and extracts it using zipfile.extractall() without validating that archive entries stay within the intended directory. A malicious ZIP file can contain entries with path traversal sequences (e.g., '../../../../etc/cron.d/malicious') that write files outside the staging directory, leading to arbitrary file write and potential remote code execution.","remediation":"The fix introduces a safe_extract() function that validates each ZIP entry's resolved path stays within the intended destination directory before extraction, preventing Zip Slip attacks. Additionally, the device_id and uploaded filename are sanitized using os.path.basename() to prevent path traversal through form inputs, and proper error handling is added for malicious or invalid archives.","secure_code":"import zipfile\nimport os\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n\ndef safe_extract(zip_ref, destination):\n    \"\"\"Safely extract ZIP contents, preventing path traversal (Zip Slip).\"\"\"\n    destination = os.path.realpath(destination)\n    for member in zip_ref.namelist():\n        member_path = os.path.realpath(os.path.join(destination, member))\n        if not member_path.startswith(destination + os.sep) and member_path != destination:\n            raise ValueError(f\"Attempted path traversal in zip entry: {member}\")\n    zip_ref.extractall(destination)\n\n\n@app.route('/api/v1/firmware/upload', methods=['POST'])\ndef process_iot_firmware_package():\n    if 'firmware_bundle' not in request.files:\n        return jsonify({'error': 'No firmware file provided'}), 400\n    \n    fw_archive = request.files['firmware_bundle']\n    device_id = request.form.get('device_id', 'unknown')\n    \n    # Sanitize device_id to prevent path traversal via form input\n    device_id = os.path.basename(device_id)\n    if not device_id or device_id in ('.', '..'):\n        return jsonify({'error': 'Invalid device ID'}), 400\n    \n    staging_path = f'/var/iot/firmware_staging/{device_id}'\n    \n    os.makedirs(staging_path, exist_ok=True)\n    \n    # Sanitize the uploaded filename\n    safe_filename = os.path.basename(fw_archive.filename)\n    if not safe_filename:\n        return jsonify({'error': 'Invalid filename'}), 400\n    \n    archive_path = os.path.join(staging_path, safe_filename)\n    fw_archive.save(archive_path)\n    \n    try:\n        with zipfile.ZipFile(archive_path, 'r') as zip_ref:\n            safe_extract(zip_ref, staging_path)\n    except ValueError as e:\n        os.remove(archive_path)\n        return jsonify({'error': f'Malicious archive detected: {str(e)}'}), 400\n    except zipfile.BadZipFile:\n        os.remove(archive_path)\n        return jsonify({'error': 'Invalid ZIP file'}), 400\n    \n    manifest_file = os.path.join(staging_path, 'manifest.json')\n    if os.path.exists(manifest_file):\n        return jsonify({'status': 'Firmware extracted successfully', 'device': device_id}), 200\n    return jsonify({'error': 'Invalid firmware package'}), 400"}