{"title":"Unrestricted File Upload Leading to Remote Code Execution","language":"Python","severity":"Critical","cwe":"CWE-434","source_lines":[7,8],"flow_lines":[7,8,10,11,12],"sink_lines":[12],"vulnerable_code":"from flask import Flask, request\nimport os\n\napp = Flask(__name__)\n\n@app.route('/iot/firmware/update', methods=['POST'])\ndef update_device_firmware():\n    device_id = request.form.get('device_id')\n    fw_file = request.files['firmware']\n    upload_dir = f'/var/iot/devices/{device_id}/firmware/'\n    os.makedirs(upload_dir, exist_ok=True)\n    fw_path = os.path.join(upload_dir, fw_file.filename)\n    fw_file.save(fw_path)\n    os.system(f'python3 {fw_path} --install --device={device_id}')\n    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":"from flask import Flask, request, jsonify\nimport os\nimport re\nimport hashlib\nimport subprocess\nimport uuid\n\napp = Flask(__name__)\n\nALLOWED_FIRMWARE_EXTENSIONS = {'.bin', '.fw', '.img'}\nFIRMWARE_BASE_DIR = '/var/iot/devices/'\nKNOWN_FIRMWARE_HASHES = {}  # In production, load from a secure database/registry\n\ndef validate_device_id(device_id):\n    \"\"\"Ensure device_id is alphanumeric to prevent path traversal.\"\"\"\n    if not device_id or not re.match(r'^[a-zA-Z0-9_-]+$', device_id):\n        return False\n    return True\n\ndef validate_firmware_filename(filename):\n    \"\"\"Validate firmware file has allowed extension and no path traversal.\"\"\"\n    if not filename:\n        return False\n    # Prevent path traversal\n    if '..' in filename or '/' in filename or '\\\\' in filename:\n        return False\n    _, ext = os.path.splitext(filename)\n    if ext.lower() not in ALLOWED_FIRMWARE_EXTENSIONS:\n        return False\n    return True\n\ndef verify_firmware_integrity(file_content, expected_hash=None):\n    \"\"\"Verify firmware file integrity using SHA-256 hash.\"\"\"\n    file_hash = hashlib.sha256(file_content).hexdigest()\n    if expected_hash and file_hash != expected_hash:\n        return False, file_hash\n    return True, file_hash\n\ndef validate_firmware_signature(file_content):\n    \"\"\"Check firmware binary signature/magic bytes.\"\"\"\n    # In production, implement proper cryptographic signature verification\n    # using vendor-provided public keys\n    FIRMWARE_MAGIC_BYTES = b'\\x7fFW'  # Example magic bytes for valid firmware\n    if not file_content.startswith(FIRMWARE_MAGIC_BYTES):\n        return False\n    return True\n\n@app.route('/iot/firmware/update', methods=['POST'])\ndef update_device_firmware():\n    device_id = request.form.get('device_id')\n    expected_hash = request.form.get('firmware_hash')\n\n    # Validate device_id\n    if not validate_device_id(device_id):\n        return jsonify({'status': 'error', 'message': 'Invalid device ID'}), 400\n\n    # Validate file presence\n    if 'firmware' not in request.files:\n        return jsonify({'status': 'error', 'message': 'No firmware file provided'}), 400\n\n    fw_file = request.files['firmware']\n\n    # Validate filename\n    if not validate_firmware_filename(fw_file.filename):\n        return jsonify({'status': 'error', 'message': 'Invalid firmware filename or extension'}), 400\n\n    # Read file content for validation\n    file_content = fw_file.read()\n\n    # Check file size (e.g., max 50MB)\n    max_size = 50 * 1024 * 1024\n    if len(file_content) > max_size:\n        return jsonify({'status': 'error', 'message': 'Firmware file too large'}), 400\n\n    # Verify firmware signature\n    if not validate_firmware_signature(file_content):\n        return jsonify({'status': 'error', 'message': 'Invalid firmware signature'}), 400\n\n    # Verify integrity hash\n    if expected_hash:\n        is_valid, file_hash = verify_firmware_integrity(file_content, expected_hash)\n        if not is_valid:\n            return jsonify({'status': 'error', 'message': 'Firmware integrity check failed'}), 400\n\n    # Generate a safe filename to prevent any residual path issues\n    safe_filename = f\"{uuid.uuid4().hex}.fw\"\n    upload_dir = os.path.join(FIRMWARE_BASE_DIR, device_id, 'firmware')\n\n    # Ensure the resolved path is within the expected base directory\n    resolved_dir = os.path.realpath(upload_dir)\n    if not resolved_dir.startswith(os.path.realpath(FIRMWARE_BASE_DIR)):\n        return jsonify({'status': 'error', 'message': 'Invalid upload path'}), 400\n\n    os.makedirs(resolved_dir, exist_ok=True)\n    fw_path = os.path.join(resolved_dir, safe_filename)\n\n    # Save the firmware binary\n    with open(fw_path, 'wb') as f:\n        f.write(file_content)\n\n    # Use a dedicated firmware installer tool (not arbitrary script execution)\n    # Run with restricted permissions using subprocess with no shell\n    try:\n        result = subprocess.run(\n            ['/usr/local/bin/firmware-installer', '--file', fw_path, '--device', device_id],\n            capture_output=True,\n            timeout=120,\n            shell=False,\n            cwd='/var/iot/installer'\n        )\n        if result.returncode != 0:\n            return jsonify({'status': 'error', 'message': 'Firmware installation failed'}), 500\n    except subprocess.TimeoutExpired:\n        return jsonify({'status': 'error', 'message': 'Firmware installation timed out'}), 500\n\n    return jsonify({'status': 'Firmware update initiated', 'device': device_id})"}