{"title":"Insecure Deserialization via Pickle","language":"Python","severity":"Critical","cwe":"CWE-502","source_lines":[6],"flow_lines":[6,9],"sink_lines":[9],"vulnerable_code":"import pickle\nimport redis\nfrom multiprocessing import Pool\ndef fetch_and_run_task(worker_id):\n    cache = redis.StrictRedis(host='task-broker.internal', port=6379, db=2)\n    raw_payload = cache.lpop(f'iot_worker_queue:{worker_id}')\n    if not raw_payload:\n        return None\n    task_obj = pickle.loads(raw_payload)\n    result = task_obj.execute()\n    cache.rpush(f'iot_results:{worker_id}', str(result))\n    return result","explanation":"The raw bytes fetched from Redis via `cache.lpop()` on line 6 are untrusted external data. On line 9, `pickle.loads(raw_payload)` deserializes these bytes without any integrity check, signature verification, or allowlist validation. An attacker with write access to Redis or the ability to perform a MITM attack on the Redis connection can inject a crafted pickle payload that executes arbitrary OS commands during deserialization.","remediation":"The fix eliminates `pickle.loads()` entirely and replaces it with `json.loads()`, which cannot execute arbitrary code during deserialization. An HMAC signature (SHA-256) is verified before the payload is parsed, ensuring that only messages produced by trusted parties (who share the secret) are accepted. Finally, an explicit allowlist of task types is enforced and dispatched to hard-coded handler functions, preventing any form of arbitrary code execution.","secure_code":"import json\nimport hmac\nimport hashlib\nimport redis\nfrom multiprocessing import Pool\n\n# This secret must be shared securely between task producers and workers.\n# In production, load from a secrets manager or environment variable.\nimport os\nHMAC_SECRET = os.environ[\"TASK_HMAC_SECRET\"].encode(\"utf-8\")\n\nALLOWED_TASK_TYPES = {\n    \"sensor_read\",\n    \"firmware_update\",\n    \"health_check\",\n    \"config_sync\",\n}\n\n\ndef _compute_hmac(payload_bytes: bytes) -> str:\n    return hmac.new(HMAC_SECRET, payload_bytes, hashlib.sha256).hexdigest()\n\n\ndef fetch_and_run_task(worker_id):\n    cache = redis.StrictRedis(host='task-broker.internal', port=6379, db=2)\n    raw_message = cache.lpop(f'iot_worker_queue:{worker_id}')\n    if not raw_message:\n        return None\n\n    # ---------- integrity check ----------\n    # The producer must push messages in the format:\n    #   <hex-hmac>:<json-payload>\n    # e.g.  cache.rpush(queue, f\"{hmac_hex}:{json_payload}\")\n    try:\n        raw_str = raw_message.decode(\"utf-8\")\n        sep_index = raw_str.index(\":\")\n        received_sig = raw_str[:sep_index]\n        json_payload = raw_str[sep_index + 1:]\n    except (ValueError, UnicodeDecodeError):\n        raise ValueError(\"Malformed task message: missing HMAC signature\")\n\n    expected_sig = _compute_hmac(json_payload.encode(\"utf-8\"))\n    if not hmac.compare_digest(received_sig, expected_sig):\n        raise ValueError(\"Task message HMAC verification failed – payload rejected\")\n\n    # ---------- safe deserialization ----------\n    task_data = json.loads(json_payload)\n\n    if not isinstance(task_data, dict):\n        raise ValueError(\"Task payload must be a JSON object\")\n\n    task_type = task_data.get(\"task_type\")\n    if task_type not in ALLOWED_TASK_TYPES:\n        raise ValueError(f\"Disallowed or unknown task type: {task_type!r}\")\n\n    task_params = task_data.get(\"params\", {})\n    if not isinstance(task_params, dict):\n        raise ValueError(\"Task params must be a JSON object\")\n\n    # ---------- dispatch to a known, safe handler ----------\n    result = _execute_task(task_type, task_params)\n\n    cache.rpush(f'iot_results:{worker_id}', json.dumps({\"result\": result}))\n    return result\n\n\ndef _execute_task(task_type: str, params: dict):\n    \"\"\"Route to a hard-coded handler based on the validated task type.\"\"\"\n    handler = _TASK_HANDLERS.get(task_type)\n    if handler is None:\n        raise ValueError(f\"No handler registered for task type: {task_type!r}\")\n    return handler(params)\n\n\n# ---- Register concrete, safe task handlers here ----\ndef _handle_sensor_read(params):\n    return {\"status\": \"ok\", \"sensor\": params.get(\"sensor_id\")}\n\ndef _handle_firmware_update(params):\n    return {\"status\": \"ok\", \"version\": params.get(\"version\")}\n\ndef _handle_health_check(params):\n    return {\"status\": \"healthy\"}\n\ndef _handle_config_sync(params):\n    return {\"status\": \"synced\"}\n\n\n_TASK_HANDLERS = {\n    \"sensor_read\": _handle_sensor_read,\n    \"firmware_update\": _handle_firmware_update,\n    \"health_check\": _handle_health_check,\n    \"config_sync\": _handle_config_sync,\n}"}