
استغلال تنفيذ أوامر بصلاحيات الجذر عبر بث UDP في خدمة infosvr لراوترات ASUS
تتضمن عدة طرازات من موجهات ASUS خدمة تُسمى infosvr تستمع على منفذ بث UDP رقم 9999 على واجهة LAN أو WLAN. تُستخدم من قِبل إحدى أدوات ASUS لتسهيل إعداد الموجه عبر تحديد مواقع الموجهات تلقائيًا على الشبكة الفرعية المحلية. تعمل هذه الخدمة بصلاحيات root وتحتوي على ثغرة تنفيذ أوامر بدون مصادقة. الكود المصدري لهذه الخدمة، بالإضافة إلى بقية أجزاء الموجه، متاح من موقع دعم ASUS.
معرّف CVE المخصص لهذه المشكلة هو CVE-2014-9583 (للأسف، ليس CVE-2014-10000 في النهاية :-/).
حاليًا، يُفترض أن جميع إصدارات البرامج الثابتة المعروفة للموجهات المعنية (RT-AC66U، RT-N66U، إلخ) معرّضة للثغرة. تم إجراء الاختبار على الإصدار 3.0.0.376.2524-g0013f52.
الموجهات/إصدارات البرامج الثابتة التالية مؤكدة التأثر بالثغرة:
| الموجه | إصدار البرنامج الثابت | تم التحقق بواسطة |
|---|---|---|
| RT-N66U | 3.0.0.4.376_1071-g8696125 | Friedrich Postelstorfer |
| RT-AC87U | 3.0.0.4.378_3754 | David Longenecker |
| RT-N56U | 3.0.0.4.374_5656 | @argilo |
| RT-AC68U | 3.0.0.4.376_3626-g9a8323e | @Arie |
| DSL-N55U | 3.0.0.4.374_4422-gc83c78f | @S2- |
| DSL-AC68U | 3.0.0.4.376_2158-g340202b | @magJ, @gitFurious |
| RT-AC66R | 3.0.0.4.376_2524-g0013f52 | @asgh |
| RT-AC66R | 3.0.0.4.376_3602 | @facerolling |
| RT-AC55U | 3.0.0.4.376_6587-gaa506e9 | @pellaeon |
| RT-N12HP_B1 | 3.0.0.4.374_1327 | @vittee |
| RT-N16 | 3.0.0.4.220 | @xorrbit |
بالإضافة إلى ذلك، فإن الموجهات التي تعمل ببرامج ثابتة مبنية على مشروع asuswrt-merlin المدعوم من المجتمع تكون معرّضة للثغرة في الإصدارات الأقدم من 376.49_5.
لا ينبغي أن تتأثر الموجهات التي تستخدم إصدارات برامج ثابتة صدرت في 12 يناير 2015 أو بعده. يتتبع الجدول التالي البلاغات عن الموجهات/البرامج الثابتة غير المتأثرة.
| الموجه | إصدار البرنامج الثابت | تم التحقق بواسطة |
|---|---|---|
| RT-AC66R | 3.0.0.4.376_3754-g5ef7c1f | @asgh |
| RT-N12HP_B1 | 3.0.0.4.376_3754-g5ef7c1f | @vittee |
تأمل المقتطف التالي من مشروع ASUSWRT-Merlin، وهو فرع محسّن من كود ASUS. يمكنك عرض الملف بالكامل (موصى به لمتعة إضافية) هنا.
177 char *processPacket(int sockfd, char *pdubuf)
178 {
...
202 phdr = (IBOX_COMM_PKT_HDR *)pdubuf;
...
207 if (phdr->ServiceID==NET_SERVICE_ID_IBOX_INFO &&
208 phdr->PacketType==NET_PACKET_TYPE_CMD)
209 {
...
تُستدعى الدالة processPacket بعد استلام حزمة بحجم INFO_PDU_LENGTH (512) بايت. مسار الكود المعرّض للثغرة تحديدًا هو main->processReq->processPacket. تقوم الخدمة بعد ذلك بتحويل الحزمة إلى بنية وتتحقق من أن حقلي ServiceID وPacketType يطابقان القيم المتوقعة.
تحتوي الكتلة التالية على ما يُعتقد أنه السبب الجذري لهذه الثغرة.
222 if (phdr->OpCode!=NET_CMD_ID_GETINFO && phdr->OpCode!=NET_CMD_ID_GETINFO_MANU)
223 {
224 phdr_ex = (IBOX_COMM_PKT_HDR_EX *)pdubuf;
225
226 // Check Mac Address
227 if (memcpy(phdr_ex->MacAddress, mac, 6)==0)
228 {
229 _dprintf("Mac Error %2x%2x%2x%2x%2x%2x\n",
230 (unsigned char)phdr_ex->MacAddress[0],
231 (unsigned char)phdr_ex->MacAddress[1],
232 (unsigned char)phdr_ex->MacAddress[2],
233 (unsigned char)phdr_ex->MacAddress[3],
234 (unsigned char)phdr_ex->MacAddress[4],
235 (unsigned char)phdr_ex->MacAddress[5]
236 );
237 return NULL;
238 }
239
240 // Check Password
241 //if (strcmp(phdr_ex->Password, "admin")!=0)
242 //{
243 // phdr_res->OpCode = phdr->OpCode | NET_RES_ERR_PASSWORD;
244 // _dprintf("Password Error %s\n", phdr_ex->Password);
245 // return NULL;
246 //}
247 phdr_res->Info = phdr_ex->Info;
248 memcpy(phdr_res->MacAddress, phdr_ex->MacAddress, 6);
249 }
تبدأ الكتلة باستثناء قيمتين من OpCode، واللتين يُفترض أنهما لا تتطلبان مصادقة بحكم التصميم. بعد ذلك، تستدعي الدالة memcpy وتتحقق بشكل مريب من قيمة الإرجاع مقابل الصفر. هذا يشير بشدة إلى أن المؤلف كان يقصد استخدام memcmp بدلًا من ذلك. ومع ذلك، حتى لو تم تنفيذ هذا الفحص بشكل صحيح، فإن معرفة عنوان MAC الخاص بالجهاز لا تكاد تكون مصادقة كافية.
الكتلة التالية تم تعليقها (commented out)، لكنها تُظهر أن المؤلف جرّب في مرحلة ما التحقق من كلمة مرور. وإن كان في هذه الحالة، كلمة المرور كانت مثبتة بشكل ثابت (hardcoded) على أنها "admin".
بالانتقال إلى الأمام، تقوم جملة switch التالية بتوزيع المعالجة بناءً على قيمة OpCode المقدمة.
251 switch(phdr->OpCode)
252 {
...
428 case NET_CMD_ID_MANU_CMD:
429 {
430 #define MAXSYSCMD 256
431 char cmdstr[MAXSYSCMD];
432 PKT_SYSCMD *syscmd;
...
440 syscmd = (PKT_SYSCMD *)(pdubuf+sizeof(IBOX_COMM_PKT_HDR_EX));
...
443 if (syscmd->len>=MAXSYSCMD) syscmd->len=MAXSYSCMD;
444 syscmd->cmd[syscmd->len]=0;
445 syscmd->len=strlen(syscmd->cmd);
446 fprintf(stderr,"system cmd: %d %s\n", syscmd->len, syscmd->cmd);
447 #if 0
...
512 #endif
513 {
514 sprintf(cmdstr, "%s > /tmp/syscmd.out", syscmd->cmd);
515 system(cmdstr);
إذا حدد المهاجم قيمة OpCode كـ NET_CMD_ID_MANU_CMD، تقوم الكتلة السابقة بمعالجة الحزمة عبر تحويلها إلى بنية PKT_SYSCMD. وبناءً عليه، فإن أي أعضاء في syscmd يتحكم بها المهاجم بالكامل. وقبل الاهتمام (غمزة) بإنهاء سلسلة الأوامر بحرف NUL، ينفذ المؤلف الأمر في السطر 514. بعد تنفيذ الأمر، يُقرأ المخرَج من الملف المؤقت ويُعاد إرساله إلى عنوان المصدر للحزمة التي بدأت الطلب.
المستخدمون النهائيون: حدّث البرنامج الثابت إلى الإصدار 3.0.0.4.376.3754 أو أحدث. من المهم ملاحظة أن وظيفة "التحقق من وجود تحديثات" في الموجه قد لا تعمل بشكل صحيح. تحقق يدويًا من إصدار البرنامج الثابت الذي تشغّله، وإذا كان أقدم، فقم بتنزيل/تثبيت البرنامج الثابت الجديد.
ASUS/Merlin: أزل وظيفة تنفيذ الأوامر عن بُعد من هذه الخدمة. حتى لو كانت محمية بمصادقة قوية، فإن بث كلمة مرور إلى الشبكة المحلية بأكملها ليس أمرًا مرغوبًا فيه حقًا. إذا كان تنفيذ الأوامر مطلوبًا فعلًا، فيجب توفيره عبر SSH أو آلية آمنة مماثلة.
يوصي David Longenecker باستخدام سكربت (JFFS) بالاشتراك مع إعداد nvram script_usbmount لقتل عملية infosvr عند الإقلاع. لمزيد من المعلومات، راجع منشوره في المدونة.
يوصي Eric Sauvageau (@RMerl) بحظر المنفذ 9999 عبر جدار الحماية. لمزيد من المعلومات، راجع منشوره على منتدى Small Net Builder.
بدلًا من ذلك، يمكنك تعطيل خدمة infosvr بقتل العملية بعد كل إقلاع. لمتعة/سخرية إضافية، استخدم الاستغلال للقيام بذلك:
$ ./asus-cmd "killall -9 infosvr"
[...]
ملاحظة: لن تحصل على استجابة لهذا الأمر. ومرة أخرى، ستحتاج إلى القيام بذلك في كل مرة يُعاد فيها تشغيل الجهاز.
بعد الإفصاح الأولي عن هذه المشكلة، عالجت عدة جهات المشكلة في قواعد أكوادها.