{"title":"Pickle Deserialization via __reduce__","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[9],"flow_lines":[9,11,12],"sink_lines":[12],"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': 'Device configuration restored', 'device_id': device_obj.device_id})\n    return jsonify({'error': 'No state provided'}), 400","explanation":"The application accepts base64-encoded serialized data from an untrusted client request, decodes it, and passes it directly to pickle.loads() without any validation. Pickle deserialization of untrusted data allows arbitrary code execution because attackers can craft malicious pickle payloads using __reduce__ methods to execute arbitrary system commands. The source is the user-controlled input at line 9 (request.json.get), which flows through base64 decoding at line 11 and into the unsafe pickle.loads() call at line 12.","remediation":"The fix replaces unsafe pickle deserialization with JSON deserialization, which cannot execute arbitrary code during parsing. Additionally, it adds input validation with a whitelist of allowed configuration fields, uses a safe DeviceConfig class instead of deserializing arbitrary objects, and optionally supports HMAC signature verification to ensure data integrity from trusted sources. Proper error handling for malformed input is also added.","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 signature verification (should be stored securely in production)\nSIGNING_SECRET = b'your-secure-signing-secret-here'\n\n# Whitelist of allowed device configuration fields\nALLOWED_CONFIG_FIELDS = {'device_id', 'network', 'sensors', 'firmware_version', 'polling_interval', 'thresholds', 'display', 'power_mode'}\n\n\nclass DeviceConfig:\n    \"\"\"Safe device configuration class that only accepts validated fields.\"\"\"\n    def __init__(self, config_dict):\n        self.device_id = config_dict.get('device_id')\n        self.settings = {}\n        for key, value in config_dict.items():\n            if key in ALLOWED_CONFIG_FIELDS:\n                self.settings[key] = value\n\n    def apply_settings(self):\n        # Apply validated settings to the device\n        pass\n\n\ndef verify_signature(data_bytes, signature):\n    \"\"\"Verify HMAC signature to ensure data integrity.\"\"\"\n    expected_sig = hmac.new(SIGNING_SECRET, data_bytes, hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected_sig, signature)\n\n\ndef validate_config(config_dict):\n    \"\"\"Validate that the configuration dictionary contains only safe, expected fields and types.\"\"\"\n    if not isinstance(config_dict, dict):\n        return False\n    if 'device_id' not in config_dict:\n        return False\n    if not isinstance(config_dict.get('device_id'), str):\n        return False\n    for key in config_dict:\n        if key not in ALLOWED_CONFIG_FIELDS:\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:\n        return jsonify({'error': 'No state provided'}), 400\n\n    try:\n        decoded_bytes = base64.b64decode(encoded_state)\n\n        if signature:\n            if not verify_signature(decoded_bytes, signature):\n                return jsonify({'error': 'Invalid signature - data integrity check failed'}), 403\n\n        config_dict = json.loads(decoded_bytes)\n\n        if not validate_config(config_dict):\n            return jsonify({'error': 'Invalid configuration format'}), 400\n\n        device_obj = DeviceConfig(config_dict)\n        device_obj.apply_settings()\n\n        return jsonify({'status': 'Device configuration restored', 'device_id': device_obj.device_id})\n\n    except (json.JSONDecodeError, UnicodeDecodeError):\n        return jsonify({'error': 'Invalid device state encoding'}), 400\n    except Exception:\n        return jsonify({'error': 'Failed to restore device configuration'}), 500"}