
Dépassement de pile UPnP Cisco RV110w
UPnP est très populaire ces derniers temps. J'avais justement un Cisco RV110W sous la main. En août 2021, Cisco a officiellement divulgué un 0day concernant UPnP dans la série RV, mais les détails précis n'ont pas été publiés. J'ai donc voulu utiliser cet appareil pour déboguer et analyser cette vulnérabilité. L'avis de vulnérabilité est disponible sur le site officiel.
Tout d'abord, j'ai mis à jour le firmware vers la dernière version 1.2.2.8. Ensuite, le problème à résoudre était de savoir comment déboguer et localiser la vulnérabilité. Commençons par régler le problème du débogage : la première étape consiste à obtenir un shell sur l'appareil. Cependant, le dernier firmware ne fournit pas d'interface de débogage. J'ai donc obtenu les droits de débogage du dernier firmware via le port série UART et en modifiant le paquet de firmware. La méthode détaillée est décrite dans un article que j'ai écrit précédemment : Débogage de routeurs : getshell.
Le Cisco RV110W est basé sur une architecture mipsel, il faut donc d'abord trouver un gdb-server correspondant. On peut le compiler soi-même en croisé ou utiliser une version compilée par quelqu'un d'autre. Ici, je recommande gdb-static-cross.
L'avis officiel indique que la vulnérabilité se trouve dans le service UPnP. Tout d'abord, allez dans l'interface d'administration, puis dans FireWall > Basic Settings pour activer la configuration UPnP.

Ensuite, j'ai scanné les ports avec nmap : aucun port UPnP n'est apparu, mais les tests ont montré qu'UPnP était bien activé.

J'ai utilisé UPnPy pour tester et exploiter la vulnérabilité.
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
J'ai bien reçu une réponse UPnP et le processus UPnP correspondant est présent dans les processus de l'appareil.

UPnP est un standard de protocole générique. La plupart des fabricants l'implémentent selon ce standard, de sorte que de nombreuses actions sont identiques. Il est néanmoins nécessaire d'analyser les services fournis par l'appareil pour localiser et exploiter la vulnérabilité. J'ai donc utilisé UPnPy pour collecter les informations.
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)
On obtient ainsi une série d'informations sur les services, dont certaines ci-dessous :
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
Ces services peuvent être globalement classés en services de type get, de type set, et quelques services de type delete. L'un des objectifs principaux d'UPnP étant d'exposer des appareils du réseau interne au réseau public, une certaine configuration est nécessaire (mappage de ports, etc.). Puisqu'il faut configurer, les paramètres et les informations de configuration sont indispensables, et ces paramètres sont précisément ceux des services de type set.
Comme le fabricant n'a pas implémenté tous les services, il faut rétro-concevoir le firmware soi-même. Tout d'abord, localisons upnp_mainloop.

Toute la logique de traitement est implémentée dans upnp_dispatch. En poursuivant l'analyse, on découvre des parties de traitement des requêtes SSDP et HTTP.

ssdp_process correspond aux requêtes M-Search lors de la découverte, et upnp_http_process au traitement des requêtes HTTP. Comme les appels de service normaux sont tous des requêtes HTTP, on peut supposer que upnp_http_process est susceptible de contenir une vulnérabilité. Un exemple d'appel de service est illustré ci-dessous.

Dans upnp_http_process, on trouve un appel à upnp_http_fsm_emgine.

En poursuivant l'analyse, on constate que plusieurs fonctions à l'adresse off_45ab80 sont exécutées.

Ces fonctions assurent l'initialisation et l'analyse de l'en-tête du protocole. La dernière fonction est upnp_http_fsm_dispatch ; je suppose qu'elle exécute la fonction de service correspondante.

En suivant upnp_http_fsm_dispatch, on constate qu'il y a bien des appels de fonctions, mais ils sont effectués en fonction de a1 et a2. Il est impossible de déterminer quelle fonction spécifique est appelée, un débogage dynamique est donc nécessaire.

Si c'est une requête de découverte SSDP normale, SUB_405B34 et description_process seront appelés. Si c'est une requête liée à un service, soap_process sera appelé. Dans soap_process, selon les informations de l'en-tête de la requête, query_process ou action_process est invoqué.
On s'intéresse principalement à action_process. D'après les en-têtes des requêtes d'appel de service, on peut supposer que soap_control dans action_process correspond à l'appel de service.

Dans soap_control, un débogage dynamique est encore nécessaire pour déterminer les informations précises sur la fonction.

Finalement, grâce au débogage dynamique, j'ai déterminé que sub_414C28 correspond à l'action AddPortMapping. Sa chaîne d'appels de fonctions est la suivante :
sub_414c28->upnp_portmap_add->upnp_osl_nat_config->strcpy(stack overflow)
Cependant, upnp_portmap_add vérifie l'adresse de l'interface wan locale. Comme je n'avais pas configuré l'interface wan lors de mes tests, j'ai directement utilisé nvram pour définir l'adresse IP de l'interface wan.

Au moment du débordement de pile, il faut contrôler le flux du programme pour atteindre le bloc rouge, car la fonction du bloc bleu accède à la pile débordée, ce qui fait planter le programme avant l'exécution du RCE. Il faut donc contrôler la valeur de *(a2+11) pour qu'elle soit égale à 0. Heureusement, cette valeur est contrôlable.

Comme le dernier firmware ne contient pas telnetd, on peut envoyer un shell inversé puis déposer utelnetd et activer telnet.
