针对 CVE-2020-12124 的概念验证漏洞利用,目标为 Wavlink AC1200 路由器,演示了 CGI 接口中未经身份验证的命令注入和栈缓冲区溢出漏洞。
原文发表于 https://www.klogixsecurity.com/scorpion-labs-blog/anatomy-of-an-iot-exploit-from-hands-on-to-rce
作者:David E. Baker,发布日期:2023年6月1日
本研究针对的是2020年6月Wavlink Wireless-AC1200千兆路由器的固件。此处讨论的漏洞可能已被供应商修补,也可能尚未修补,但这是一次漏洞研究案例,其价值在于过程而非结果。作者在进行本研究时,这些漏洞尚未公开发布,但已被其他研究人员独立发现并向供应商报告。
供应商在其网站的支持部分提供其产品的固件;这是获取物联网固件的常见方式,也是从设备内存中提取固件的有用替代方案。固件未加密,因此可以轻松使用binwalk提取。动态分析通过访问设备的物理样机进行,静态分析则通过Ghidra完成。
Wavlink Wireless-AC1200千兆路由器的Web界面存在多个脆弱端点,允许将用户提供的数据不受限制地复制到应用程序堆栈中,甚至直接复制到命令行,从而实现任意命令执行。
对设备的初始扫描表明,唯一暴露的资源是管理Web控制台,可通过LAN接口上的HTTP(TCP端口80)供经过身份验证的用户访问。设备可以提供更多服务,但这些服务在开箱即用的情况下默认未启用。因此,本调查仅关注Web界面。
对样机设备的nmap扫描,仅显示Web接口处于监听状态。
常见的测试——例如在设备诊断面板上对ping或traceroute命令的参数进行典型命令注入——并未立即产生令人感兴趣的结果,这令人失望。
通过管理Web面板验证后可用的管理选项。“USB存储”可见为第二个选项。
此处第一个(最终可利用的)接口出现在“USB存储”面板上,如上方截图中的第二个选项所示。该设备在802.2以太网插孔旁有一个USB端口,表明它可以提供网络附加存储(NAS)功能。
实际样机背面的照片,显示USB可用性。
漏洞研究的一个简单原则是:一段代码交互的组件越多、活动部件越多,附近存在可利用代码的可能性就越大。NAS功能的存在很有希望,因为它表明存在同时与设备软件层、硬件层和插入的外设(USB存储本身)交互的代码。
USB存储控制台的管理界面如下所示。仅“工作组”字段的存在就有其价值,因为这表明该WiFi路由器甚至可能尝试与服务器消息块(SMB)交互——这对物联网路由器而言是一项重大任务。我数不清有多少次看到用户提供的输入直接作为Unix smbpasswd函数的参数发送到命令行。
经过身份验证的用户可用的USB存储选项。
最初尝试操控这些设置失败,因为设备未检测到USB驱动器,如下所示。
除非格式正确的驱动器手动插入设备的USB端口,否则USB存储选项的配置更改将不会保存。
然而,一旦插入正确格式化的驱动器,设备允许设置FTP用户名和密码。正如预期,它将用户提供的输入放置到命令行上:
“密码”字段中的命令注入首次获得直接访问设备操作系统的Shell。
虽然有趣,但这个漏洞很难令人极度兴奋:它不仅需要拥有设备管理接口的凭据访问权限,还需要物理接触设备以操控其USB驱动器。上述漏洞允许研究人员与各个操作系统组件进行交互(并提取这些组件用于逆向工程)。
该设备是一个以BusyBox为中心的Linux系统,其Web界面由Lighttpd驱动。通用网关接口(CGI)功能由/etc_ro/lighttpd/www/cgi-bin/中的独立二进制文件提供,对CGI URI的Web请求直接启动这些二进制文件。快速查看Ghidra中的nas.cgi,显示了下面的第38行命令注入,它将用户提供的密码直接发送给do_system函数(该函数本身只是标准libc system调用的一个封装)。
用户输入被放置到命令行,作为第38行chpasswd.sh脚本的参数,导致命令注入并直接访问设备操作系统的Shell。
查看/cgi-bin/目录将寻找更有趣漏洞的任务简化为枚举可供用户使用的CGI接口,如下所示:
设备上可用CGI二进制文件的详尽列表,从本节所述漏洞建立的Shell中实时获取。用户输入被放置到命令行,作为第38行chpasswd.sh脚本的参数,导致命令注入并直接访问设备操作系统的Shell。
初步检查时,有几件事很突出。首先要注意的是,CGI二进制文件经常调用check_valid_user函数。此方法检查发出请求的IP地址是否存储在文件系统上的一个特定临时文件中。最小测试表明,在调用此方法之前,客户端的身份验证状态并未得到验证,因此在每个CGI二进制文件中,调用此函数之前的所有代码表面都可以在未经过身份验证的情况下访问。
adm.cgi的反编译,显示了check_valid_user方法。此调用之前的所有代码都在验证请求者的身份验证状态之前执行。
另一个有趣的观察是大量方法将用户提供的输入直接复制到堆栈上。例如,wireless.cgi的反编译显示了第14和15行从Web请求体中提取的NewName参数参数,接着在第34行对该用户输入进行了无保护的strcpy操作到堆栈上,如下所示:
第14和34行展示了对请求体中的NewName参数进行无保护的strcpy操作,直接复制到程序堆栈。adm.cgi的反编译显示了check_valid_user方法。此调用之前的所有代码都在验证客户端的身份验证状态之前执行。
这种将用户输入复制到堆栈上的行为本身就暗示了内存破坏漏洞,以下命令可以验证:
curl –XPOST --data "page=SetName&NewName=\`python3 –c 'print(\\"A\\"\*(512)'\`" http://target-ip/cgi-bin/wireless.cgi
虽然很有前景,但面向返回的攻击在这里并非最优选择。使用已获得的Shell访问操作系统,以下命令显示为‘1’,表明地址空间布局随机化(ASLR)实施较弱,使得ROP链有1/256的概率命中目标gadget:
# cat /proc/sys/kernel/randomize\_va\_space
1
此外,以下命令显示小端二进制文件在编译时设置了堆栈保护字节,因此只有链中的最后一个gadget可以落在二进制文件内的预定位置。如果存在看门狗进程可以在崩溃后重启Web服务器,那么反复抛出面向返回的漏洞并期望最终成功并非不可能,但代码中其他地方也可能存在更好的bug。
xxd /etc\_ro/lighttpd/www/cgi-bin/wireless.cgi | head -n 10
继续查看CGI函数,最终会检查live\_api.cgi。这个二进制文件没有调用check\_valid\_user,因此任何发往URI /cgi-bin/live\_api.cgi的Web请求都会未经身份验证地运行此CGI应用程序。图11中的第9行显示,QUERY\_STRING环境变量(根据Apache CGI规范,它是请求URI中紧跟在问号之后的部分,因此由用户提供)被存储在pcVar1中,并在图11的第19行发送给satellite_status方法。
live\_api.cgi的反编译,显示第9行从URI获取的用户输入,并在第19行发送给satellite\_status。adm.cgi的反编译显示了check\_valid\_user方法。此调用之前的所有代码都在验证客户端的身份验证状态之前执行。
satellite_status方法的反编译如图12所示,显示查询字符串本身(现在为param\_1)被解析为page、id和ip参数。ip参数通过printf函数复制到图12第38行的局部变量中,并在第39行传递给do\_system函数。缺少对check\_user\_auth的调用表明,URI中来自未授权用户的任意客户端输入将被直接放置到ip URI参数的命令行上,如下所示确认:
satellite\_status函数的反编译,显示第22和23行从查询字符串(现在为param\_1)中解析出ip参数,然后在第38和39行调用do\_system。
事实胜于雄辩,展示了利用漏洞实现设备远程接管的效果。
在刚刚上市的物联网设备中寻找漏洞可能看起来像是唾手可得,但正如本文开头所言,本调查的价值在于其过程而非最终结果。
如果没有对固件的静态分析,就不可能理解身份验证的绕过方式和命令注入的位置。如果固件不能在线获取,就需要通过动态方式访问设备的操作系统来获取这些信息,而这几乎无望实现。这种访问级别本身既依赖于物理接触设备,也依赖于专门的硬件,或者对代码中可能出错位置的定向猜测。
希望您喜欢这次旅程,并期待您的再次光临。 祝你黑客愉快!
作者:
David Baker,高级安全顾问,测试部门,K logix