{"title":"Zip Path Traversal via zipfile.extractall()","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[7],"flow_lines":[7,11,12,13],"sink_lines":[13],"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_zip = request.files['firmware']\n    deployment_path = f'/opt/iot/devices/{device_id}/firmware'\n    os.makedirs(deployment_path, exist_ok=True)\n    zip_path = os.path.join(deployment_path, 'package.zip')\n    firmware_zip.save(zip_path)\n    with zipfile.ZipFile(zip_path, 'r') as zf:\n        zf.extractall(deployment_path)\n    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})","explanation":"The firmware_zip file uploaded by the user is saved and extracted without validating the contents of the ZIP archive. An attacker can craft a malicious ZIP file with path traversal sequences (e.g., ../../) in filenames, causing extractall() to write files outside the intended deployment_path directory, potentially overwriting critical system files.","remediation":"The fix validates each entry in the ZIP archive before extraction by resolving the real path of each member and ensuring it falls within the intended deployment directory. It also validates the device_id parameter to prevent path traversal through that input. If any malicious path is detected, the uploaded ZIP is removed and an error is returned.","secure_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    if not device_id or '..' in device_id or '/' in device_id or '\\\\' in device_id:\n        return jsonify({'status': 'error', 'message': 'Invalid device_id'}), 400\n    firmware_zip = request.files['firmware']\n    deployment_path = f'/opt/iot/devices/{device_id}/firmware'\n    os.makedirs(deployment_path, exist_ok=True)\n    zip_path = os.path.join(deployment_path, 'package.zip')\n    firmware_zip.save(zip_path)\n    with zipfile.ZipFile(zip_path, 'r') as zf:\n        for member in zf.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) and member_path != os.path.realpath(deployment_path):\n                os.remove(zip_path)\n                return jsonify({'status': 'error', 'message': f'Malicious path detected in archive: {member}'}), 400\n        zf.extractall(deployment_path)\n    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})"}