# Zip Path Traversal via zipfile.extractall()

Language: Python
Severity: Critical
CWE: CWE-22

## Source
7

## Flow
7-11-12-13

## Sink
13

## 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_zip = request.files['firmware']
    deployment_path = f'/opt/iot/devices/{device_id}/firmware'
    os.makedirs(deployment_path, exist_ok=True)
    zip_path = os.path.join(deployment_path, 'package.zip')
    firmware_zip.save(zip_path)
    with zipfile.ZipFile(zip_path, 'r') as zf:
        zf.extractall(deployment_path)
    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})
```

## Explanation

The firmware_zip file uploaded by the user is saved and extracted without validating the contents of the ZIP archive. An attacker can craft a malicious ZIP file with path traversal sequences (e.g., ../../) in filenames, causing extractall() to write files outside the intended deployment_path directory, potentially overwriting critical system files.

## Remediation

The fix validates each entry in the ZIP archive before extraction by resolving the real path of each member and ensuring it falls within the intended deployment directory. It also validates the device_id parameter to prevent path traversal through that input. If any malicious path is detected, the uploaded ZIP is removed and an error is returned.

## Secure 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')
    if not device_id or '..' in device_id or '/' in device_id or '\\' in device_id:
        return jsonify({'status': 'error', 'message': 'Invalid device_id'}), 400
    firmware_zip = request.files['firmware']
    deployment_path = f'/opt/iot/devices/{device_id}/firmware'
    os.makedirs(deployment_path, exist_ok=True)
    zip_path = os.path.join(deployment_path, 'package.zip')
    firmware_zip.save(zip_path)
    with zipfile.ZipFile(zip_path, 'r') as zf:
        for member in zf.namelist():
            member_path = os.path.realpath(os.path.join(deployment_path, member))
            if not member_path.startswith(os.path.realpath(deployment_path) + os.sep) and member_path != os.path.realpath(deployment_path):
                os.remove(zip_path)
                return jsonify({'status': 'error', 'message': f'Malicious path detected in archive: {member}'}), 400
        zf.extractall(deployment_path)
    return jsonify({'status': 'deployed', 'device': device_id, 'path': deployment_path})
```
