{"title":"Unsafe jsonpickle Deserialization via decode()","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[8],"flow_lines":[8,10],"sink_lines":[10],"vulnerable_code":"import jsonpickle\nfrom flask import Flask, request\n\napp = Flask(__name__)\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    backup_payload = request.form.get('config_backup')\n    if backup_payload:\n        restored_config = jsonpickle.decode(backup_payload)\n        device_id = restored_config.get('device_id', 'unknown')\n        apply_device_settings(restored_config)\n        return {'status': 'success', 'device': device_id, 'message': 'Configuration restored'}\n    return {'status': 'error', 'message': 'No backup data provided'}, 400\n\ndef apply_device_settings(config):\n    pass","explanation":"The application accepts untrusted user input via request.form.get('config_backup') and directly deserializes it using jsonpickle.decode() without validation. jsonpickle can deserialize arbitrary Python objects including those with __reduce__ methods, allowing attackers to execute arbitrary code during deserialization.","remediation":"The fix replaces jsonpickle.decode() with the standard json.loads() which only deserializes data into safe primitive types (dicts, lists, strings, numbers, booleans, null) and cannot instantiate arbitrary Python objects. Additionally, a validation function checks that the parsed configuration only contains expected keys and safe value types, providing defense-in-depth against malformed input.","secure_code":"import json\nfrom flask import Flask, request\n\napp = Flask(__name__)\n\nALLOWED_CONFIG_KEYS = {'device_id', 'device_name', 'network', 'sensors', 'schedule', 'firmware_version', 'timezone', 'thresholds'}\n\ndef validate_config(config):\n    \"\"\"Validate that the configuration only contains expected keys and safe value types.\"\"\"\n    if not isinstance(config, dict):\n        return False\n    for key in config:\n        if key not in ALLOWED_CONFIG_KEYS:\n            return False\n        value = config[key]\n        if not isinstance(value, (str, int, float, bool, list, dict, type(None))):\n            return False\n    return True\n\n@app.route('/iot/device/restore', methods=['POST'])\ndef restore_device_config():\n    backup_payload = request.form.get('config_backup')\n    if backup_payload:\n        try:\n            restored_config = json.loads(backup_payload)\n        except (json.JSONDecodeError, ValueError):\n            return {'status': 'error', 'message': 'Invalid JSON format'}, 400\n        if not validate_config(restored_config):\n            return {'status': 'error', 'message': 'Invalid configuration structure'}, 400\n        device_id = restored_config.get('device_id', 'unknown')\n        apply_device_settings(restored_config)\n        return {'status': 'success', 'device': device_id, 'message': 'Configuration restored'}\n    return {'status': 'error', 'message': 'No backup data provided'}, 400\n\ndef apply_device_settings(config):\n    pass"}