{"title":"Arbitrary Object Instantiation via jsonpickle.decode()","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[8],"flow_lines":[8,11],"sink_lines":[11],"vulnerable_code":"import jsonpickle\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_state():\n    backup_payload = request.json.get('device_state')\n    if not backup_payload:\n        return jsonify({'error': 'Missing device state'}), 400\n    restored_obj = jsonpickle.decode(backup_payload)\n    restored_obj.apply_configuration()\n    return jsonify({'status': 'Device restored', 'device_id': restored_obj.device_id})","explanation":"The application accepts untrusted JSON data from the request and passes it directly to jsonpickle.decode(), which can deserialize arbitrary Python objects. An attacker can craft a malicious payload containing dangerous object types that execute code upon deserialization, leading to remote code execution.","remediation":"The fix removes jsonpickle entirely and replaces it with standard json.loads() for parsing, combined with strict validation of the input against an allowlist of known fields. Instead of deserializing arbitrary objects, the input is parsed as plain JSON data and used to construct a known-safe DeviceState class, eliminating any possibility of arbitrary object instantiation or code execution.","secure_code":"import json\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_DEVICE_FIELDS = {'device_id', 'firmware_version', 'network_config', 'sensor_config', 'thresholds', 'name'}\n\nclass DeviceState:\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_config = config_dict.get('sensor_config', {})\n        self.thresholds = config_dict.get('thresholds', {})\n        self.name = config_dict.get('name', '')\n\n    def apply_configuration(self):\n        # Apply validated configuration to the device\n        pass\n\n\ndef validate_device_state(data):\n    \"\"\"Validate that the device state contains only allowed fields and safe value types.\"\"\"\n    if not isinstance(data, dict):\n        raise ValueError('Device state must be a JSON object')\n    unknown_fields = set(data.keys()) - ALLOWED_DEVICE_FIELDS\n    if unknown_fields:\n        raise ValueError(f'Unknown fields in device state: {unknown_fields}')\n    if 'device_id' not in data or not isinstance(data['device_id'], str):\n        raise ValueError('device_id is required and must be a string')\n    return True\n\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_state():\n    backup_payload = request.json.get('device_state')\n    if not backup_payload:\n        return jsonify({'error': 'Missing device state'}), 400\n\n    try:\n        config_dict = json.loads(backup_payload) if isinstance(backup_payload, str) else backup_payload\n        validate_device_state(config_dict)\n        restored_obj = DeviceState(config_dict)\n    except (json.JSONDecodeError, ValueError) as e:\n        return jsonify({'error': f'Invalid device state: {str(e)}'}), 400\n\n    restored_obj.apply_configuration()\n    return jsonify({'status': 'Device restored', 'device_id': restored_obj.device_id})"}