{"title":"Pickle Deserialization via __reduce__","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[11],"flow_lines":[11,15,16],"sink_lines":[16],"vulnerable_code":"import pickle\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    device_id = request.json.get('device_id')\n    encoded_snapshot = request.json.get('config_snapshot')\n    if not encoded_snapshot:\n        return jsonify({'error': 'Missing configuration snapshot'}), 400\n    try:\n        snapshot_bytes = base64.b64decode(encoded_snapshot)\n        device_config = pickle.loads(snapshot_bytes)\n        device_config['device_id'] = device_id\n        device_config['restored_at'] = __import__('datetime').datetime.now().isoformat()\n        return jsonify({'status': 'success', 'config': device_config, 'message': f'Device {device_id} restored'})\n    except Exception as e:\n        return jsonify({'error': 'Failed to restore configuration', 'details': str(e)}), 500","explanation":"The application accepts a base64-encoded pickle object from user input (config_snapshot) and directly deserializes it using pickle.loads() without any validation. Python's pickle module can execute arbitrary code during deserialization through specially crafted payloads using __reduce__ or other magic methods, allowing remote code execution.","remediation":"The fix replaces pickle.loads() with json.loads() for deserialization, eliminating the arbitrary code execution vulnerability since JSON parsing cannot execute code. Additionally, a validation layer checks that configuration keys are from an allowed whitelist and values are safe primitive types, and an optional HMAC signature verification mechanism is added to ensure configuration integrity.","secure_code":"import json\nimport base64\nimport hmac\nimport hashlib\nfrom flask import Flask, request, jsonify\nfrom datetime import datetime\n\napp = Flask(__name__)\n\n# Secret key for HMAC signature verification of config snapshots\n# In production, load from environment variable or secure vault\nCONFIG_SIGNING_KEY = app.config.get('CONFIG_SIGNING_SECRET', 'change-this-to-a-secure-secret-key').encode()\n\nALLOWED_CONFIG_KEYS = {\n    'network', 'wifi_ssid', 'wifi_password', 'firmware_version',\n    'update_interval', 'sensors', 'thresholds', 'name',\n    'location', 'timezone', 'logging_level', 'enabled_features'\n}\n\ndef verify_config_signature(encoded_data, signature):\n    \"\"\"Verify HMAC signature of the configuration data.\"\"\"\n    expected_sig = hmac.new(CONFIG_SIGNING_KEY, encoded_data.encode(), hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected_sig, signature)\n\ndef validate_config(config):\n    \"\"\"Validate that the config only contains allowed keys and safe value types.\"\"\"\n    if not isinstance(config, dict):\n        return False, \"Configuration must be a JSON object\"\n    for key, value in config.items():\n        if key not in ALLOWED_CONFIG_KEYS:\n            return False, f\"Unrecognized configuration key: {key}\"\n        if not isinstance(value, (str, int, float, bool, list, dict, type(None))):\n            return False, f\"Invalid value type for key: {key}\"\n    return True, None\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    device_id = request.json.get('device_id')\n    encoded_snapshot = request.json.get('config_snapshot')\n    signature = request.json.get('signature')\n    if not encoded_snapshot:\n        return jsonify({'error': 'Missing configuration snapshot'}), 400\n    try:\n        # Decode base64 and parse as JSON instead of pickle\n        snapshot_bytes = base64.b64decode(encoded_snapshot)\n        device_config = json.loads(snapshot_bytes)\n\n        # Validate the configuration structure and content\n        is_valid, error_msg = validate_config(device_config)\n        if not is_valid:\n            return jsonify({'error': 'Invalid configuration', 'details': error_msg}), 400\n\n        # Optionally verify signature if provided (recommended for production)\n        if signature:\n            if not verify_config_signature(encoded_snapshot, signature):\n                return jsonify({'error': 'Invalid configuration signature'}), 403\n\n        device_config['device_id'] = device_id\n        device_config['restored_at'] = datetime.now().isoformat()\n        return jsonify({'status': 'success', 'config': device_config, 'message': f'Device {device_id} restored'})\n    except json.JSONDecodeError:\n        return jsonify({'error': 'Invalid configuration format', 'details': 'Snapshot must be valid JSON'}), 400\n    except Exception as e:\n        return jsonify({'error': 'Failed to restore configuration', 'details': str(e)}), 500"}