{"title":"Insecure Deserialization via Python Pickle in Multiprocessing Queue IPC","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[9],"flow_lines":[9,11],"sink_lines":[11],"vulnerable_code":"import pickle\nimport multiprocessing\nimport base64\nfrom flask import Flask, request\napp = Flask(__name__)\ntask_queue = multiprocessing.Queue()\n@app.route('/submit_sensor_payload', methods=['POST'])\ndef ingest_sensor_data():\n    raw_b64 = request.form.get('sensor_blob')\n    decoded_bytes = base64.b64decode(raw_b64)\n    sensor_obj = pickle.loads(decoded_bytes)\n    task_queue.put(sensor_obj)\n    return {'status': 'queued', 'type': str(type(sensor_obj).__name__)}, 202","explanation":"The endpoint reads `sensor_blob` directly from attacker-controlled HTTP form data (line 9), base64-decodes it (line 10), and passes the resulting bytes to `pickle.loads()` (line 11) without any validation, signing, or integrity check. Python's pickle deserialization can execute arbitrary code during the unpickling process via `__reduce__` or similar magic methods, giving an attacker full remote code execution on the gateway host.","remediation":"The fix completely replaces `pickle.loads()` with `json.loads()`, which is a safe deserialization format that cannot execute arbitrary code during parsing. Additionally, a strict schema validation function (`validate_sensor_data`) is applied to the deserialized data, ensuring only expected fields with allowed types are accepted. Input size limits and proper error handling for base64 decoding and JSON parsing are also added to harden the endpoint.","secure_code":"import json\nimport multiprocessing\nimport base64\nfrom flask import Flask, request, jsonify\n\napp = Flask(__name__)\ntask_queue = multiprocessing.Queue()\n\n# Define the strict schema of allowed sensor data fields and types\nALLOWED_SENSOR_FIELDS = {\n    \"device_id\": str,\n    \"timestamp\": (int, float),\n    \"temperature\": (int, float),\n    \"humidity\": (int, float),\n    \"pressure\": (int, float),\n    \"battery_level\": (int, float),\n    \"status\": str,\n}\n\nMAX_PAYLOAD_SIZE = 1 * 1024 * 1024  # 1 MB limit\n\n\ndef validate_sensor_data(data):\n    \"\"\"Validate that sensor data conforms to the expected schema.\"\"\"\n    if not isinstance(data, dict):\n        raise ValueError(\"Sensor data must be a JSON object\")\n\n    # Require at least a device_id\n    if \"device_id\" not in data:\n        raise ValueError(\"Missing required field: device_id\")\n\n    for key, value in data.items():\n        if key not in ALLOWED_SENSOR_FIELDS:\n            raise ValueError(f\"Unexpected field: {key}\")\n        expected_type = ALLOWED_SENSOR_FIELDS[key]\n        if not isinstance(value, expected_type):\n            raise ValueError(\n                f\"Invalid type for field '{key}': expected {expected_type}, got {type(value).__name__}\"\n            )\n\n    return data\n\n\n@app.route('/submit_sensor_payload', methods=['POST'])\ndef ingest_sensor_data():\n    raw_b64 = request.form.get('sensor_blob')\n    if not raw_b64:\n        return jsonify({'error': 'Missing sensor_blob field'}), 400\n\n    # Enforce payload size limit before decoding\n    if len(raw_b64) > MAX_PAYLOAD_SIZE:\n        return jsonify({'error': 'Payload too large'}), 413\n\n    try:\n        decoded_bytes = base64.b64decode(raw_b64, validate=True)\n    except Exception:\n        return jsonify({'error': 'Invalid base64 encoding'}), 400\n\n    # Use JSON for safe deserialization instead of pickle\n    try:\n        sensor_obj = json.loads(decoded_bytes)\n    except (json.JSONDecodeError, UnicodeDecodeError):\n        return jsonify({'error': 'Invalid JSON payload'}), 400\n\n    # Validate the deserialized data against a strict schema\n    try:\n        validated_data = validate_sensor_data(sensor_obj)\n    except ValueError as e:\n        return jsonify({'error': f'Validation failed: {str(e)}'}), 422\n\n    task_queue.put(validated_data)\n    return jsonify({'status': 'queued', 'device_id': validated_data.get('device_id', 'unknown')}), 202"}