
هذا تقرير موجز عن ثغرة أمنية (CVE-2019-12489) تم اكتشافها أثناء اللعب بمودم/راوتر Fastgate من Fastweb وباستخدام Ghidra. تسمح الثغرة بتنفيذ حقن أوامر عبر طلب HTTP ويمكن استخدامها لتمكين واجهة SSH.
جميع ملفات البرامج الثابتة موجودة في http://59.0.121.191:8080/ACS-server/file/FILENAME
حيث FILENAME يساوي:
لذا، على سبيل المثال، للحصول على آخر برنامج ثابت، اذهب إلى http://59.0.121.191:8080/ACS-server/file/0.00.89_FW_200_Askey
لاستخراج الملفات، نحتاج إلى الأداة الموجودة في https://github.com/nlitsme/ubidump (هذه أول أداة عملت معي).
عند تشغيل binwalk على الملف، نحصل على:
$ binwalk 0.00.89_FW_200_Askey
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
94 0x5E JFFS2 filesystem, little endian
3276894 0x32005E UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x800, data offset: 0x1000
لسنا بحاجة إلى نظام الملفات JFFS2، فقط جزء UBI. للقيام بذلك، يمكننا قص الملف باستخدام dd.
للقيام بذلك، نحتاج إلى الحجم الكلي ونقطة بداية UBI.
يمكن الحصول على حجم الملف باستخدام ls:
$ ls -la
total 55836
drwxr-xr-x 3 riccardo users 4096 set 24 10:07 .
drwx------ 83 riccardo users 12288 set 24 10:08 ..
-rw-r--r-- 1 riccardo users 57147506 set 24 10:05 0.00.89_FW_200_Askey
القيمة 57147506 هي ما نحتاجه.
بالنظر إلى مخرجات binwalk، نرى أن جزء UBI يبدأ عند 3276894. باستخدام هاتين القيمتين، يمكننا استخدام dd كالتالي:
$ dd if=0.00.89_FW_200_Askey of=0.00.89_FW_200_Askey.UBI bs=1 skip=3276894 count=57147506
الآن يمكننا استخدام ubidump (بعد بعض الإعدادات الأولية حسب إعدادات النظام):
$ git clone https://github.com/nlitsme/ubidump.git
$ sudo pip install crcmod
$ yay -S python-lzo
$ python ubidump/ubidump.py -s bump 0.00.89_FW_200_Askey.UBI
لدينا الآن نظام الملفات في bump/rootfs_ubifs/
يمكننا الآن المتابعة قليلاً في التحليل.
بالنظر في نظام الملفات، نرى أن واجهة الويب تُدار بواسطة mini_httpd:
$ grep -i -r "mini_httpd"
etc/sniproxy/urlfilter3.sh:ln -sf /usr/sbin/mini_httpd $URLFILTER_WEBD_PATH
etc/sniproxy/urlfilter3.sh:$URLFILTER_WEBD_PATH -d $URLFILTER_WEB_PATH -c "**.cgi" -u admin -S -E /web/mini_httpd.pem -p $webd_https_port
bin/web.sh:#mv /tmp/mini_httpd /usr/sbin
bin/web.sh:killall -9 mini_httpd
bin/web.sh:mini_httpd -d /web -c "STATUSAPI/**.cgi|STATUSAPI/*" -u admin -T "UTF-8" -p 8080
ونرى أن mini_httpd يعتمد على ملف .cgi:
$ grep -i -r "status.cgi"
web/factory/mtcmd/mt_info.htm: "url":"/status.cgi",
web/fw-router/app/app.js:.value('apiServiceUrl', '/status.cgi')
**web/js/status.js:var CGI_NAME='status.cgi';**
web/js/status.js: "url":"http://192.168.101.93/STATUSAPI/status.cgi",
web/js/status.js: "url":"http://192.168.101.93/STATUSAPI/status.cgi",
web/js/status.js: "url":http://192.168.101.93/STATUSAPI/status.cgi,
لذا، باستخدام Ghidra، يمكننا البدء في النظر في ملف status.cgi
بتجاوز الكثير من التفاصيل، الثغرة موصوفة أدناه.
أول شيء نبحث عنه هو دالة رئيسية مثل الموجودة في هذه الصورة:

نرى أنه يتم استدعاء setup. بعد البحث قليلاً، run_service(...) يبدو جوهر جميع الوظائف.

عند البحث عن &service_tab نرى أن بعض السلاسل المحددة مرتبطة بكود فعلي يتم تنفيذه بواسطة run_service(...)

بعد النظر في هذه الدوال واحدة تلو الأخرى، عثرت على usb_remove

بالتفصيل:
mountVal = (char *)find_val(httpparams,"mount");
يأخذ السلسلة من طلب URL مباشرة في متغير
sprintf((char *)&mountLocation,"/mnt/%s",mountVal);
ينشئ مسارًا كاملاً بناءً على المعامل في طلب URL
mountLocation
يستخدم mountLocation مباشرة في أمر
الآن، هناك فحوصات لمنع حقن الأوامر. على وجه الخصوص:
invalidChars = 0x60273b7c;
تُستخدم للتحقق من بعض الأحرف غير الصالحة مثل ` ' ; | ولكن & غير موجود في القائمة، لذا إذا كان المعامل mount يساوي &ping -c 10 192.168.1.172& فسيؤدي الفحص إلى السماح بتنفيذ /statusapi/bin/umount -lf &ping -c 10 192.168.1.172&.
سينفذ أمر mount وسيفشل، لكن ping التالي سيتم تنفيذه.
التصحيح المنشور يبسط الكود بإلغاء الفحوصات للأحرف الخاصة، ببساطة عن طريق التحقق من وجود المجلد.

الكود لاستغلال هذه الثغرة وتمكين SSH موجود في fastwebEnableSSH.py https://github.com/garis/Fastgate/blob/master/fastwebEnableSSH.py