# Zip Slip via zipfile.extractall() on Untrusted Archives

Language: Python
Severity: Critical
CWE: CWE-22

## Source
2

## Flow
2-6-7

## Sink
7

## Vulnerable Code
```python
def deploy_iot_firmware_package(device_id, firmware_archive):
    import zipfile
    import os
    deployment_path = f"/opt/iot/devices/{device_id}/firmware"
    os.makedirs(deployment_path, exist_ok=True)
    with zipfile.ZipFile(firmware_archive, 'r') as fw_zip:
        fw_zip.extractall(deployment_path)
    config_file = os.path.join(deployment_path, "device.conf")
    with open(config_file, 'r') as cfg:
        return cfg.read()
```

## Explanation

The function accepts an untrusted firmware_archive parameter and extracts it without validating file paths within the archive. A malicious ZIP file can contain entries with path traversal sequences (e.g., '../../../etc/cron.d/malicious') that allow writing files outside the intended deployment_path directory, potentially overwriting critical system files.

## Remediation

The fix replaces the unsafe extractall() call with a two-pass approach: first validating all archive members to ensure none resolve outside the deployment directory, then extracting each member individually after re-verifying the path. Each member's resolved path is checked against the canonical deployment_path using os.path.realpath() to prevent path traversal via '../' sequences or symlinks.

## Secure Code
```python
def deploy_iot_firmware_package(device_id, firmware_archive):
    import zipfile
    import os
    deployment_path = f"/opt/iot/devices/{device_id}/firmware"
    os.makedirs(deployment_path, exist_ok=True)
    with zipfile.ZipFile(firmware_archive, 'r') as fw_zip:
        for member in fw_zip.infolist():
            member_path = os.path.realpath(os.path.join(deployment_path, member.filename))
            if not member_path.startswith(os.path.realpath(deployment_path) + os.sep) and member_path != os.path.realpath(deployment_path):
                raise ValueError(f"Attempted path traversal in firmware archive: {member.filename}")
        for member in fw_zip.infolist():
            member_path = os.path.realpath(os.path.join(deployment_path, member.filename))
            if not member_path.startswith(os.path.realpath(deployment_path) + os.sep) and member_path != os.path.realpath(deployment_path):
                raise ValueError(f"Attempted path traversal in firmware archive: {member.filename}")
            fw_zip.extract(member, deployment_path)
    config_file = os.path.join(deployment_path, "device.conf")
    with open(config_file, 'r') as cfg:
        return cfg.read()
```
