{"title":"Zip Slip via unsafe zipfile extraction","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[8],"flow_lines":[8,9,10,8,9,13],"sink_lines":[10,13],"vulnerable_code":"import zipfile\nimport os\n\ndef deploy_iot_firmware_package(firmware_zip, device_id):\n    deployment_path = f\"/opt/iot/devices/{device_id}/firmware\"\n    os.makedirs(deployment_path, exist_ok=True)\n    with zipfile.ZipFile(firmware_zip, 'r') as archive:\n        for component in archive.namelist():\n            target = os.path.join(deployment_path, component)\n            if component.endswith('/'):\n                os.makedirs(target, exist_ok=True)\n            else:\n                with open(target, 'wb') as outfile:\n                    outfile.write(archive.read(component))\n    return {\"status\": \"deployed\", \"device\": device_id, \"path\": deployment_path}","explanation":"The code extracts ZIP file entries without validating that the component paths don't contain directory traversal sequences like '../'. A malicious ZIP file can include entries with paths like '../../../../etc/cron.d/malicious' which, when joined with the deployment_path, allows writing files outside the intended directory.","remediation":"The fix resolves the absolute real path of each target file and verifies it starts with the intended deployment directory path, preventing directory traversal attacks. If any archive entry attempts to escape the deployment directory via '../' sequences or symbolic links, a ValueError is raised and extraction is halted.","secure_code":"import zipfile\nimport os\n\ndef deploy_iot_firmware_package(firmware_zip, device_id):\n    deployment_path = f\"/opt/iot/devices/{device_id}/firmware\"\n    os.makedirs(deployment_path, exist_ok=True)\n    with zipfile.ZipFile(firmware_zip, 'r') as archive:\n        for component in archive.namelist():\n            target = os.path.join(deployment_path, component)\n            # Resolve the absolute path and verify it stays within the deployment directory\n            real_target = os.path.realpath(target)\n            real_deployment = os.path.realpath(deployment_path)\n            if not real_target.startswith(real_deployment + os.sep) and real_target != real_deployment:\n                raise ValueError(f\"Unsafe path detected in firmware archive: {component}\")\n            if component.endswith('/'):\n                os.makedirs(real_target, exist_ok=True)\n            else:\n                os.makedirs(os.path.dirname(real_target), exist_ok=True)\n                with open(real_target, 'wb') as outfile:\n                    outfile.write(archive.read(component))\n    return {\"status\": \"deployed\", \"device\": device_id, \"path\": deployment_path}"}