# Unrestricted File Upload Leading to Remote Code Execution

Language: Python
Severity: Critical
CWE: CWE-434

## Source
7-8

## Flow
7-8-10-11-12

## Sink
12

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

app = Flask(__name__)

@app.route('/iot/firmware/update', methods=['POST'])
def update_device_firmware():
    device_id = request.form.get('device_id')
    fw_file = request.files['firmware']
    upload_dir = f'/var/iot/devices/{device_id}/firmware/'
    os.makedirs(upload_dir, exist_ok=True)
    fw_path = os.path.join(upload_dir, fw_file.filename)
    fw_file.save(fw_path)
    os.system(f'python3 {fw_path} --install --device={device_id}')
    return {'status': 'Firmware update initiated', 'device': device_id}
```

## Explanation

The application accepts uploaded files without validating file type, content, or filename, then directly executes the uploaded file using os.system(). An attacker can upload malicious Python code disguised as firmware and achieve remote code execution. The filename is also vulnerable to path traversal attacks.

## Remediation

The fix eliminates RCE by replacing os.system() execution of arbitrary uploaded files with a controlled subprocess call to a dedicated firmware installer binary, using shell=False to prevent command injection. It adds comprehensive input validation including device_id sanitization, filename extension whitelisting, path traversal prevention with realpath checks, firmware signature verification, integrity hash validation, and file size limits. Uploaded files are saved with randomly generated safe filenames rather than user-controlled names.

## Secure Code
```python
from flask import Flask, request, jsonify
import os
import re
import hashlib
import subprocess
import uuid

app = Flask(__name__)

ALLOWED_FIRMWARE_EXTENSIONS = {'.bin', '.fw', '.img'}
FIRMWARE_BASE_DIR = '/var/iot/devices/'
KNOWN_FIRMWARE_HASHES = {}  # In production, load from a secure database/registry

def validate_device_id(device_id):
    """Ensure device_id is alphanumeric to prevent path traversal."""
    if not device_id or not re.match(r'^[a-zA-Z0-9_-]+$', device_id):
        return False
    return True

def validate_firmware_filename(filename):
    """Validate firmware file has allowed extension and no path traversal."""
    if not filename:
        return False
    # Prevent path traversal
    if '..' in filename or '/' in filename or '\\' in filename:
        return False
    _, ext = os.path.splitext(filename)
    if ext.lower() not in ALLOWED_FIRMWARE_EXTENSIONS:
        return False
    return True

def verify_firmware_integrity(file_content, expected_hash=None):
    """Verify firmware file integrity using SHA-256 hash."""
    file_hash = hashlib.sha256(file_content).hexdigest()
    if expected_hash and file_hash != expected_hash:
        return False, file_hash
    return True, file_hash

def validate_firmware_signature(file_content):
    """Check firmware binary signature/magic bytes."""
    # In production, implement proper cryptographic signature verification
    # using vendor-provided public keys
    FIRMWARE_MAGIC_BYTES = b'\x7fFW'  # Example magic bytes for valid firmware
    if not file_content.startswith(FIRMWARE_MAGIC_BYTES):
        return False
    return True

@app.route('/iot/firmware/update', methods=['POST'])
def update_device_firmware():
    device_id = request.form.get('device_id')
    expected_hash = request.form.get('firmware_hash')

    # Validate device_id
    if not validate_device_id(device_id):
        return jsonify({'status': 'error', 'message': 'Invalid device ID'}), 400

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

    fw_file = request.files['firmware']

    # Validate filename
    if not validate_firmware_filename(fw_file.filename):
        return jsonify({'status': 'error', 'message': 'Invalid firmware filename or extension'}), 400

    # Read file content for validation
    file_content = fw_file.read()

    # Check file size (e.g., max 50MB)
    max_size = 50 * 1024 * 1024
    if len(file_content) > max_size:
        return jsonify({'status': 'error', 'message': 'Firmware file too large'}), 400

    # Verify firmware signature
    if not validate_firmware_signature(file_content):
        return jsonify({'status': 'error', 'message': 'Invalid firmware signature'}), 400

    # Verify integrity hash
    if expected_hash:
        is_valid, file_hash = verify_firmware_integrity(file_content, expected_hash)
        if not is_valid:
            return jsonify({'status': 'error', 'message': 'Firmware integrity check failed'}), 400

    # Generate a safe filename to prevent any residual path issues
    safe_filename = f"{uuid.uuid4().hex}.fw"
    upload_dir = os.path.join(FIRMWARE_BASE_DIR, device_id, 'firmware')

    # Ensure the resolved path is within the expected base directory
    resolved_dir = os.path.realpath(upload_dir)
    if not resolved_dir.startswith(os.path.realpath(FIRMWARE_BASE_DIR)):
        return jsonify({'status': 'error', 'message': 'Invalid upload path'}), 400

    os.makedirs(resolved_dir, exist_ok=True)
    fw_path = os.path.join(resolved_dir, safe_filename)

    # Save the firmware binary
    with open(fw_path, 'wb') as f:
        f.write(file_content)

    # Use a dedicated firmware installer tool (not arbitrary script execution)
    # Run with restricted permissions using subprocess with no shell
    try:
        result = subprocess.run(
            ['/usr/local/bin/firmware-installer', '--file', fw_path, '--device', device_id],
            capture_output=True,
            timeout=120,
            shell=False,
            cwd='/var/iot/installer'
        )
        if result.returncode != 0:
            return jsonify({'status': 'error', 'message': 'Firmware installation failed'}), 500
    except subprocess.TimeoutExpired:
        return jsonify({'status': 'error', 'message': 'Firmware installation timed out'}), 500

    return jsonify({'status': 'Firmware update initiated', 'device': device_id})
```
