# Zip Slip via zipfile.extractall()

Language: Python
Severity: Critical
CWE: CWE-22

## Source
7

## Flow
7-10-12

## Sink
12

## Vulnerable Code
```python
import zipfile
import os
from flask import request, jsonify

@app.route('/api/iot/firmware/deploy', methods=['POST'])
def deploy_firmware_package():
    device_id = request.form.get('device_id')
    firmware_archive = request.files['firmware']
    deployment_path = f'/opt/iot/devices/{device_id}/firmware'
    os.makedirs(deployment_path, exist_ok=True)
    archive_path = os.path.join('/tmp', firmware_archive.filename)
    firmware_archive.save(archive_path)
    with zipfile.ZipFile(archive_path, 'r') as fw_zip:
        fw_zip.extractall(deployment_path)
    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})
```

## Explanation

The firmware archive filename from user upload (line 7) flows unsanitized into the zip extraction at line 12. A malicious ZIP file containing entries with path traversal sequences (e.g., '../../../../etc/cron.d/malicious') will extract files outside the intended deployment_path, allowing arbitrary file write on the server filesystem.

## Remediation

The fix validates each ZIP entry's resolved path to ensure it stays within the intended deployment directory by using os.path.realpath() and checking the prefix. Additionally, device_id is validated against a whitelist pattern to prevent path traversal in the deployment path itself, and the temporary archive file is cleaned up after processing.

## Secure Code
```python
import zipfile
import os
import re
from flask import request, jsonify

@app.route('/api/iot/firmware/deploy', methods=['POST'])
def deploy_firmware_package():
    device_id = request.form.get('device_id')
    # Validate device_id to prevent path traversal in deployment_path
    if not device_id or not re.match(r'^[a-zA-Z0-9_\-]+$', device_id):
        return jsonify({'error': 'Invalid device_id'}), 400
    firmware_archive = request.files['firmware']
    deployment_path = f'/opt/iot/devices/{device_id}/firmware'
    os.makedirs(deployment_path, exist_ok=True)
    # Sanitize the uploaded filename
    safe_filename = os.path.basename(firmware_archive.filename)
    archive_path = os.path.join('/tmp', safe_filename)
    firmware_archive.save(archive_path)
    try:
        with zipfile.ZipFile(archive_path, 'r') as fw_zip:
            # Validate each entry to prevent Zip Slip path traversal
            for member in fw_zip.namelist():
                member_path = os.path.realpath(os.path.join(deployment_path, member))
                if not member_path.startswith(os.path.realpath(deployment_path) + os.sep):
                    os.remove(archive_path)
                    return jsonify({'error': f'Malicious path detected in archive: {member}'}), 400
            fw_zip.extractall(deployment_path)
    finally:
        if os.path.exists(archive_path):
            os.remove(archive_path)
    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})
```
