{"title":"Pickle Deserialization via __reduce__ Gadget Execution","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[9],"flow_lines":[9,10,11],"sink_lines":[11],"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    encoded_state = request.json.get('device_state')\n    serialized_config = base64.b64decode(encoded_state)\n    device_obj = pickle.loads(serialized_config)\n    device_obj.apply_settings()\n    return jsonify({'status': 'restored', 'device_id': device_obj.device_id})","explanation":"The application accepts base64-encoded serialized data from user input (device_state parameter) and directly deserializes it using pickle.loads() without validation. Pickle deserialization of untrusted data allows attackers to execute arbitrary code by crafting malicious payloads with __reduce__ methods that execute system commands during deserialization.","remediation":"The fix replaces unsafe pickle deserialization with JSON parsing, which cannot execute arbitrary code during deserialization. Additionally, a strict schema validation layer ensures only expected fields with proper types and value ranges are accepted, and an optional HMAC signature verification ensures data integrity and authenticity from trusted edge devices.","secure_code":"import json\nimport base64\nimport hmac\nimport hashlib\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n# Secret key for HMAC verification (should be stored securely, e.g., environment variable)\nSIGNING_SECRET = b'your-secure-signing-secret-here'\n\nALLOWED_DEVICE_FIELDS = {\n    'device_id', 'firmware_version', 'network_config', 'sensor_thresholds',\n    'reporting_interval', 'power_mode', 'display_settings', 'alert_config'\n}\n\nALLOWED_NETWORK_FIELDS = {'ssid', 'ip_address', 'subnet_mask', 'gateway', 'dns'}\n\n\nclass DeviceConfig:\n    \"\"\"Safe device configuration object constructed from validated JSON data.\"\"\"\n\n    def __init__(self, config_dict):\n        self.device_id = config_dict.get('device_id', '')\n        self.firmware_version = config_dict.get('firmware_version', '')\n        self.network_config = config_dict.get('network_config', {})\n        self.sensor_thresholds = config_dict.get('sensor_thresholds', {})\n        self.reporting_interval = config_dict.get('reporting_interval', 60)\n        self.power_mode = config_dict.get('power_mode', 'normal')\n        self.display_settings = config_dict.get('display_settings', {})\n        self.alert_config = config_dict.get('alert_config', {})\n\n    def apply_settings(self):\n        \"\"\"Apply validated device settings.\"\"\"\n        pass\n\n\ndef verify_signature(data_bytes, provided_signature):\n    \"\"\"Verify HMAC signature of the device state payload.\"\"\"\n    expected_signature = hmac.new(SIGNING_SECRET, data_bytes, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected_signature, provided_signature)\n\n\ndef validate_config(config_dict):\n    \"\"\"Validate that the configuration only contains allowed fields and safe values.\"\"\"\n    if not isinstance(config_dict, dict):\n        raise ValueError(\"Configuration must be a JSON object\")\n\n    unknown_fields = set(config_dict.keys()) - ALLOWED_DEVICE_FIELDS\n    if unknown_fields:\n        raise ValueError(f\"Unknown configuration fields: {unknown_fields}\")\n\n    if 'device_id' not in config_dict or not isinstance(config_dict['device_id'], str):\n        raise ValueError(\"Valid device_id is required\")\n\n    if len(config_dict['device_id']) > 64:\n        raise ValueError(\"device_id exceeds maximum length\")\n\n    if 'network_config' in config_dict:\n        if not isinstance(config_dict['network_config'], dict):\n            raise ValueError(\"network_config must be a JSON object\")\n        unknown_net_fields = set(config_dict['network_config'].keys()) - ALLOWED_NETWORK_FIELDS\n        if unknown_net_fields:\n            raise ValueError(f\"Unknown network fields: {unknown_net_fields}\")\n\n    if 'reporting_interval' in config_dict:\n        interval = config_dict['reporting_interval']\n        if not isinstance(interval, (int, float)) or interval < 1 or interval > 86400:\n            raise ValueError(\"reporting_interval must be between 1 and 86400 seconds\")\n\n    if 'power_mode' in config_dict:\n        if config_dict['power_mode'] not in ('normal', 'low_power', 'sleep', 'performance'):\n            raise ValueError(\"Invalid power_mode value\")\n\n    return True\n\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    request_data = request.json\n    if not request_data or 'device_state' not in request_data:\n        return jsonify({'status': 'error', 'message': 'Missing device_state'}), 400\n\n    encoded_state = request_data.get('device_state')\n    signature = request_data.get('signature', '')\n\n    try:\n        config_bytes = base64.b64decode(encoded_state)\n    except Exception:\n        return jsonify({'status': 'error', 'message': 'Invalid base64 encoding'}), 400\n\n    if signature:\n        if not verify_signature(config_bytes, signature):\n            return jsonify({'status': 'error', 'message': 'Invalid signature'}), 403\n\n    try:\n        config_dict = json.loads(config_bytes)\n    except json.JSONDecodeError:\n        return jsonify({'status': 'error', 'message': 'Invalid JSON configuration'}), 400\n\n    try:\n        validate_config(config_dict)\n    except ValueError as e:\n        return jsonify({'status': 'error', 'message': str(e)}), 400\n\n    device_obj = DeviceConfig(config_dict)\n    device_obj.apply_settings()\n\n    return jsonify({'status': 'restored', 'device_id': device_obj.device_id})"}