تسريب Debug Topics في MQTT: لما الجهاز يكتب مذكراته ويخليها للكل
الـ retained MQTT debug topic زي ما تترك المفتاح الرئيسي على لوحة إعلانات عامة وما حد يمسحه. هنا كيف تحليل الـ firmware وكلمة مرور ضعيفة على الـ broker وعلم retain واحد يحول الـ thermostat إلى خزنة مفتوحة.
تخيل تدخل مبنى ذكي والـ thermostat قاعد يثرثر على قناة عامة. مو بس درجة الحرارة. الجهاز ينشر device ID حقه، نسخة الـ firmware، provision token، وملاحظة لطيفة تقول “هنا البيانات الحساسة، تفضلوا خذوها”. الرسالة معلّمة retain، فأي مشترك جديد يستلم النسخة كاملة أول ما يتصل. هذا مو سيناريو خيالي. هذا اللي يصير لما تظل debug topics مفعّلة في الـ production والـ broker يعامل كل عميل مصادق عليه كأنه من العائلة.
المقال هذا يمشي على النمط الأساسي: كيف firmware جهاز IoT يكشف تفاصيل الـ broker وبنية الـ topics، كيف كلمة مرور ضعيفة تفتح الباب، وكيف رسالة debug واحدة retained تعطي المهاجم كل اللي يبيه. ما فيه دراما. بس جهاز ما تعلم يسكت.
وش هو تسريب Debug Topic في MQTT أصلاً؟
MQTT بروتوكول خفيف publish/subscribe مصمم للأجهزة المحدودة. العملاء يتصلون بـ broker، يشتركون في topics، وينشرون رسائل. فيه خاصيتين تخليانه ممتع من ناحية أمنية:
- Retained messages. لما الناشر يحط علم retain، الـ broker يحفظ آخر رسالة على الـ topic ويعطيها فوراً لأي مشترك مستقبلي. مفيدة لحالة “آخر معلومة معروفة”. خطيرة لما المعلومة سر.
- Hierarchical topics والـ wildcards. الـ topics شكلها
building/floor3/thermostat/status. المشترك يقدر يستخدم#أو+عشان ياخذ شجرة كاملة. إذا الـ ACL ناقص أو فضفاض، عميل واحد مصادق عليه يقدر يقرأ كل شيء.
الـ “debug topic” ببساطة topic يستخدمه الـ firmware (أو السيرفر) للتشخيص: device ID، أعلام البناء، tokens داخلية، dumps للإعدادات، وأحياناً أعلام باقية من مرحلة التطوير. لما هذا الـ topic ينشر مع retain=1 وأي مستخدم مصادق عليه يقدر يشترك فيه، عندك تسريب معلومات كلاسيكي يعيش بعد إعادة التشغيل وانقطاع العملاء.
الثغرة نادراً تكون “MQTT مكسور”. غالباً تكون مزيج من:
- بيانات اعتماد ضعيفة أو مشتركة أو قابلة للاستخراج من الـ firmware
- قنوات debug/diagnostic تظل مفتوحة
- ACLs على الـ topics فضفاضة جداً (أو ما فيه ACLs أصلاً)
- استخدام retain على بيانات ما المفروض تكون لزجة
فكر فيها كفرق بين ملف log خاص وورقة لاصقة على ثلاجة المكتب. نفس المحتوى. جمهور مختلف جداً.
ليش تهتم؟
انتشار IoT يحب MQTT. Thermostats، حساسات، gateways، أقفال ذكية، متحكمات صناعية. كثير منها يطلع مع:
- أسماء مستخدمين وـ broker hostnames مكتوبة في الـ firmware أو سهلة الاستخراج
- كلمات مرور ما طلعت من قوائم المصنع
- debug topics ما انقفلت بعلم compile-time
- brokers تقبل نفس اسم المستخدم لكل الأجهزة وتعطي وصول كامل للـ topics
النتائج الحقيقية تظهر كـ:
- تسريب credentials و tokens. provision tokens، OTA salts، device IDs، وملاحظات داخلية تمشي على الشبكة وتبقى على الـ broker.
- حركة جانبية. credential جهاز واحد مخترق غالباً يقرأ بيانات كل الأجهزة الثانية تحت نفس الـ ACL.
- استمرارية بدون استمرارية. الـ retained messages تعيش بعد الناشر. المهاجم يتصل مرة ويجمع الـ dump لاحقاً.
- سلسلة فيزيائية + شبكة. firmware مستخرج من وحدة متروكة أو مهملة يصير الخريطة للـ broker الحي.
إذا المبنى (أو المنتج) يعامل الـ MQTT broker كـ “داخلي بس”، أنت على بعد كلمة مرور ضعيفة واحدة من تسليم المخزون الكامل للأجهزة وأسرارها لبرّه.
مثال عملي: من الـ Firmware إلى الـ Dump الـ Retained
هنا سيناريو عام اصطناعي يطابق النمط الشائع.
تستخرج binary firmware من thermostat تجاري. هو ELF 32-bit Xtensa (فئة ESP)، مو stripped، ولسه فيه debug symbols وstrings قابلة للقراءة. الفحص الأولي القياسي:
file firmware.bin
strings firmware.bin | grep -E 'mqtt|broker|topic|debug|device'
readelf -W -s firmware.bin | grep -E 'mqtt|debug|connect|pub'
الـ strings فوراً تطلع قوالب topics وأسماء دوال:
device/%s/status
device/%s/telemetry
device/%s/diag
device/%s/$debug
[MQTT] Connecting broker=%s user=%s
[NG-MQTT] Debug payload published to %s (retain=1)
BUG: password not stored in firmware — provisioned at manufacture
عكس مسار الاتصال يظهر إن العميل يصادق باسم مستخدم ثابت وكلمة مرور NULL داخل الـ firmware نفسه. كلمة المرور الحقيقية موجودة فقط على جانب الـ broker (اتوفرت وقت التصنيع وما اتغيرت أبداً). روتين نشر الـ debug يبني topic بالشكل device/<device-id>/$debug، يحزم كائن JSON فيه model ونسخة firmware وprovision token وملاحظة داخلية، ثم ينشره مع علم retain.
الـ device ID وبعض الأسرار الثانية مخزنة كمصفوفات بايتات مشفرة خفيفاً (XOR متناوب بسيط). فكها سهل بعد ما تستخرج الثابت:
def decode(enc):
return bytes(b ^ (0xAB if i % 2 == 0 else 0xCD) for i, b in enumerate(enc))
الآن تعرف اسم المستخدم، خريطة الـ topics، وإن فيه رسالة debug retained. القطعة الناقصة هي كلمة مرور الـ broker.
قائمة قصيرة من المرشحين مبنية على اسم المنتج وقوائم كلمات شائعة تكفي. واحد منها يصادق. من هنا اشتراك واحد يكفي:
mosquitto_sub -h broker.example -p 1883 \
-u shared_user -P recovered_password \
-t 'device/#' -v
من ضمن الرسائل الـ retained يطلع الـ debug payload:
{
"device_id": "TH-2000-4f7a",
"fw_version": "2.3.1",
"debug_build": true,
"provision_token": "a1b2c3d4",
"note": "server-provisioned diagnostic data"
}
هذه السلسلة كاملة: فحص firmware → هوية و خريطة topics قابلة للاستخراج → كلمة مرور مشتركة ضعيفة → debug topic retained → بيانات حساسة.
التغيرات المجاورة سهلة التصور. إذا الـ debug topic مو retained لكنه ينشر بشكل دوري، اشتراك قصير يمسكه. إذا الـ ACLs تقيد الـ device ID الدقيق لكن تسمح بـ wildcard أعلى، نفس التسريب يصير. إذا كلمة المرور هي DJB2 hash لسلسلة معروفة، الكسر offline يستبدل محاولة قائمة الكلمات online. الدرس الأساسي يبقى نفسه.
أمثلة كود ضعيفة
عميل MQTT (مفهومي بأسلوب C / ESP)
النسخة الضعيفة
// device_id و username مستخرجين من الـ firmware؛ كلمة المرور NULL في الـ binary
mqtt_client.setServer(broker_host, 1883);
mqtt_client.connect(device_id, "shared_user", NULL); // كلمة المرور الحقيقية على الـ broker فقط
char debug_topic[64];
snprintf(debug_topic, sizeof(debug_topic), "device/%s/$debug", device_id);
char payload[256];
snprintf(payload, sizeof(payload),
"{\"device_id\":\"%s\",\"fw\":\"%s\",\"token\":\"%08x\",\"note\":\"diag\"}",
device_id, FW_VERSION, provision_token);
mqtt_client.publish(debug_topic, payload, true); // retain = true
// أي عميل يصادق كـ shared_user يقدر لاحقاً يشترك ويستلم هذا.
علم retain يحول نشر تشخيصي لمرة واحدة إلى تخزين دائم على الـ broker. مع اسم مستخدم مشترك وبدون ACL لكل جهاز، كل مشترك مستقبلي ياخذ الـ dump.
النسخة المُصلحة
// بيانات اعتماد قوية وفريدة لكل جهاز؛ كلمة المرور أبداً ما تكون NULL
mqtt_client.setServer(broker_host, 1883);
mqtt_client.connect(device_id, device_user, device_password);
// الـ debug topic يتجمع فقط لما DEBUG معرف، وأبداً ما يكون retained
#ifdef DEBUG
char debug_topic[64];
snprintf(debug_topic, sizeof(debug_topic), "device/%s/$debug", device_id);
mqtt_client.publish(debug_topic, payload, false); // بدون retain
#endif
أحسن بعد: شيل مسار نشر الـ debug من بناءات الـ production تماماً، وفرض ACLs على الـ broker بحيث الجهاز المخترق ما يقدر يقرأ topics الأجهزة الثانية.
ACL على جانب الـ Broker (مثال Mosquitto)
ضعيف
user shared_user
topic readwrite #
مُحصّن
user device_TH2000_4f7a
topic read device/TH-2000-4f7a/#
topic write device/TH-2000-4f7a/telemetry
topic write device/TH-2000-4f7a/status
# ما فيه وصول لأي $debug أو أجهزة ثانية
الدفاع / كيف تصلح
- عامل debug topics كمخاطر production. قفلها وراء أعلام compile-time. أبداً ما تشحن payloads تشخيصية retained فيها tokens أو ملاحظات داخلية أو مخزون أجهزة.
- استخدم بيانات اعتماد فريدة لكل جهاز. أسماء مستخدمين مشتركة + كلمات مرور مصنع هدية. فضل شهادة (mTLS) أو tokens قصيرة العمر قدر الإمكان.
- فرض ACLs على الـ topics. العميل يقرأ ويكتب فقط الـ topics اللي تخصه. الـ wildcards للمستخدمين الإداريين فقط، وحتى هم بحذر.
- عطل retain على البيانات الحساسة. آخر درجة حرارة معروفة تمام. آخر provision token لا.
- دور بيانات الاعتماد وامسح الرسائل الـ retained لما جهاز ينسحب أو كلمة مرور يشتبه فيها.
mosquitto_pub -n -r -t topicهي الطريقة المعتادة لمسح رسالة retained. - افترض إن الـ firmware بيتستخرج. التعمية (XOR بسيط، packing خفيف) تشتري دقائق، مو أمان. صمم كأن المهاجم عنده الـ binary أصلاً.
- راقب الـ broker. اشتراكات غير متوقعة على
$debugأو#أو topics إدارية تستاهل تنبيه.
خلاصة
MQTT ممتاز في نقل رسائل صغيرة بين أجهزة محدودة. وهو كمان ممتاز في الاحتفاظ بهذي الرسائل بهدوء لأي حد يصادق بعده. لما الرسالة تكون debug dump والمصادقة تكون كلمة مرور مشتركة ضعيفة مستخرجة من firmware، المبنى “الذكي” يبدأ يبان أقل ذكاء بكثير.
أأمن debug topic هو اللي ما يتشحن أصلاً. الثاني أأمن هو اللي يطلب credential فريد، ما عنده علم retain، ويعيش وراء ACL يعامل كل عميل ثاني كغير موثوق.
وقفوا أجهزةكم عن كتابة مذكرات عامة.
المراجع
- مواصفة OASIS MQTT Version 5.0 — الرسائل الـ retained وفلاتر الـ topics: https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html
- معيار التحقق من أمن IoT من OWASP (ISVS) — إرشادات MQTT والرسائل: https://owasp.org/www-project-iot-security-verification-standard/
- وثائق Eclipse Mosquitto — الـ ACLs والرسائل الـ retained: https://mosquitto.org/documentation/
- أساسيات أمن MQTT من HiveMQ: https://www.hivemq.com/mqtt-security-fundamentals/
- NIST SP 800-213 — إرشادات أمن أجهزة IoT: https://csrc.nist.gov/publications/detail/sp/800-213/final