# Arbitrary File Upload via Unvalidated Flask Multipart Handling

Language: Python
Severity: Critical
CWE: CWE-22

## Source
7, 8

## Flow
7-10-11, 8-11

## Sink
11

## Vulnerable Code
```python
from flask import Flask, request
import os

app = Flask(__name__)

@app.route('/iot/firmware/update', methods=['POST'])
def deploy_firmware_patch():
    device_id = request.form.get('device_uuid')
    firmware_blob = request.files['firmware_file']
    storage_path = f'/opt/iot_hub/devices/{device_id}/'
    os.makedirs(storage_path, exist_ok=True)
    firmware_blob.save(os.path.join(storage_path, firmware_blob.filename))
    return {'status': 'firmware_deployed', 'device': device_id}, 200
```

## Explanation

The application accepts unsanitized user input for both device_id and filename parameters without validation. The device_id is used in path construction and filename is directly used from the uploaded file, allowing path traversal attacks (e.g., '../../../etc/malicious') to write arbitrary files anywhere on the filesystem with web server permissions.

## Remediation

The fix validates device_id as a proper UUID to prevent path traversal via that parameter, uses werkzeug's secure_filename() to sanitize the uploaded filename stripping any directory traversal sequences, validates file extensions against an allowlist of firmware formats, and performs realpath checks to ensure both the storage directory and final file path remain within the designated base directory.

## Secure Code
```python
from flask import Flask, request, jsonify
import os
import re
import uuid
from werkzeug.utils import secure_filename

app = Flask(__name__)

BASE_STORAGE_PATH = '/opt/iot_hub/devices/'
ALLOWED_EXTENSIONS = {'.bin', '.fw', '.hex', '.img', '.ota'}
MAX_FIRMWARE_SIZE = 50 * 1024 * 1024  # 50MB

def is_valid_uuid(value):
    try:
        uuid.UUID(str(value))
        return True
    except ValueError:
        return False

def is_allowed_extension(filename):
    _, ext = os.path.splitext(filename)
    return ext.lower() in ALLOWED_EXTENSIONS

@app.route('/iot/firmware/update', methods=['POST'])
def deploy_firmware_patch():
    device_id = request.form.get('device_uuid')
    
    if not device_id or not is_valid_uuid(device_id):
        return {'error': 'Invalid device UUID'}, 400
    
    if 'firmware_file' not in request.files:
        return {'error': 'No firmware file provided'}, 400
    
    firmware_blob = request.files['firmware_file']
    
    if not firmware_blob.filename:
        return {'error': 'Empty filename'}, 400
    
    safe_filename = secure_filename(firmware_blob.filename)
    
    if not safe_filename:
        return {'error': 'Invalid filename'}, 400
    
    if not is_allowed_extension(safe_filename):
        return {'error': 'Invalid firmware file extension'}, 400
    
    storage_path = os.path.join(BASE_STORAGE_PATH, device_id)
    resolved_storage = os.path.realpath(storage_path)
    
    if not resolved_storage.startswith(os.path.realpath(BASE_STORAGE_PATH)):
        return {'error': 'Invalid storage path'}, 400
    
    full_path = os.path.join(resolved_storage, safe_filename)
    resolved_full_path = os.path.realpath(full_path)
    
    if not resolved_full_path.startswith(resolved_storage):
        return {'error': 'Invalid file path'}, 400
    
    os.makedirs(resolved_storage, exist_ok=True)
    firmware_blob.save(resolved_full_path)
    
    return {'status': 'firmware_deployed', 'device': device_id}, 200
```
