{"title":"Tar Slip via Unsafe tarfile Extraction","language":"Python","severity":"Critical","cwe":"CWE-22","source_lines":[1],"flow_lines":[1,4,6,7],"sink_lines":[7],"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 member in tar_handle.getmembers():\n            tar_handle.extract(member, path=restore_path)\n    return {\"status\": \"restored\", \"device\": device_id, \"location\": restore_path}","explanation":"The function accepts an untrusted backup_archive path and extracts tar members without validating their paths. A malicious tar archive can contain path traversal sequences (../) in member names, allowing files to be written outside the intended restore_path directory, potentially overwriting critical system files.","remediation":"The fix validates each tar member's resolved path to ensure it stays within the intended restore_path directory before extraction. It uses os.path.realpath to resolve any relative path components (like ../) and checks that the resulting absolute path starts with the restore directory prefix. Additionally, it checks symlinks and hardlinks to prevent indirect path traversal attacks.","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 member in tar_handle.getmembers():\n            member_path = os.path.realpath(os.path.join(restore_path, 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: {member.name}\")\n            if member.issym() or member.islnk():\n                link_target = os.path.realpath(os.path.join(restore_path, os.path.dirname(member.name), 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: {member.name}\")\n            tar_handle.extract(member, path=restore_path)\n    return {\"status\": \"restored\", \"device\": device_id, \"location\": restore_path}"}