Enviar #838439: 78 xiaozhi-esp32 2aeecd4e014780ac15cfa4866906cca16267010d Denial of Serviceinformación

Título78 xiaozhi-esp32 2aeecd4e014780ac15cfa4866906cca16267010d Denial of Service
Descripción## Vulnerability Title Denial of Service via Hash-Key Confusion in MQTT Goodbye Handling ## Affected Component `main/protocols/mqtt_protocol.cc` Repository: https://github.com/78/xiaozhi-esp32 ## Summary An attacker with the ability to deliver MQTT control messages to a device can craft a `goodbye` payload whose effective application key is either an absent `session_id` or the victim's current `session_id`. The shutdown decision excludes MQTT topic and source context, causing application-level key confusion that leads to premature UDP audio-channel termination for users of the MQTT + UDP audio protocol, resulting in a Denial of Service (DoS). ## Technical Details The vulnerability occurs because `MqttProtocol` computes an application-level message key from only `type == "goodbye"` and `session_id` matching logic, then uses that key for a security-relevant shutdown decision. The effective key does not include critical security fields, such as the incoming MQTT topic or the MQTT publisher/source context. **Vulnerable Code Logic** In `main/protocols/mqtt_protocol.cc`, the incoming message is evaluated. If the payload `type` is `goodbye`, it checks if the `session_id` is missing (`nullptr`) OR if it matches the stored `session_id_`. If either condition is met, it executes `CloseAudioChannel(false)`: ```cpp if (strcmp(type->valuestring, "goodbye") == 0) { auto session_id = cJSON_GetObjectItem(root, "session_id"); ESP_LOGI(TAG, "Received goodbye message, session_id: %s", session_id ? session_id->valuestring : "null"); if (session_id == nullptr || session_id_ == session_id->valuestring) { auto alive = alive_; // Capture alive flag Application::GetInstance().Schedule([this, alive]() { if (*alive) { // Server initiated goodbye, don't send goodbye back to avoid ping-pong CloseAudioChannel(false); } }); } } ``` Because a missing `session_id` acts as a wildcard, an attacker does not even need to guess the valid session ID to trigger the channel reset. Furthermore, the check ignores the incoming `topic` parameter, allowing messages delivered on incorrect or unauthorized topics to successfully trigger the session shutdown pathway. **Conflicting Object Scenario** Legitimate server message for the victim device: ```json { "topic": "devices/A/down", "publisher": "trusted-server", "payload": { "type": "goodbye", "session_id": "sess-42" } } ``` Attacker-controlled or misrouted message: ```json { "topic": "devices/B/down", "publisher": "attacker-or-compromised-client", "payload": { "type": "goodbye" } } ``` Both messages drive the exact same shutdown path because the missing `session_id` satisfies the wildcard branch, breaking the expected security boundaries of the MQTT context. ## Impact This vulnerability allows attackers to: - Instantly terminate active MQTT + UDP audio sessions by injecting or misrouting a malformed `goodbye` control message. - Cause a persistent Denial of Service (DoS) on target devices by repeatedly transmitting `{"type":"goodbye"}` payloads. - Exploit deployments that utilize broad MQTT subscriptions, shared broker credentials, or misconfigured ACLs where message isolation is expected but not enforced by the firmware. ## Proof of Concept The following minimal script demonstrates the broken security invariant by modeling the vulnerable handler's decision rules. It shows how non-equivalent MQTT contexts trigger the same premature channel termination. ```python #!/usr/bin/env python3 """Minimal PoC for application-level key confusion in MQTT goodbye handling.""" class MqttProtocolModel: def __init__(self): self.session_id = "sess-42" self.udp_open = True self.close_calls = 0 def effective_key(self, message): payload = message["payload"] if payload.get("type") != "goodbye": return None incoming_session = payload.get("session_id") if incoming_session is None: return "goodbye:any-session" if incoming_session == self.session_id: return f"goodbye:session:{self.session_id}" return None def on_message(self, message): key = self.effective_key(message) if key is not None: self.udp_open = False self.close_calls += 1 return key victim_server_message = { "topic": "devices/A/down", "publisher": "trusted-server", "payload": {"type": "goodbye", "session_id": "sess-42"}, } attacker_message = { "topic": "devices/B/down", "publisher": "attacker-or-misrouted-client", "payload": {"type": "goodbye"}, } assert victim_server_message != attacker_message protocol = MqttProtocolModel() attacker_key = protocol.on_message(attacker_message) print("attacker effective key:", attacker_key) print("udp_open after attacker message:", protocol.udp_open) print("close_calls:", protocol.close_calls) ``` *Observed output: `udp_open after attacker message: False`, confirming that the unauthenticated, misrouted attacker message successfully triggers audio closure.* ## Remediation Modify the logic in `MqttProtocol::OnMessage` to enforce strict validation on the `goodbye` payload and context. Reject payloads missing a valid string-based `session_id` and ensure that the incoming topic matches the device's designated inbound downlink topic. Example remediation layout: ```cpp if (strcmp(type->valuestring, "goodbye") == 0) { cJSON* session_id = cJSON_GetObjectItem(root, "session_id"); if (!cJSON_IsString(session_id)) { cJSON_Delete(root); return; // Reject wildcard/missing session IDs } if (!IsExpectedInboundTopic(topic)) { cJSON_Delete(root); return; // Reject messages delivered on unexpected topics } if (session_id_ == session_id->valuestring) { CloseAudioChannel(false); } } ``` Additional mitigations: 1. Reject Wildcards: Disallow any processing of `goodbye` payloads that lack explicit parameter validation. 2. Broker-Side Authorization: Implement strict broker ACL rules ensuring that only authenticated server components can write to device downlink branches. 3. Complete Field Coverage: Build session validation checks that explicitly bind the context of the session ID together with the expected transport boundaries. ## References - Vulnerable MQTT callback handling: `https://github.com/78/xiaozhi-esp32/blob/2aeecd4e014780ac15cfa4866906cca16267010d/main/protocols/mqtt_protocol.cc#L100-L126` - Audio channel termination path: `https://github.com/78/xiaozhi-esp32/blob/2aeecd4e014780ac15cfa4866906cca16267010d/main/protocols/mqtt_protocol.cc#L192-L212` - Protocol specification documentation: `https://github.com/78/xiaozhi-esp32/blob/2aeecd4e014780ac15cfa4866906cca16267010d/docs/mqtt-udp.md#L160-L177`
Fuente⚠️ https://github.com/78/xiaozhi-esp32/issues/2022
Usuario
 dem0000 (UID 98390)
Sumisión2026-05-27 09:20 (hace 1 mes)
Moderación2026-06-27 18:04 (1 month later)
EstadoAceptado
Entrada de VulDB374488 [78 xiaozhi-esp32 hasta 2.2.6 MQTT Goodbye mqtt_protocol.cc Application::GetInstance session_id denegación de servicio]
Puntos20

Do you want to use VulDB in your project?

Use the official API to access entries easily!