Cisco RV110W UPnP 0day 分析
0x01 前言
最近 UPnP 比較火,恰好手里有一臺 Cisco RV110W,在 2021 年 8 月份思科官方公布了一個 Cisco RV 系列關于 UPnP 的 0day,但是具體的細節并沒有公布出來。于是想要用手中的設備調試挖掘一下這個漏洞,漏洞的公告可以在官網 https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-cisco-sb-rv-overflow-htpymMB5 看到。
0x02 準備工作
首先將固件更新到最新版本 1.2.2.8,傳送門:https://software.cisco.com/download/home/283879340/type/282487380/release/1.2.2.8?i=!pp,接下來面臨的一個問題就是如何調試和定位漏洞。
首先解決調試的問題,調試的首要工作就是拿到設備的 shell,不過最新的固件并沒有提供調試的接口,筆者這里通過 UART 串口和修改固件包的方式拿到了最新固件的調試權限,具體的方法參考之前寫過的一篇文章路由器調試之getshell,可見:https://badmonkey.site/archives/router-debug-getshell
調試準備
Cisco RV110W 是 mipsel 架構,所以需要先找一個對應的 gdb-server,可以自己交叉編譯也可以使用別人編譯好的,這里推薦 gdb-static-cross,傳送門:https://github.com/stayliv3/gdb-static-cross
漏洞定位
官方公告指出漏洞存在于 UPnP 服務中,首先進入后臺管理,FireWall 的 Basic Settings 打開 UPnP 的配置

然后 nmap 掃一下端口,并沒有發現 UPnP 的端口,但是測試發現 UPnP 的確打開了。

這里筆者使用 UPnPy (https://upnpy.readthedocs.io/en/latest/)進行漏洞的測試和利用。
import socket
msg = \
b'M-SEARCH * HTTP/1.1\r' \
b'HOST:239.255.255.250:1900\r' \
b'ST:upnp:rootdevice\r' \
b'MX:2\r' \
b'MAN:"ssdp:discover"\r' \
b'\r'
# 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 是一種通用的協議標準,廠商大多按照標準實現,即很多 action 都是一致的,但是也有必要對設備提供的服務進行分析以便于漏洞的定位和利用,同樣使用 UPnPy 進行信息的收集
import upnpy
import socket
import requests
from upnpy.ssdp.SSDPDevice import SSDPDevice
msg = \
b'M-SEARCH * HTTP/1.1\r' \
b'HOST:239.255.255.250:1900\r' \
b'ST:upnp:rootdevice\r' \
b'MX:2\r' \
b'MAN:"ssdp:discover"\r' \
b'\r'
# 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\rCache-Control: max-age=120\rDate: Fri, 01 Jan 2010 00:44:16 GMT\rExt: \rLocation: http://192.168.2.1:1780/InternetGatewayDevice.xml\rServer: POSIX UPnP/1.0 linux/5.70.48.16\rST: upnp:rootdevice\rUSN: uuid:31474a87-67ea-dae4-2f73-f157fb06d22b::upnp:rootdevice\r\r'
# data = b'HTTP/1.1 200 OK\rCache-Control: max-age=3600\rST: upnp:rootdevice\rUSN: uuid:824ff22b-8c7d-41c5-a131-8c3bad401726::upnp:rootdevice\rEXT:\rServer: Unspecified, UPnP/1.0, Unspecified\rLocation: http://192.168.3.1:56688/rootDesc.xml\r\r'
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,根據服務調用的請求頭,猜測action_process中的soap_control對應服務調用

在 soap_control 中仍然需要動態調試,確定具體的函數信息。

最終經過動態調試確定sub_414C28 對應AddPortMapping action,其函數調用鏈為
sub_414c28->upnp_portmap_add->upnp_osl_nat_config->strcpy(stack overflow)
不過在 upnp_portmap_add 中檢查了一下本機的 wan 口地址,由于筆者在測試的時候并沒有配置 wan 口,所以直接用 nvram 設置了一下 wan 口 ip。

棧溢出的時候需要控制程序流走到紅色的方塊中,因為藍色方塊的函數會訪問被溢出的棧導致程序在 RCE 之前崩掉,因此需要控制 *(a2+11) 的值為 0,幸運的是此處的值是可控的

0x03 漏洞利用
由于最新固件沒有 telnetd,可以反彈 shell 然后自己上一個 utelnetd,然后開啟 telnet
