{"title":"Zip Slip via zipfile.extractall()","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[7],"flow_lines":[7,10,12],"sink_lines":[12],"vulnerable_code":"import zipfile\nimport os\nfrom flask import request, jsonify\n\n@app.route('/api/iot/firmware/deploy', methods=['POST'])\ndef deploy_firmware_package():\n    device_id = request.form.get('device_id')\n    firmware_archive = request.files['firmware']\n    deployment_path = f'/opt/iot/devices/{device_id}/firmware'\n    os.makedirs(deployment_path, exist_ok=True)\n    archive_path = os.path.join('/tmp', firmware_archive.filename)\n    firmware_archive.save(archive_path)\n    with zipfile.ZipFile(archive_path, 'r') as fw_zip:\n        fw_zip.extractall(deployment_path)\n    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})","explanation":"The firmware archive filename from user upload (line 7) flows unsanitized into the zip extraction at line 12. A malicious ZIP file containing entries with path traversal sequences (e.g., '../../../../etc/cron.d/malicious') will extract files outside the intended deployment_path, allowing arbitrary file write on the server filesystem.","remediation":"The fix validates each ZIP entry's resolved path to ensure it stays within the intended deployment directory by using os.path.realpath() and checking the prefix. Additionally, device_id is validated against a whitelist pattern to prevent path traversal in the deployment path itself, and the temporary archive file is cleaned up after processing.","secure_code":"import zipfile\nimport os\nimport re\nfrom flask import request, jsonify\n\n@app.route('/api/iot/firmware/deploy', methods=['POST'])\ndef deploy_firmware_package():\n    device_id = request.form.get('device_id')\n    # Validate device_id to prevent path traversal in deployment_path\n    if not device_id or not re.match(r'^[a-zA-Z0-9_\\-]+$', device_id):\n        return jsonify({'error': 'Invalid device_id'}), 400\n    firmware_archive = request.files['firmware']\n    deployment_path = f'/opt/iot/devices/{device_id}/firmware'\n    os.makedirs(deployment_path, exist_ok=True)\n    # Sanitize the uploaded filename\n    safe_filename = os.path.basename(firmware_archive.filename)\n    archive_path = os.path.join('/tmp', safe_filename)\n    firmware_archive.save(archive_path)\n    try:\n        with zipfile.ZipFile(archive_path, 'r') as fw_zip:\n            # Validate each entry to prevent Zip Slip path traversal\n            for member in fw_zip.namelist():\n                member_path = os.path.realpath(os.path.join(deployment_path, member))\n                if not member_path.startswith(os.path.realpath(deployment_path) + os.sep):\n                    os.remove(archive_path)\n                    return jsonify({'error': f'Malicious path detected in archive: {member}'}), 400\n            fw_zip.extractall(deployment_path)\n    finally:\n        if os.path.exists(archive_path):\n            os.remove(archive_path)\n    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})"}