
Cisco RV110w UPnP スタックオーバーフロー
最近UPnPが注目されており、ちょうど手元にCisco RV110Wがあります。2021年8月にCisco公式がCisco RVシリーズに関するUPnPの0dayを発表しましたが、具体的な詳細は公開されていませんでした。そこで、手元のデバイスを使ってこの脆弱性をデバッグ・調査することにしました。脆弱性の告知は公式サイトで確認できます。
まず、ファームウェアを最新バージョン1.2.2.8にアップデートします。次に直面する問題は、どのようにデバッグし、脆弱性を特定するかです。まずデバッグの問題を解決します。デバッグの最初の作業はデバイスのシェルを取得することですが、最新のファームウェアにはデバッグ用のインターフェースが用意されていません。筆者はここで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は汎用プロトコル標準であり、多くのベンダーは標準に従って実装しています。つまり、多くのアクションは共通ですが、脆弱性の特定や悪用のためにデバイスが提供するサービスを分析する必要があります。同様に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_engineが呼び出されます。

追跡分析すると、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アクションに対応することが確認されました。その関数呼び出しチェーンは以下の通りです。
sub_414c28->upnp_portmap_add->upnp_osl_nat_config->strcpy(stack overflow)
ただし、upnp_portmap_add内でWANインターフェースのアドレスがチェックされます。テスト時にWANインターフェースを設定していなかったため、nvramでWAN IPを直接設定しました。

スタックオーバーフロー時には、プログラムの流れを赤い四角のブロックに向ける必要があります。青い四角の関数はオーバーフローしたスタックにアクセスし、RCEの前にプログラムがクラッシュするためです。そのため、*(a2+11)の値を0に制御する必要があります。幸いなことに、この値は制御可能です。

最新のファームウェアにはtelnetdが含まれていないため、リバースシェルを取得した後、utelnetdをアップロードしてtelnetを有効にすることができます。

参考:リバースシェルの手法