{"title":"Pickle Deserialization via __reduce__","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    if encoded_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.id})\n    return jsonify({'error': 'missing state'}), 400","explanation":"The application accepts untrusted user input containing a base64-encoded pickled object, decodes it, and directly deserializes it using pickle.loads(). Pickle deserialization of untrusted data allows remote code execution because pickle can instantiate arbitrary objects and execute code through __reduce__ methods during deserialization.","remediation":"The fix replaces pickle deserialization with JSON parsing, which cannot execute arbitrary code during deserialization. Additionally, it adds HMAC signature verification to ensure data integrity, a whitelist-based validation of configuration fields, and a safe DeviceConfig class that only accepts known properties rather than deserializing arbitrary objects.","secure_code":"import json\nimport base64\nimport hmac\nimport hashlib\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nSIGNING_SECRET = b'your-secure-signing-secret-here'\n\nALLOWED_CONFIG_FIELDS = {\n    'id', 'name', 'firmware_version', 'network_settings',\n    'sensor_config', 'reporting_interval', 'power_mode',\n    'thresholds', 'enabled_features'\n}\n\n\nclass DeviceConfig:\n    \"\"\"Safe device configuration object with validated fields.\"\"\"\n\n    def __init__(self, config_dict):\n        self.id = config_dict.get('id')\n        self.name = config_dict.get('name')\n        self.firmware_version = config_dict.get('firmware_version')\n        self.network_settings = config_dict.get('network_settings', {})\n        self.sensor_config = config_dict.get('sensor_config', {})\n        self.reporting_interval = config_dict.get('reporting_interval')\n        self.power_mode = config_dict.get('power_mode')\n        self.thresholds = config_dict.get('thresholds', {})\n        self.enabled_features = config_dict.get('enabled_features', [])\n\n    def apply_settings(self):\n        \"\"\"Apply validated device settings.\"\"\"\n        pass\n\n\ndef compute_signature(data: bytes) -> str:\n    \"\"\"Compute HMAC signature for data integrity verification.\"\"\"\n    return hmac.new(SIGNING_SECRET, data, hashlib.sha256).hexdigest()\n\n\ndef verify_signature(data: bytes, signature: str) -> bool:\n    \"\"\"Verify HMAC signature to ensure data integrity.\"\"\"\n    expected = hmac.new(SIGNING_SECRET, data, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, 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        return False\n    if not set(config_dict.keys()).issubset(ALLOWED_CONFIG_FIELDS):\n        return False\n    if 'id' not in config_dict or not isinstance(config_dict['id'], str):\n        return False\n    return True\n\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    encoded_state = request.json.get('device_state')\n    signature = request.json.get('signature')\n\n    if not encoded_state or not signature:\n        return jsonify({'error': 'missing state or signature'}), 400\n\n    try:\n        raw_data = base64.b64decode(encoded_state)\n    except Exception:\n        return jsonify({'error': 'invalid base64 encoding'}), 400\n\n    if not verify_signature(raw_data, signature):\n        return jsonify({'error': 'invalid signature - data may be tampered'}), 403\n\n    try:\n        config_dict = json.loads(raw_data)\n    except (json.JSONDecodeError, ValueError):\n        return jsonify({'error': 'invalid configuration format'}), 400\n\n    if not validate_config(config_dict):\n        return jsonify({'error': 'invalid configuration fields'}), 400\n\n    device_obj = DeviceConfig(config_dict)\n    device_obj.apply_settings()\n\n    return jsonify({'status': 'restored', 'device_id': device_obj.id})"}