
Cisco RV110w UPnP stack overflow
В последнее время UPnP стал очень популярен, и у меня как раз есть Cisco RV110W. В августе 2021 года Cisco официально объявила об уязвимости 0day для серии RV, связанной с UPnP, но конкретные детали не были раскрыты. Поэтому я решил с помощью имеющегося устройства отладить и исследовать эту уязвимость. Уведомление об уязвимости можно увидеть на официальном сайте.
Сначала обновим прошивку до последней версии 1.2.2.8. Следующая проблема – как отлаживать и локализовать уязвимость. Сначала решаем вопрос отладки. Первоочередная задача отладки – получить shell устройства, но последняя прошивка не предоставляет отладочного интерфейса. Автор получил права отладки последней прошивки через UART-порт и модификацию пакета прошивки. Подробный метод описан в предыдущей статье Отладка роутера: getshell.
Cisco RV110W основана на архитектуре mipsel, поэтому сначала нужно найти подходящий gdb-server. Можно собрать кросс-компиляцией самостоятельно или использовать уже собранный кем-то. Здесь рекомендуется gdb-static-cross.
Официальное уведомление указывает, что уязвимость существует в службе UPnP. Сначала заходим в панель управления, в FireWall -> 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 соответствует action 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, можно получить обратный shell, затем загрузить utelnetd и включить telnet.
