{"title":"Pickle Deserialization via __reduce__","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[10],"flow_lines":[10,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_state():\n    encoded_state = request.json.get('device_snapshot')\n    if not encoded_state:\n        return jsonify({'error': 'Missing snapshot'}), 400\n    device_blob = base64.b64decode(encoded_state)\n    restored_config = pickle.loads(device_blob)\n    return jsonify({'status': 'restored', 'device_id': restored_config.get('id')})","explanation":"The application accepts untrusted base64-encoded data from user input, decodes it, and directly deserializes it using pickle.loads() without validation. Python's pickle module can execute arbitrary code during deserialization through __reduce__ methods, allowing attackers to achieve remote code execution by crafting malicious pickled objects.","remediation":"The fix replaces pickle.loads() with json.loads(), which is a safe deserialization format that cannot execute arbitrary code during parsing. Additionally, a validation function checks that the deserialized configuration only contains expected keys and required fields, providing defense-in-depth against malformed input.","secure_code":"import json\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_DEVICE_KEYS = {'id', 'name', 'firmware_version', 'config', 'network', 'sensors', 'thresholds', 'enabled'}\n\ndef validate_device_config(config):\n    \"\"\"Validate that the device configuration only contains expected keys and safe value types.\"\"\"\n    if not isinstance(config, dict):\n        return False\n    if not config.get('id'):\n        return False\n    for key in config:\n        if key not in ALLOWED_DEVICE_KEYS:\n            return False\n    return True\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_state():\n    encoded_state = request.json.get('device_snapshot')\n    if not encoded_state:\n        return jsonify({'error': 'Missing snapshot'}), 400\n    try:\n        device_blob = base64.b64decode(encoded_state)\n        restored_config = json.loads(device_blob)\n    except (base64.binascii.Error, json.JSONDecodeError) as e:\n        return jsonify({'error': 'Invalid snapshot format'}), 400\n    if not validate_device_config(restored_config):\n        return jsonify({'error': 'Invalid device configuration'}), 400\n    return jsonify({'status': 'restored', 'device_id': restored_config.get('id')})"}