
Cisco RV110w UPnP تجاوز سعة المكدس
في الآونة الأخيرة، أصبح UPnP شائعًا، ولدي جهاز Cisco RV110W. في أغسطس 2021، أعلنت شركة Cisco رسميًا عن ثغرة يوم الصفر (0day) في سلسلة RV الخاصة بـ UPnP، لكن التفاصيل المحددة لم تُنشر. لذا أردت استخدام الجهاز لتصحيح واكتشاف هذه الثغرة. يمكن الاطلاع على الإعلان الرسمي للثغرة على الموقع الرسمي.
أولاً، قم بتحديث البرنامج الثابت إلى أحدث إصدار 1.2.2.8. بعد ذلك، التحدي هو كيفية تصحيح الثغرة وتحديد موقعها. أولاً، حل مشكلة التصحيح، المهمة الأولى هي الحصول على شل الجهاز، لكن أحدث إصدار من البرنامج الثابت لا يوفر واجهة تصحيح. لقد حصلت على صلاحيات تصحيح أحدث إصدار من البرنامج الثابت باستخدام منفذ UART وتعديل حزمة البرنامج الثابت. الطريقة المحددة مذكورة في مقال سابق تصحيح أجهزة التوجيه - الحصول على شل.
Cisco RV110W يعتمد على بنية mipsel، لذا نحتاج أولاً إلى إيجاد خادم gdb مناسب. يمكننا تجميعه بأنفسنا أو استخدام نسخة مجمعة من قبل الآخرين. هنا أوصي بـ gdb-static-cross.
يشير الإعلان الرسمي إلى أن الثغرة موجودة في خدمة UPnP. أولاً، ادخل إلى لوحة الإدارة الخلفية، وفي الإعدادات الأساسية لجدار الحماية (Basic Settings) قم بتفعيل إعدادات UPnP.

ثم قم بمسح المنافذ باستخدام nmap، ولم يتم العثور على منفذ UPnP، لكن الاختبار أظهر أن UPnP مفتوح بالفعل.

هنا استخدمت UPnPy لاختبار الثغرة واستغلالها.
import socket
msg = \
b'M-SEARCH * HTTP/1.1\r\n' \
b'HOST:239.255.255.250:1900\r\n' \
b'ST:upnp:rootdevice\r\n' \
b'MX:2\r\n' \
b'MAN:"ssdp:discover"\r\n' \
b'\r\n'
# Set up UDP socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.bind((b"192.168.2.100",23333)) #本机IP
s.settimeout(2)
s.sendto(msg, (b'239.255.255.250', 1900))
addr = ('192.168.2.1', 1900) # 网关IP
try:
while True:
data, addr = s.recvfrom(65507)
print(addr,data)
except socket.timeout:
pass
لقد وجدت أنه تم بالفعل الحصول على استجابة UPnP، وأن هناك عملية UPnP مقابلة في عمليات الجهاز.

UPnP هو معيار بروتوكول عام، ومعظم الشركات المصنعة تطبقه وفقًا للمعيار، أي أن العديد من الإجراءات (actions) متطابقة، ولكن من الضروري أيضًا تحليل الخدمات التي يوفرها الجهاز لتسهيل تحديد الثغرة واستغلالها. تم استخدام UPnPy أيضًا لجمع المعلومات.
import upnpy
import socket
import requests
from upnpy.ssdp.SSDPDevice import SSDPDevice
msg = \
b'M-SEARCH * HTTP/1.1\r\n' \
b'HOST:239.255.255.250:1900\r\n' \
b'ST:upnp:rootdevice\r\n' \
b'MX:2\r\n' \
b'MAN:"ssdp:discover"\r\n' \
b'\r\n'
# Set up UDP socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.bind((b"192.168.2.100",23333))
s.settimeout(2)
s.sendto(msg, (b'239.255.255.250', 1900))
addr = ('192.168.2.1', 1900)
data = b""
try:
while True:
data, addr = s.recvfrom(65507)
print(addr,data)
except socket.timeout:
pass
# data = b'HTTP/1.1 200 OK\r\nCache-Control: max-age=120\r\nDate: Fri, 01 Jan 2010 00:44:16 GMT\r\nExt: \r\nLocation: http://192.168.2.1:1780/InternetGatewayDevice.xml\r\nServer: POSIX UPnP/1.0 linux/5.70.48.16\r\nST: upnp:rootdevice\r\nUSN: uuid:31474a87-67ea-dae4-2f73-f157fb06d22b::upnp:rootdevice\r\n\r\n'
# data = b'HTTP/1.1 200 OK\r\nCache-Control: max-age=3600\r\nST: upnp:rootdevice\r\nUSN: uuid:824ff22b-8c7d-41c5-a131-8c3bad401726::upnp:rootdevice\r\nEXT:\r\nServer: Unspecified, UPnP/1.0, Unspecified\r\nLocation: http://192.168.3.1:56688/rootDesc.xml\r\n\r\n'
device = SSDPDevice(addr, data.decode())
services = device.get_services()
services_id = [services[i].id.split(":")[-1] for i in range(len(services))]
for id in services_id:
service = device[id]
actions = service.get_actions()
for action in actions:
for argument in action.arguments:
print(id,action.name,argument.name)
يمكن الحصول على سلسلة من معلومات الخدمة، جزء منها كما يلي:
WANIPConn1 AddPortMapping NewRemoteHost
WANIPConn1 AddPortMapping NewExternalPort
WANIPConn1 AddPortMapping NewProtocol
WANIPConn1 AddPortMapping NewInternalPort
WANIPConn1 AddPortMapping NewInternalClient
WANIPConn1 AddPortMapping NewEnabled
WANIPConn1 AddPortMapping NewPortMappingDescription
WANIPConn1 AddPortMapping NewLeaseDuration
WANIPConn1 DeletePortMapping NewRemoteHost
WANIPConn1 DeletePortMapping NewExternalPort
WANIPConn1 DeletePortMapping NewProtocol
WANIPConn1 GetExternalIPAddress NewExternalIPAddress
يمكن تصنيف هذه الخدمات تقريبًا إلى خدمات من نوع get وخدمات من نوع set، بالإضافة إلى عدد قليل من خدمات الحذف (delete). نظرًا لأن أحد الأهداف الرئيسية لـ UPnP هو تعريض الأجهزة الداخلية للأجهزة الخارجية، يتطلب ذلك بعض التهيئة (مثل إعادة توجيه المنافذ). وبما أن التهيئة مطلوبة، فإن معلمات التهيئة والمعلومات ضرورية، وهذه المعلمات هي في الواقع معلمات خدمات set.
نظرًا لأن الشركة المصنعة لم تنفذ جميع الخدمات، نحتاج إلى إجراء هندسة عكسية للبرنامج الثابت بأنفسنا. أولاً، حدد موقع upnp_mainloop.

