در طی مسابقات Pwn2Own توکیو در پاییز گذشته، اشخاصی به نام Pedro Ribeiro و Radek Domanski از آسیبپذیری تزریق فرمان به عنوان بخشی از زنجیره بهرهبرداری برای اجرای کد بر روی روتر بی سیم TP-Link Archer A7 استفاده کردند که 5000 دلار برای آنها درآمد داشت. باگ به کار رفته در این بهرهبرداری اخیراً وصله شده است. در این گزارش به بررسی آسیبپذیری تزریق فرمان که در نوامبر 2019 منتشر شد میپردازیم.
این آسیبپذیری در سرور tdp daemon (/usr/bin/tdpServer) در روتر TP- Link Archer A7 (AC1750) با نسخه سختافزار 5، معماری MIPS و سیستمعامل نسخه 190726 وجود دارد که توسط مهاجمی در سمت LAN روتر قابل بهرهبرداری است و نیاز به احراز هویت ندارد.
پس از بهرهبرداری مهاجم قادر است هر دستوری شامل بارگیری و اجرای یک دودویی از یک میزبان دیگر را با دسترسی root اجرا کند. این آسیب پذیری با نام CVE-2020-10882 شناخته میشود و توسط TP-Link با نسخه سیستمعامل A7 (US) _V5_200220 رفع شده است. در کلیه قطعه کدها و توابع در این گزارش از /usr / bin / tdpServer، نسخه سیستم عامل 190726 استفاده شده است.
1 جزئیات tdpServer
سرور tdp daemon به پورت 20002 UDP بر روی خط اتصال 0.0.0.0 گوش میدهد. عملکرد کلی daemon در این مرحله توسط نویسندگان کاملاً درک نشده است، زیرا این مورد برای بهرهبرداری غیر ضروری بود. با این حال به نظر میرسد daemon پلی بین برنامههای موبایلی و روتر TP-Link، با امکان ایجاد نوعی کانال کنترل از برنامه موبایل است. سرور daemon با برنامههای موبایل از طریق بستههای UDP با محتوای رمزگذاری شده ارتباط برقرار میکند. قالب بسته معکوس شده و در شکل 1 قابل مشاهده است.
نوع بسته تعیین میکند چه سرویسی در Daemon مورد استفاده قرار خواهد گرفت. نوع 1 باعث میشود Daemon از سرویس tdpd استفاده کند، که بستهها را به آسانی با یک مقدار hash مشخص TETHER_KEY پاسخ میدهد. این مسئله به آسیبپذیری مربوط نیست، بنابراین جزییات آن ذکر نشده است.
نوع دیگر بستهها 0xf0 است که از سرویس onemesh استفاده میکند؛ در این سرویس آسیبپذیری نهفته است. به نظر میرسد سرویس onemesh یک فناوری mesh اختصاصی است که توسط TP-Link در نسخههای سیستمعامل اخیر تعدادی از روترهای آنها معرفی شده است.
2 بررسی آسیبپذیری
هنگامی که دستگاه شروع به کار میکند، اولین تابعی که فراخوانی میشود tdpd_pkt_handler_loop() (آدرس 0x40d164) است که یک سوکت UDP باز کرده و به پورت 20002 گوش میدهد. هنگامی که بسته دریافت شد، این تابع بسته را به tpdp_pkt_parser() (0x40cfe0) تحویل میدهد. یک قطعه از کد این تابع در شکل 2 نشان داده شده است:
در اولین قطعه، تجزیه کننده ابتدا بررسی میکند که اندازه بسته گزارش شده توسط سوکت UDP حداقل 0x10 (به اندازه header) باشد. سپس tdpd_get_pkt_len() (0x40d620) را فراخوانی میکند. این تابع مقدار طول بستههای مشخص شده مطابق با header بستهها (فیلد len) را برمیگرداند. اگر طول بسته از 0x410 تجاوز کند، تابع مقدار 1- را برمیگرداند.
بررسی نهایی توسط tdpd_pkt_sanity_checks() (0x40c9d0) انجام میشود، که به علت اختصار نشان داده نمیشود، اما دو تأیید انجام میدهد: ابتدا بررسی میکند نسخه بسته (فیلد نسخه، اولین بایت در بسته) برابر با 1 باشد. سپس میزان checksum بسته را با استفاده از تابع checksum محاسبه میکند: tpdp_pkt_calc_checksum() (0x4037f0).
برای فهم بهتر، در شکل 3 تابع calc_checksum() شرح داده شده است که بخشی از کد بهرهبرداری lao_bomb میباشد. این تابع به جای tpdp_pkt_calc_checksum() نشان داده شده تا فهم آن آسانتر گردد.
محاسبه checksum کاملاً ساده است. در ابتدا باید مقدار متغیر 0x5a6b7c8d در فیلد بسته checksum تنظیم شود، سپس از جدول reference_tbl (جدولی با 1024 بایت) برای محاسبه checksum در کل بسته شامل header استفاده میکند. پس از تایید صحت checksum، تابع tdpd_pkt_sanity_checks() مقدار 0 را بازمیگرداند. سپس بخش بعدی tdpd_pkt_parser() را فراخوانی میکنیم. در شکل 4 این عملیات قابل مشاهده است.
در اینجا بررسی میشود مقدار دومین بایت بسته (فیلد نوع)، 0 (tdpd) یا 0xf0 (onemesh) است. در بخش دوم، همچنین بررسی میشود که متغیر جهانی onemesh_flag طبق پیشفرض برابر 1 تنظیم شده باشد. سپس onemesh_main() (0x40cd78) را فراخوانی میکنیم.
تابع onemesh_main() در اینجا به علت اختصار نمایش داده نمیشود، اما وظیفه آن فراخوانی تابع دیگری مبتنی بر فیلد opcode بسته است. به منظور یافتن تابع آسیبپذیر، مقدار فیلد opcode برابر 6 و فیلدهای flag برابر 1 قرار میگیرند. در این مثال، onemesh_slave_key_offer() (0x414d14) فراخوانی میشود.
شکل 5 نمایانگر تابع آسیبپذیر است، از آنجا که این تابع بسیار طولانی است، فقط بخشهای مرتبط نمایش داده شدهاند.
در بخش اول onemesh_slave_key_offer()، این تابع بسته را به tpapp_aes_decrypt() (0x40b190) تحویل میدهد. تابع tpapp_aes_decrypt() نیز به علت اختصار نمایش داده نمیشود، اما فهم آن ساده است و وظیفه آن رمزگشایی بسته با استفاده از الگوریتم AES و کد ایستای PONEMESH_Kf!xn?gj6pMAt-wBNV_TDP میباشد.
فرض میشود که tpapp_aes_decrypt قادر به رمزگشایی کامل بسته باشد، حال در شکل 6 به بررسی قطعه مرتبط بعدی onemesh_slave_key_offer() میپردازیم.
در این قطعه توابع دیگری (توابع تنظیم شیء onemesh) برای تجزیه و تحلیل بار بسته فراخوانی میشوند. بار مورد انتظار، یک شیء JSON است، که در شکل 7 نمایش داده شده است.
قطعه بعدی نشان میدهد که هر کلید از شیء داده به ترتیب پردازش میشود. اگر یکی از کلیدها وجود نداشت، تابع خارج میشود. در شکل 8 این موضوع نمایش داده شده است.
همانطور که در شکل بالا مشاهده میشود، مقدار هر کلید JSON تجزیه شده و درون یک پشته متغیر کپی میشود (slaveMac، slaveIp و غیره). سپس تابع با فراخوانی create_csjon_obj() (0x405fe8) به بسته پاسخ میدهد.
از اینجا به بعد تابع، عملگرهای متغیری را روی دادههای دریافتی اعمال میکند، بخشهای مهم آن در شکل 9 نمایش داده شدهاند.
در شکل 8، مقدار کلید JSON slave_mac، درون پشته متغیر slaveMac کپی شد. در شکل 9 slaveMac، توسط sprintf در متغیر systemCmd کپی شده و سپس به system() منتقل میشود.
3 بهرهبرداری
3-1 دستیابی به تابع آسیبپذیر
اولین چیزی که باید مشخص شود چگونگی دستیابی به تزریق فرمان است. پس از آزمایش و خطا، محققان دریافتند ارسال ساختار JSON نمایش داده شده در شکل 7 همیشه در مسیر کد آسیبپذیر قرار میگیرد. به طور خاص، این روش باید slave_key_offer باشد و استفاده از تابع want_to_join نادرست است. سایر مقادیر میتوانند به طور دلخواه انتخاب شوند، اگرچه بعضی از کاراکترهای خاص در فیلدهایی غیر از slave_mac ممکن است باعث شوند که تابع آسیبپذیر زودتر از موعد خارج شود و تزریق را پردازش نکند.
با توجه به header بسته، همانطور که توضیح داده شد، مقدار نوع برابر 0xf0، opcode برابر 6 و flagها برابر 1 تنظیم میشوند تا فیلد checksum صحیح تنظیم شود.
3-2 رمزنگاری بسته
همانطور که در بخش گذشته توضیح داده شد، بسته با الگوریتم AES و کلید ثابت TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP رمزنگاری میشود. رمز در حالت CBC و IV دارای مقدار ثابت 1234567890abcdef1234567890abcdef است. علاوه بر این با وجود داشتن کلید 256 بیتی و IV، الگوریتم واقعی مورد استفاده AES-CBC با کلید 128 بیتی است، بنابراین نیمی از کلید و IV استفاده نمیشوند.
3-3 دستیابی به اجرای کد
اکنون میدانیم چگونه مسیر کد آسیب پذیر را بیابیم. برای ارسال بسته فرمان و اجرای کد، دو مشکل وجود دارد:
- تابع strncpy() تنها 0x11 بایت را از کلید slave_mac_info به متغیر slaveMac کپی میکند و بایت تهی را از بین میبرد.
- از آنجایی که مقدار slaveMac به صورت تکی و دوتایی ذخیره میشود، باید دادهها را از آن خارج کرد.
با توجه به این دو محدودیت، فضای واقعی موجود کاملاً محدود است.
به منظور خارج کردن آرگومانها و اجرای بار، باید کاراکترهای زیر را وارد کرد:
‘;<PAYLOAD>’
باید سه کاراکتر را از بین ببریم و تنها با 13 بایت بار خود را بسازیم. با 13 بایت (کاراکتر) اجرای هر کد معنیدار غیرممکن است.
به علاوه با انجام آزمایشاتی متوجه شدیم که حد در واقع 12 بایت است. دلیل این مسئله مشخص نیست، اما به نظر میرسد مربوط به خروج داده است.
سیستم فایلهای root خواندنی و نوشتنی است که یک اشتباه امنیتی بزرگ توسط TP-Link است. اگر این سیستم مانند اکثر دستگاههای تعبیه شده که از سیستم فایلهای SquashFS استفاده میکنند، فقط خواندنی بود، این حمله خاص غیرممکن میشد؛ زیرا افزودن cd tmp، بیش از 12 کاراکتر موجود را مصرف خواهد کرد. حال باید دستورات را بایت به بایت ارسال کرده و آنها را به یک پرونده فرمان «z» اضافه کنیم. سپس بار را ارسال کنیم:
sh z
حال پرونده فرمان به عنوان root اجرا میشود. از اینجا به بعد میتوان دودویی را بارگیری و اجرا کرده و کنترل کامل روتر را در اختیار داشته باشیم.
4 مراجع
[1]