{"title":"JSONPickle Deserialization 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/telemetry/restore', methods=['POST'])\ndef restore_device_telemetry():\n    archived_data = request.json.get('telemetry_snapshot')\n    if not archived_data:\n        return jsonify({'error': 'Missing telemetry data'}), 400\n    restored_metrics = jsonpickle.decode(archived_data)\n    device_id = restored_metrics.get('device_id', 'unknown')\n    return jsonify({'status': 'restored', 'device': device_id, 'metrics': restored_metrics})","explanation":"The application accepts user-controlled JSON data through the 'telemetry_snapshot' parameter and directly deserializes it using jsonpickle.decode() without validation. Jsonpickle can reconstruct arbitrary Python objects including those with __reduce__ methods, allowing an attacker to execute arbitrary code during deserialization.","remediation":"The fix replaces jsonpickle.decode() with the safe json.loads() function, which only deserializes data into primitive Python types (dicts, lists, strings, numbers, booleans, None) and cannot instantiate arbitrary objects. Additionally, a validation function ensures that the deserialized data only contains expected telemetry keys with primitive value types, providing defense-in-depth against malformed input.","secure_code":"import json\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\n\nALLOWED_METRIC_KEYS = {'device_id', 'temperature', 'humidity', 'pressure', 'battery_level', 'signal_strength', 'timestamp', 'firmware_version', 'uptime'}\n\ndef validate_telemetry_data(data):\n    \"\"\"Validate that telemetry data contains only expected primitive types.\"\"\"\n    if not isinstance(data, dict):\n        return False\n    for key, value in data.items():\n        if key not in ALLOWED_METRIC_KEYS:\n            return False\n        if not isinstance(value, (str, int, float, bool, type(None))):\n            return False\n    return True\n\n@app.route('/iot/telemetry/restore', methods=['POST'])\ndef restore_device_telemetry():\n    archived_data = request.json.get('telemetry_snapshot')\n    if not archived_data:\n        return jsonify({'error': 'Missing telemetry data'}), 400\n    try:\n        restored_metrics = json.loads(archived_data)\n    except (json.JSONDecodeError, TypeError):\n        return jsonify({'error': 'Invalid telemetry data format'}), 400\n    if not validate_telemetry_data(restored_metrics):\n        return jsonify({'error': 'Telemetry data contains invalid or disallowed fields'}), 400\n    device_id = restored_metrics.get('device_id', 'unknown')\n    return jsonify({'status': 'restored', 'device': device_id, 'metrics': restored_metrics})"}