جميع منطق المعالجة يتم تنفيذه في upnp_dispatch. بعد المتابعة والتحليل، تم العثور على أجزاء معالجة طلبات SSDP و HTTP.

يتوافق ssdp_process مع طلبات البحث M-Search أثناء العنونة، ويتوافق upnp_http_process مع معالجة طلبات HTTP. نظرًا لأن استدعاءات الخدمة العادية تكون عبر طلبات HTTP، فمن المحتمل أن upnp_http_process يحتوي على ثغرة. مثال على استدعاء الخدمة موضح في الشكل أدناه.

في upnp_http_process، يتم استدعاء upnp_http_fsm_emgine.

بعد المتابعة والتحليل، تم العثور على تنفيذ عدة دوال في off_45ab80.

تتضمن هذه الدوال وظائف التهيئة وتحليل رأس البروتوكول، وآخر دالة هي upnp_http_fsm_dispatch، ويُعتقد أنها تنفذ دالة الخدمة المقابلة.

بعد متابعة upnp_http_fsm_dispatch، تم العثور على استدعاء لدوال، لكن الاستدعاء يعتمد على a1 و a2، ولا يمكن تحديد الدالة المحددة التي يتم استدعاؤها، لذا يلزم التصحيح الديناميكي.

إذا كان طلبًا عاديًا لعنونة SSDP، فسيتم استدعاء SUB_405B34 و description_process. وإذا كان طلبًا متعلقًا بالخدمة، فسيتم استدعاء soap_process. داخل soap_process، بناءً على معلومات رأس الطلب، يتم استدعاء query_process أو action_process.

نركز بشكل أساسي على action_process. بناءً على رأس طلب استدعاء الخدمة، يُعتقد أن soap_control داخل action_process يتوافق مع استدعاء الخدمة.

داخل soap_control، لا يزال التصحيح الديناميكي مطلوبًا لتحديد معلومات الدالة المحددة.
أخيرًا، من خلال التصحيح الديناميكي، تم تحديد أن sub_414C28 يتوافق مع إجراء AddPortMapping، وسلسلة استدعاء الدوال هي:
sub_414c28->upnp_portmap_add->upnp_osl_nat_config->strcpy(stack overflow)
ومع ذلك، في upnp_portmap_add، تم التحقق من عنوان واجهة WAN الخاصة بالجهاز. نظرًا لعدم تكوين واجهة WAN أثناء الاختبار، تم تعيين عنوان IP لواجهة WAN مباشرة باستخدام nvram.

عند حدوث تجاوز سعة المكدس، يجب التحكم في تدفق البرنامج للوصول إلى المربع الأحمر، لأن دالة المربع الأزرق ستصل إلى المكدس المتجاوز مما يؤدي إلى تعطل البرنامج قبل تنفيذ RCE. لذلك، يجب التحكم في قيمة *(a2+11) لتكون 0، ولحسن الحظ، هذه القيمة قابلة للتحكم.

نظرًا لأن أحدث إصدار من البرنامج الثابت لا يحتوي على telnetd، يمكن إرسال شل عائدة (reverse shell) ثم تحميل utelnetd يدويًا، ثم تشغيل telnet.
