# Path Traversal via Zipfile Extraction (Zip Slip)

Language: Python
Severity: Critical
CWE: CWE-22

## Source
9

## Flow
9-11

## Sink
11

## Vulnerable Code
```python
import zipfile, os
def deploy_firmware_package(pkg_path, deploy_dir):
    with zipfile.ZipFile(pkg_path, 'r') as zf:
        for entry in zf.infolist():
            target = os.path.join(deploy_dir, entry.filename)
            if entry.is_dir():
                os.makedirs(target, exist_ok=True)
            else:
                os.makedirs(os.path.dirname(target), exist_ok=True)
                with zf.open(entry) as src, open(target, 'wb') as dst:
                    dst.write(src.read())
```

## Explanation

The function iterates over ZIP entries and constructs the target path by joining deploy_dir with entry.filename without validating that the resolved path stays within deploy_dir. A malicious ZIP entry with a filename like '../../etc/cron.d/backdoor' causes os.path.join to produce a path outside the intended directory, and the file is then written to that arbitrary location via open(target, 'wb').

## Remediation

The fix resolves both the deployment directory and every target path to their canonical absolute forms using os.path.realpath(), then verifies that each resolved target starts with the deployment directory path followed by os.sep (or equals it exactly). Any entry whose resolved path escapes the deployment directory raises a ValueError, preventing writes to arbitrary filesystem locations.

## Secure Code
```python
import zipfile, os

def deploy_firmware_package(pkg_path, deploy_dir):
    # Resolve deploy_dir to an absolute path for reliable comparison
    abs_deploy_dir = os.path.realpath(deploy_dir)

    with zipfile.ZipFile(pkg_path, 'r') as zf:
        for entry in zf.infolist():
            # Construct the target path and resolve any '..' or symlink tricks
            target = os.path.realpath(os.path.join(abs_deploy_dir, entry.filename))

            # Ensure the resolved target is strictly within the deployment directory
            if not (target == abs_deploy_dir or target.startswith(abs_deploy_dir + os.sep)):
                raise ValueError(
                    f"Blocked path traversal attempt: entry '{entry.filename}' "
                    f"resolves to '{target}', which is outside '{abs_deploy_dir}'"
                )

            if entry.is_dir():
                os.makedirs(target, exist_ok=True)
            else:
                os.makedirs(os.path.dirname(target), exist_ok=True)
                with zf.open(entry) as src, open(target, 'wb') as dst:
                    dst.write(src.read())
```
