本文介绍在2019年11月的Pwn2Own Tokyo比赛中发现并使用的命令注入漏洞。
该漏洞存在于tdpServer守护进程(/usr/bin/tdpServer)中,该守护进程在TP-Link Archer A7(AC1750)路由器上运行,硬件版本为5,MIPS体系结构,固件版本190726。此漏洞只能由路由器LAN端的攻击者利用 ,但是不需要身份验证。利用此漏洞后,攻击者能够以root用户身份执行任何命令,包括从另一台主机下载并执行二进制文件,漏洞已分配编号CVE-2020-10882,TP-Link在固件版本A7(US)_V5_200220上做了补丁修复。
https://www.tp-link.com/us/support/download/archer-a7/
本文中的所有函数偏移量和/usr/bin/tdpServer代码段均来自固件版本190726。
0x01 tdpServer
在tdpServer上0.0.0.0 UDP端口20002的守护进程在做监听,目前尚未完全了解守护进程的整体功能,这对于开发者来说是不必要的。但是,该守护进程似乎是TP-Link移动应用程序和路由器之间的桥梁,从而允许从移动应用程序建立某种控制。
守护进程通过使用带有加密payload的UDP数据包与移动应用程序进行通信。
我们逆向了数据包格式,如下所示:
图1-逆向的tdpServer数据包格式
数据包类型确定守护进程中将调用什么服务。1类型将导致守护进程调用tdpd服务,该服务将简单地使用具有特定TETHER_KEY哈希值的数据包进行回复。因为这与漏洞无关,所以我们没有对其进行详细分析。
另一种可能的类型是0xf0,它调用onemesh服务,该服务是漏洞所在。
TP-Link在其许多路由器的最新固件版本中引入了 OneMesh,这是一项特有的网格技术。
https://www.tp-link.com/us/onemesh/compatibility/
数据包中的其他字段在上面的注释中做了解释。
0x02 漏洞分析
设备启动后,调用的第一个函数是tdpd_pkt_handler_loop()(偏移量0x40d164),这将打开侦听端口20002的UDP套接字,一旦接收到数据包,此函数会将数据包传递给tpdp_pkt_parser()(0x40cfe0)。
如下所示:
图2-tdpd_pkt_parser()#1
在第一个代码段中,我们看到解析器首先检查以查看UDP套接字报告的数据包大小是否至少为0x10,即Header的大小。然后,它调用tdpd_get_pkt_len()(0x40d620),该函数返回在包头(len字段)中声明的包长度。如果数据包长度超过0x410,则此函数返回-1。
最终检查将通过tdpd_pkt_sanity_checks()(0x40c9d0)完成,为简洁起见,将不会显示该检查,但会进行两次验证。首先,它检查数据包版本(版本字段,数据包中的第一个字节)是否等于1。接着,它使用自定义校验和函数tpdp_pkt_calc_checksum()(0x4037f0)计算数据包的校验和。
为了更好地了解发生了什么,下面是函数calc_checksum(),它是lao_bomb利用代码的一部分。
显示在tpdp_pkt_calc_checksum()函数附近,更易于理解。
图3-来自lao_bomb漏洞利用代码的calc_checksum()
校验和的计算非常简单。首先,在数据包的校验和字段中设置魔术变量0x5a6b7c8d,然后使用带有1024个字节的表reference_tbl来计算整个数据包(包括报头)的校验和。
校验和通过验证并且所有结果正确之后,tdpd_pkt_sanity_checks()返回0,然后输入tdpd_pkt_parser()的下一部分:
图4-tdpd_pkt_parser()#2
在此检查数据包的第二个字节,即类型字段, 以查看它是0(tdpd)还是0xf0(onemesh)。在后一个分支中,它还会检查全局变量onemesh_flag是否设置为1(默认情况下为1),这是我们要遵循的分支。然后,我们输入onemesh_main()(0x40cd78)。
为简洁起见,此处不会显示onemesh_main(),但其工作是根据数据包的操作码字段调用另一个函数。为了达到我们的漏洞的函数,必须将操作码字段设置为6,将标志字段设置为1。在这种情况下,将调用onemesh_slave_key_offer()(0x414d14)。
这是我们的漏洞的函数,而且由于它很长,因此我值展示相关部分。
图5-onemesh_slave_key_offer()#1
在onemesh_slave_key_offer()的第一个代码段中,我们看到它将数据包payload传递给tpapp_aes_decrypt()(0x40b190)。为了简洁起见,也不会显示此函数,但是很容易从名称及其参数中了解它的作用:它使用AES算法和静态密钥“ TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP”解密数据包payload。
要在lao_bomb漏洞中进行复制,这种加密非常复杂。我们将在下一部分中对此进行详细说明。
现在,我们将假定tpapp_aes_decrypt能够成功解密该数据包,因此进入onemesh_slave_key_offer()中的下一个相关代码段:
图6- onemesh_slave_key_offer()#2
在此代码段中,我们看到一些其他函数(基本上是onemesh对象的设置)被调用,随后是实际数据包Payload的解析开始位置。
预期的payload是一个JSON对象,如下所示:
图7-onemesh_slave_key_offer()的示例JSONpayload
在图6中,我们可以看到代码首先获取方法 JSON键及其值,然后获取已解析数据 JSON对象的开始。
下一个代码段显示了数据对象的每个键都是按顺序处理的。
如果所需的键之一不存在,该函数将直接退出:
图8-onemesh_slave_key_offer()#3
从上面可以看出,每个JSON密钥的值都经过解析,然后复制到堆栈变量(slaveMac,slaveIp等)中。
解析JSON对象后,该函数通过调用create_csjon_obj()(0x405fe8)开始准备响应。
从这里开始,该函数对接收到的数据执行各种操作。
重要的代码部分如下所示:
图9-onemesh_slave_key_offer()#4
这就是存在漏洞的地方。返回上面的图8 ,可以看到JSON键slave_mac的值已复制到slaveMac堆栈变量中。在图9中,slaveMac被复制的sprintf到systemCmd变量,然后将其传递给()系统。
0x03 漏洞利用
到达漏洞函数
首先要确定的是如何到达命令注入函数。经过反复试验,我们发现发送上面图7所示的JSON结构始终会命中漏洞的代码路径。方法必须为slave_key_offer,而want_to_join必须为false。其他值可以任意选择,尽管slave_mac以外的字段中的某些特殊字符可能会导致漏洞的函数提前退出并且不处理我们的注入。
关于包头,如前所述,我们必须将type设置为0xf0,将opcode设置为6,将flags设置为1,并正确获取校验和字段。
加密数据包
如上一节所述,数据包使用固定密钥为TPONEMESH_Kf!xn?gj6pMAt-wBNV_TDP的AES加密。不过,这个难题还有一些遗漏的部分。密码用于CBC模式,IV为1234567890abcdef1234567890abcdef固定值。此外,尽管有256位密钥和IV,但实际使用的算法是有128位密钥的AES-CBC,因此未使用密钥和IV的一半。
实现代码执行
现在我们知道了如何访问漏洞的代码路径,我们是否可以仅通过命令发送数据包并执行代码?有两个要解决的问题:
1. strncpy()仅将slave_mac_info键中的0x11字节复制到slaveMac变量中,其中包括终止的空字节。
2. 需要执行一些转义操作,因为slaveMac中的值将用单引号和双引号引起来。
考虑到这两个限制,实际可用空间非常有限。
为了转义参数并执行payload,我们必须添加以下字符:
';< PAYLOAD >'
刚刚丢弃了3个字符,仅剩下13个字节来构造payload。只有13个字节(字符),代码执行几乎是不可能的。另外,我们通过测试发现该限制实际上是12个字节。
我们的解决方案是多次触发该错误,一次在目标字符上建立所需的命令文件。然后,我们最后一次触发该错误以将命令文件作为Shell脚本执行。
例如,考虑将字符“ a”附加到名为“ z”的文件中,我们可以简单地执行以下操作:
`printf a>>z`
请注意,即使是这种简单情况,也需要11个字节。
如果我们想写一个数字,上面显示的技术将不起作用。这是因为Shell会将数字解释为文件描述符。同样,特殊字符如“。” 要么 ';' 无法使用上述方法将由Shell解释的文件写入文件。
要处理这些情况,我们需要执行以下操作:
`printf '1'>x`
这实际上并不会将字符附加到现有文件上,而是创建一个仅包含字符“ 1”的名为“ x”的新文件(用该名称覆盖任何现有文件)。由于此payload已经是12个字符长,因此无法添加额外的“>”,这将使我们无法将追加1到正在构建的命令文件中。
但是,有一个解决方案。每次需要发出数字或特殊字符时,我们首先将字符写入新文件,然后使用cat将新文件的内容附加到正在构建的命令文件中:
`cat x*>>z*`
你可能想知道为什么我们在每个文件名的末尾都需要''。这是因为尽管我们总是对发送的命令进行转义,但是应该执行的lua脚本的最后几个字节仍以文件名结尾。这意味着当我们尝试创建名为“ z”的文件时,实际上它将被命名为“ z”})'。将完整文件名添加到我们的命令中将消耗太多字节,shell会使用特殊的''字符自动补全。
我们没有更改为/ tmp,因为嵌入式设备通常需要多次写入/ tmp,因为文件系统根目录通常不可写。根文件系统以读写方式安装,这是TP-Link的主要安全错误。如果像大多数使用SquashFS文件系统的嵌入式设备一样,以只读方式安装它,则这种特殊的攻击将是不可能的,因为添加cd tmp将占用太多可用的12个字符。
这样,我们便有了执行任意命令所需的所有步骤,我们逐字节发送命令,将其添加到命令文件“ z”,然后发送payload:
`sh z`
然后我们的命令文件将以root身份执行,从这里开始,我们可以下载并执行二进制文件,并且可以完全控制路由器。
本文翻译自:https://www.thezdi.com/blog/2020/4/6/exploiting-the-tp-link-archer-c7-at-pwn2own-tokyo如若转载,请注明原文地址: