一、简介
TP-Link最近修补了TL-R600VPN路由器中的三个漏洞,固件版本1.3.0。在与TP-Link协商后,思科公开披露这些漏洞,并给出解决方案,本篇文章对这些漏洞的内部工作方式进行深入研究,并给出概念验证的方法。
二、背景
TL-R600VPN是一款由TP-Link生产的小型办公室/家庭路由器,该路由器在芯片上集成了Realtek RTL8198系统,该芯片使用了Lexra开发的MIPS-1架构的分支版本,除了一些存储和对齐指令不同,其它指令集基本相同。Lexra中并没有LWL, SWL, LWR和SWR等指令的说明,这些专有指令通常在MIPS-1架构编译程序时使用。在Lexra中使用会产生段错误。
有关Lexra MIPS与MIPS-1两者之间更多的比较信息,请参考文章The Lexra Story和MIPS-1 patent filing。
三、漏洞细节
当该设备的HTTP服务器处理对/fs/目录的请求时会触发漏洞,该漏洞允许经身份验证的攻击者远程执行载荷代码。
当访问/fs/目录中的以下任何页面时,应用程序会错误的解析HTTP标头。
http://<router_ip>/fs/help
http://<router_ip>/fs/images
http://<router_ip>/fs/frames
http://<router_ip>/fs/dynaform
http://<router_ip>/fs/localiztion (NOTE: this is not a typo)
在函数”httpGetMimeTypeByFileName”中,Web服务器尝试解析所请求页面的文件扩展名以确定mime类型,在这个过程中,服务器会使用strlen()函数计算所请求页面名称的长度,在堆中查找并匹配该字符串末尾,然后读取文件扩展名,遇到字符”.”(0x2e)结束。
在请求页面上应始终包含扩展名,以防止解析错误。下面是相关例子,在/web/dynaform/css_main.css页面的GDB字符串输出中,可以看到解析后的文件扩展名为’css’。
但是,如果我们请求一个易受攻击的页面,如下所示,我们可以看到解析的URI并不包含字符”.”(0x2e),因此,应用程序会继续向后搜索。这导致可以搜索到一些有效负载。
如下所示,在对/fs/help页面的GDB字符串输出中(0x67a170),并没有解析到文件扩展名。
当遇到字符”.”(0x2e)后,会调用toUpper()函数处理提取到的字符串,然后通过存储指令将该操作的结果写入基于堆栈的缓冲区,如下所示:
程序继续执行,在运行httpGetMimeTypeByFileName函数后,堆栈上会保存返回值和5个寄存器的数值,当该漏洞被利用时,这些值会被覆盖。
在该函数的结尾,会将数据复制到缓冲区并覆盖堆栈上的原始数据,通过弹出堆栈,用户可以控制返回地址,也意味着用户能够在HTTPD进程的上下文中执行远程代码。
toUpper()函数分析
在解析HTTP标头时,设备会迭代每个字节,构建缓冲区。直到匹配到0x2e。随后将调用toUpper()函数,并把缓冲区作为参数。该函数是将缓冲区的每个ASCII字符转换为大写。
在用HTTP标头发送shellcode时会产生问题,因为会调用toUpper()函数,导致阻止使用任何小写字符,如下所示:
我们通过查看httpGetMimeTypeByFileName函数结尾处最后一次跳转前寄存器的值,可观察到标头的字符’a’(0x61)已经转换为大写(0x41)。如下图所示:
通过对寄存器的观察,我们可以在toUpper()函数被调用后,找到一些可预测的数据及位置。
虽然会影响httpGetMimeTypeByFileName函数结尾处的最后一次跳转,但我们可以通过检查堆栈上的数据,找到大写的标题数据(包括有效负载)存储位置。
相比之下,如果我们检查寄存器$s5指向的位置所存储的数据,我们会看到原始数据头仍然可访问。
我们发现该部分内存的权限显示可执行,可以直接跳转到原始标头。
绕过toUpper()
为了解决toUpper()函数带来的问题,我们创建了一小段memcpy()的代码,它在获取$ra的控制权后不使用任何小写字符或空字符来执行。这样我们就能将原始标头数据复制到堆栈中并跳转执行。
我们使用了一种改进的ret2libc技术,该技术允许我们利用uClibc中的组件获取指向堆栈的指针,并为我们的代码设置寄存器,这样我们便可以获得memcpy()函数的执行结果。
第一个组件位于uClibc偏移地址0x0002fc84处,用于将堆栈指针递增0x20。我们将第二个组件的地址放在0x20+$sp处,如下所示:
位于uClibc偏移地址0x000155b0处的第二个组件用于获取指向递增的堆栈缓冲区的指针。然后将所需的指针放入寄存器$a1,我们将第三个组件的地址放在0x58+$sp处。如下所示:
最后,位于uClibc偏移地址0x000172fc处的组件用于跳转到堆栈缓冲区。
我们需要获取uClibc的加载地址,以便于我们计算使用这些组件的真是偏移,通过查看下面的进程内存映射,我们可以看到uClibc的可执行版本加载到地址0x2aaee000处。
通过获取uClibc的加载地址并添加到每个组件,我们可以获得所需代码的可用地址,进而执行我们的代码和有效负载。
LexraMIPS shellcode
虽然LexraMIPS基于MIPS规范,但在尝试执行某些MIPS指令时,会存在一些偏差,导致与预期的结果不同。因此,我们选择用GCC开发的LexraMIPS下的shellcode。下面的代码采用回连攻击者的方法,将stdin,stdout和stderr复制到socket(套接字)中,最后生成一个shell。
我们首先在设备上打开一个socket,并防止$t7寄存器包含null字符,值得注意的是,MIPS $zero寄存器在使用时不包含null字符。
打开socket后,我们使用connect syscall创建设备到攻击者的TCP连接,在这个步骤中,null字符的处理是关键问题,因为此设备的默认子网包含0,为了避免该问题,我们使用了一种技术方法,该方法强制寄存器值溢出并产生所需ip地址时没有null字符。
为确保设备接受我们的输入并正确显示任何输出,我们需要复制stdin,stdout和stderr的文件描述符到socket中,这样我们便可以查看任何输出。
最后,我们使用execve系统调用在设备上生成shell,该shell是我们的socket生成,并且我们控制了stdin,stdout和stderr。我们可以通过远程控制新的shell。
四、总结
不幸的时,这种类型的漏洞在物联网设备中很常见,攻击者可以通过这些漏洞进行攻击,所以每个人都必须提高物联网安全意识。