
Cisco RV110w desbordamiento de pila en UPnP
Recientemente UPnP está bastante de moda. Resulta que tengo un Cisco RV110W a mano. En agosto de 2021, Cisco publicó oficialmente un 0day relacionado con UPnP en la serie Cisco RV, pero no se revelaron los detalles concretos. Así que quería usar el dispositivo que tenía a mano para depurar e investigar esta vulnerabilidad. El aviso de la vulnerabilidad se puede ver en el sitio oficial.
Primero, actualicé el firmware a la última versión 1.2.2.8. El siguiente problema era cómo depurar y localizar la vulnerabilidad. El primer paso para la depuración es obtener una shell en el dispositivo. Sin embargo, el firmware más reciente no ofrece una interfaz de depuración. El autor logró permisos de depuración en el firmware más reciente mediante el puerto serie UART y la modificación del paquete de firmware. Para conocer el método específico, consulta un artículo que escribí anteriormente: Depuración de routers: getshell.
El Cisco RV110W es de arquitectura mipsel, así que primero hay que encontrar un gdb-server compatible. Puedes compilarlo de forma cruzada tú mismo o usar uno ya compilado por otra persona. Aquí recomiendo gdb-static-cross.
El aviso oficial indica que la vulnerabilidad está en el servicio UPnP. Primero entré en la administración del router y activé la configuración de UPnP en Basic Settings del FireWall.

Luego escaneé los puertos con nmap y no encontré el puerto de UPnP, pero las pruebas mostraron que UPnP estaba efectivamente activado.

Aquí el autor utiliza UPnPy para probar y explotar la vulnerabilidad.
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
Se confirmó que efectivamente se recibió una respuesta UPnP, y que existe un proceso UPnP correspondiente entre los procesos del dispositivo.

UPnP es un estándar de protocolo universal. La mayoría de los fabricantes lo implementan siguiendo el estándar, por lo que muchas acciones son idénticas. Aun así, es necesario analizar los servicios que ofrece el dispositivo para localizar y explotar la vulnerabilidad. Para ello también se usó UPnPy para recopilar información.
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)
Se puede obtener una serie de información de servicios, parte de la cual se muestra a continuación:
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
Estos servicios se pueden dividir en términos generales en categorías get y set, así como algunos servicios de tipo delete. Dado que uno de los objetivos principales de UPnP es exponer dispositivos de la red interna a dispositivos de la red externa, esto requiere realizar cierta configuración (como el mapeo de puertos). Dado que se necesita configuración, los parámetros y la información de configuración son imprescindibles, y estos parámetros son precisamente los de los servicios de tipo set.
Dado que el fabricante no implementa todos los servicios, es necesario hacer ingeniería inversa al firmware por nuestra cuenta. Primero, se localiza upnp_mainloop.

Toda la lógica de procesamiento se implementa en upnp_dispatch. Al seguir este flujo se encuentran partes de procesamiento de solicitudes ssdp y http.

ssdp_process corresponde a las solicitudes M-Search durante el descubrimiento, y upnp_http_process al procesamiento de solicitudes http. Como las invocaciones normales de servicios son solicitudes http, se deduce que upnp_http_process puede contener la vulnerabilidad. En la siguiente imagen se muestra un ejemplo de invocación de servicio.

Dentro de upnp_http_process se llama posteriormente a upnp_http_fsm_emgine.

Al continuar con el análisis, se observa que se ejecutan varias funciones en off_45ab80.

Estas funciones incluyen la inicialización y el análisis de las cabeceras del protocolo. La última función es upnp_http_fsm_dispatch; se especula que ejecuta la función de servicio correspondiente.