
Estouro de pilha UPnP no Cisco RV110w
Recentemente, o UPnP está em destaque. Por coincidência, tenho um Cisco RV110W em mãos. Em agosto de 2021, a Cisco divulgou oficialmente um 0day relacionado ao UPnP na série RV, mas os detalhes específicos não foram revelados. Então, decidi usar o dispositivo para depurar e explorar essa vulnerabilidade. O aviso da vulnerabilidade pode ser visto no site oficial.
Primeiro, atualize o firmware para a versão mais recente 1.2.2.8. O próximo desafio é como depurar e localizar a vulnerabilidade. Primeiro, resolva o problema de depuração. O trabalho principal da depuração é obter o shell do dispositivo. No entanto, o firmware mais recente não fornece uma interface de depuração. Aqui, obtive permissões de depuração no firmware mais recente através da porta serial UART e modificação do pacote de firmware. O método específico pode ser consultado em um artigo que escrevi anteriormente: Depuração de roteador - getshell.
O Cisco RV110W possui arquitetura mipsel, então precisamos encontrar um gdb-server correspondente. Podemos compilar cruzadamente ou usar um já compilado por terceiros. Recomendo gdb-static-cros.
O aviso oficial indica que a vulnerabilidade existe no serviço UPnP. Primeiro, entre na interface de administração, nas Configurações Básicas do Firewall, ative a configuração UPnP.

Em seguida, escaneie as portas com nmap. As portas UPnP não foram encontradas, mas os testes mostraram que o UPnP está realmente ativado.

Aqui, usei UPnPy para testar e explorar a vulnerabilidade.
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 local
s.settimeout(2)
s.sendto(msg, (b'239.255.255.250', 1900))
addr = ('192.168.2.1', 1900) # IP do gateway
try:
while True:
data, addr = s.recvfrom(65507)
print(addr,data)
except socket.timeout:
pass
Foi confirmado que há resposta UPnP e que existe um processo UPnP correspondente em execução no dispositivo.

UPnP é um padrão de protocolo genérico. A maioria dos fabricantes implementa de acordo com o padrão, ou seja, muitas ações são idênticas. No entanto, é necessário analisar os serviços fornecidos pelo dispositivo para localizar e explorar a vulnerabilidade. Usei novamente o UPnPy para coletar informações.
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)
Obtém-se uma série de informações de serviços. Parte das informações é mostrada abaixo.
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
Esses serviços podem ser divididos em categorias: get, set e alguns poucos delete. Como um dos principais objetivos do UPnP é expor dispositivos da rede interna para a rede externa, é necessário realizar algumas configurações (como mapeamento de portas). Essas configurações exigem parâmetros e informações, que são justamente os parâmetros dos serviços do tipo set.
Como nem todos os serviços são implementados pelo fabricante, é necessário fazer engenharia reversa do firmware. Primeiro, localize upnp_mainloop.

Toda a lógica de processamento é implementada em upnp_dispatch. Analisando, encontramos partes de tratamento de requisições SSDP e HTTP.

ssdp_process lida com requisições M-Search na descoberta. upnp_http_process lida com requisições HTTP. Como as chamadas normais de serviço são requisições HTTP, suspeita-se que upnp_http_process possa conter a vulnerabilidade. Um exemplo de chamada de serviço é mostrado abaixo.

Dentro de upnp_http_process, é chamado upnp_http_fsm_emgine.

Analisando, descobre-se que são executadas várias funções em off_45ab80.

Essas funções incluem inicialização e análise do cabeçalho do protocolo. A última função é upnp_http_fsm_dispatch. Supõe-se que ela execute a função de serviço correspondente.

Analisando upnp_http_fsm_dispatch, confirma-se que ela realmente chama funções, mas com base nos parâmetros a1 e a2, não sendo possível determinar a função chamada exata sem depuração dinâmica.

Se for uma requisição de descoberta SSDP normal, chama SUB_405B34 ou description_process. Se for uma requisição relacionada a serviço, chama soap_process. Dentro de soap_process, com base nas informações do cabeçalho da requisição, chama query_process ou action_process.
Foco principal em action_process. Com base no cabeçalho da requisição de chamada de serviço, suspeita-se que soap_control dentro de action_process corresponda à chamada de serviço.

Em soap_control, ainda é necessária depuração dinâmica para determinar a função específica.

Finalmente, após depuração dinâmica, determina-se que sub_414C28 corresponde à ação AddPortMapping. Sua cadeia de chamadas de função é:
sub_414c28->upnp_portmap_add->upnp_osl_nat_config->strcpy(stack overflow)
No entanto, em upnp_portmap_add, verifica-se o endereço da interface WAN do dispositivo. Como durante o teste não configurei a interface WAN, usei nvram para definir o IP da WAN.

Ao ocorrer o estouro de pilha, é necessário controlar o fluxo do programa para seguir o bloco vermelho. Isso porque a função no bloco azul acessa a pilha estourada, causando a quebra do programa antes de atingir o RCE. Portanto, é necessário que o valor de *(a2+11) seja 0. Felizmente, esse valor é controlável.

Como o firmware mais recente não possui telnetd, podemos enviar um shell reverso e, em seguida, executar um utelnetd para habilitar o telnet.

Consulte Métodos para shell reverso