{"title":"Unsafe Pickle Deserialization via pickle.loads","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[9],"flow_lines":[9,10],"sink_lines":[10],"vulnerable_code":"import pickle\nimport base64\nfrom flask import Flask, request\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    device_config = pickle.loads(base64.b64decode(encoded_state))\n    device_config['last_restored'] = None\n    apply_device_settings(device_config)\n    return {'status': 'restored', 'device_id': device_config.get('id')}, 200","explanation":"The application accepts user-controlled base64-encoded data through the 'device_state' parameter and directly deserializes it using pickle.loads() without any validation. An attacker can craft a malicious pickled object that executes arbitrary code when deserialized, leading to Remote Code Execution (RCE) on the server.","remediation":"The fix replaces pickle deserialization with JSON deserialization, which cannot execute arbitrary code during parsing. Additionally, a strict validation function checks that only allowed keys and safe value types are present in the configuration dictionary, providing defense-in-depth against malformed input.","secure_code":"import json\nimport base64\nimport time\nfrom flask import Flask, request\n\napp = Flask(__name__)\n\nALLOWED_CONFIG_KEYS = {'id', 'name', 'firmware_version', 'network', 'sensors', 'thresholds', 'intervals', 'enabled', 'last_restored'}\nALLOWED_VALUE_TYPES = (str, int, float, bool, list, dict, type(None))\n\n\ndef validate_config(config):\n    \"\"\"Validate that the deserialized config only contains safe types and allowed keys.\"\"\"\n    if not isinstance(config, dict):\n        raise ValueError(\"Device config must be a dictionary\")\n    for key, value in config.items():\n        if key not in ALLOWED_CONFIG_KEYS:\n            raise ValueError(f\"Unexpected config key: {key}\")\n        if not isinstance(value, ALLOWED_VALUE_TYPES):\n            raise ValueError(f\"Invalid value type for key '{key}': {type(value).__name__}\")\n    if 'id' not in config:\n        raise ValueError(\"Device config must contain an 'id' field\")\n    return config\n\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    encoded_state = request.json.get('device_state')\n    if not encoded_state or not isinstance(encoded_state, str):\n        return {'status': 'error', 'message': 'Invalid device_state parameter'}, 400\n\n    try:\n        decoded_data = base64.b64decode(encoded_state)\n        device_config = json.loads(decoded_data)\n    except Exception:\n        return {'status': 'error', 'message': 'Invalid device state format'}, 400\n\n    try:\n        device_config = validate_config(device_config)\n    except ValueError as e:\n        return {'status': 'error', 'message': str(e)}, 400\n\n    device_config['last_restored'] = time.time()\n    apply_device_settings(device_config)\n    return {'status': 'restored', 'device_id': device_config.get('id')}, 200"}