{"title":"Tar Slip via Unsafe tarfile Extraction Path Traversal","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[8],"flow_lines":[8,9],"sink_lines":[9],"vulnerable_code":"import tarfile\nimport os\n\ndef restore_iot_firmware_backup(backup_archive, device_id):\n    restore_path = f\"/opt/iot_devices/{device_id}/firmware\"\n    os.makedirs(restore_path, exist_ok=True)\n    with tarfile.open(backup_archive, 'r:gz') as tar_handle:\n        for archive_member in tar_handle.getmembers():\n            tar_handle.extract(archive_member, path=restore_path)\n    return {\"status\": \"restored\", \"device\": device_id, \"location\": restore_path}","explanation":"The code extracts tar archive members without validating their paths, allowing path traversal attacks. An attacker can craft a malicious tar.gz file containing members with names like '../../../etc/cron.d/malicious' to write files outside the intended restore_path directory, potentially overwriting critical system files.","remediation":"The fix validates that each tar archive member's resolved extraction path stays within the intended restore directory by using os.path.realpath to resolve the full path and checking it starts with the restore_path prefix. It also checks symbolic and hard links to ensure their targets don't point outside the allowed directory, preventing attackers from using crafted archives to write files to arbitrary locations on the filesystem.","secure_code":"import tarfile\nimport os\n\ndef restore_iot_firmware_backup(backup_archive, device_id):\n    restore_path = os.path.realpath(f\"/opt/iot_devices/{device_id}/firmware\")\n    os.makedirs(restore_path, exist_ok=True)\n    with tarfile.open(backup_archive, 'r:gz') as tar_handle:\n        for archive_member in tar_handle.getmembers():\n            member_path = os.path.realpath(os.path.join(restore_path, archive_member.name))\n            if not member_path.startswith(restore_path + os.sep) and member_path != restore_path:\n                raise ValueError(f\"Attempted path traversal in tar member: {archive_member.name}\")\n            if archive_member.issym() or archive_member.islnk():\n                link_target = os.path.realpath(os.path.join(restore_path, archive_member.linkname))\n                if not link_target.startswith(restore_path + os.sep) and link_target != restore_path:\n                    raise ValueError(f\"Attempted path traversal via symlink in tar member: {archive_member.name}\")\n            tar_handle.extract(archive_member, path=restore_path)\n    return {\"status\": \"restored\", \"device\": device_id, \"location\": restore_path}"}