# Arbitrary File Upload via Extension-Only Validation

Language: Python
Severity: Critical
CWE: CWE-434

## Source
3

## Flow
3-5-8

## Sink
8

## Vulnerable Code
```python
@app.route('/api/v2/iot/firmware', methods=['POST'])
def deploy_device_firmware():
    device_id = request.form.get('device_id')
    fw_package = request.files['firmware']
    allowed = ['.bin', '.hex', '.fw']
    if any(fw_package.filename.endswith(ext) for ext in allowed):
        storage_path = f'/opt/iot_hub/firmware/{device_id}/{fw_package.filename}'
        os.makedirs(os.path.dirname(storage_path), exist_ok=True)
        fw_package.save(storage_path)
        return jsonify({'status': 'deployed', 'path': storage_path})
    return jsonify({'error': 'Invalid firmware format'}), 400
```

## Explanation

The code validates file uploads using only extension checking with endswith(), which can be bypassed using double extensions or manipulated filenames like 'malicious.php.bin'. The unsanitized filename is then directly used in the file path construction, allowing path traversal attacks and arbitrary file upload to any location with crafted filenames.

## Remediation

The fix addresses multiple vulnerabilities: it uses os.path.splitext() instead of endswith() to properly validate only the final extension, applies werkzeug's secure_filename() to strip path traversal sequences, generates a UUID-based unique filename to prevent name-based attacks, validates the device_id against a strict whitelist pattern, resolves the final path and verifies it remains within the intended base directory, and removes the full filesystem path from the response to prevent information disclosure.

## Secure Code
```python
import os
import re
import uuid
import hashlib
from werkzeug.utils import secure_filename

ALLOWED_EXTENSIONS = {'.bin', '.hex', '.fw'}
FIRMWARE_BASE_PATH = '/opt/iot_hub/firmware'
DEVICE_ID_PATTERN = re.compile(r'^[a-zA-Z0-9_\-]{1,64}$')


def validate_firmware_extension(filename):
    """Validate that the file has exactly one allowed extension."""
    if not filename or '.' not in filename:
        return False
    _, ext = os.path.splitext(filename)
    return ext.lower() in ALLOWED_EXTENSIONS


@app.route('/api/v2/iot/firmware', methods=['POST'])
def deploy_device_firmware():
    device_id = request.form.get('device_id')

    # Validate device_id to prevent path traversal via directory name
    if not device_id or not DEVICE_ID_PATTERN.match(device_id):
        return jsonify({'error': 'Invalid device ID'}), 400

    if 'firmware' not in request.files:
        return jsonify({'error': 'No firmware file provided'}), 400

    fw_package = request.files['firmware']

    # Validate extension using os.path.splitext (not endswith)
    if not validate_firmware_extension(fw_package.filename):
        return jsonify({'error': 'Invalid firmware format'}), 400

    # Sanitize the filename to remove path traversal characters
    safe_filename = secure_filename(fw_package.filename)

    # Re-validate after sanitization (secure_filename may alter the name)
    if not safe_filename or not validate_firmware_extension(safe_filename):
        return jsonify({'error': 'Invalid firmware filename'}), 400

    # Generate a unique filename to prevent overwrites and name-based attacks
    _, ext = os.path.splitext(safe_filename)
    unique_name = f"{uuid.uuid4().hex}{ext}"

    # Construct path safely and verify it's within the allowed directory
    storage_dir = os.path.join(FIRMWARE_BASE_PATH, device_id)
    storage_path = os.path.join(storage_dir, unique_name)

    # Resolve to absolute path and verify it stays within base directory
    resolved_path = os.path.realpath(storage_path)
    resolved_base = os.path.realpath(FIRMWARE_BASE_PATH)
    if not resolved_path.startswith(resolved_base + os.sep):
        return jsonify({'error': 'Invalid storage path'}), 400

    os.makedirs(storage_dir, exist_ok=True)
    fw_package.save(resolved_path)

    # Return only the firmware ID, not the full filesystem path
    return jsonify({'status': 'deployed', 'firmware_id': unique_name, 'device_id': device_id})
```
