站点图标 Wankko Ree's Blog

TOTOLINK CVE-2022-26210 漏洞分析&复现

本文首发于IOTsec-Zone

漏洞描述

Totolink A830R V5.9c.4729_B20191112, A3100R V4.1.2cu.5050_B20200504, A950RG V4.1.2cu.5161_B20200903, A800R V4.1.2cu.5137_B20200730, A3000RU V5.9c.5185_B20201128, and A810R V4.1.2cu.5182_B20201026 were discovered to contain a command injection vulnerability in the function setUpgradeFW, via the FileName parameter. This vulnerability allows attackers to execute arbitrary commands via a crafted request.
——NIST

Totolink多款路由器产品的setUpgradeFW函数的FileName参数存在命令注入漏洞。攻击者可利用该漏洞执行任意命令。
——阿里云漏洞库

固件模拟

这个漏洞影响范围还挺广的,上报的就有6个型号,那么大概率还有其他型号也有类似漏洞。

所以咱们就以A3000RU V5.9c.5185_B20201128为例,去模拟真实环境。(其实是正好这个型号的这个版本之前已经模拟成功过

至于模拟的话呢,这个型号其实挺好模拟的,直接FirmAE一键跑就能起来。不过因为系统是Linux的,TOTOLINK的憨批前端直接给当成移动端UA特征去显示了。

不过跑起来大概一分钟左右,就会莫名其妙访问不了。

去看了系统日志,发现有个reboot调用,好家伙,居然是watchdog调的。

那么就想办法改改watchdog呗,主要两个思路:

  1. 直接干掉watchdog
  2. 改掉重启的前置条件

第一个思路的话,可能有些路由器干掉watchdog就会崩,所以算是简单粗暴的下策,但是这回咱们测了下(启动后马上手动kill -9掉)发现直接干掉问题不大,没啥负面影响,所以干就完了。

咱们把watchdog重命名成watchdog_,然后写个shell当成假的watchdog

然后重新模拟一遍,成功干掉。

漏洞复现

先默认账号密码admin@admin登录,根据漏洞描述的点位setUpgradeFW推测,应该是固件升级的接口,尝试抓包。

似乎和描述的setUpgradeFWFileName参数不太一样。

尝试直接全局搜索关键词,发现cstecgi.cgi确实是相关的,以及一个cstecgi.cgi

那先看看这个cgi的代码吧。

这处确实和漏洞描述高度相关,那往上找找前置条件的v28是啥吧,看起来是get传参,因为上面咱们抓包的时候就见过一模一样的get传参。

是从QUERY_STRING拿的,那就没猜错,确实就是get传参的。

那么回头看看填充FileNamev20是啥。

发现是UPLOAD_FILENAME,这应该是上传的文件名?所以尝试改一下数据包重新发送。

但是命令执行没成功。

那就继续往下找找执行点在哪吧。

但是没找到,因为再往下走就开始准备响应了,所以最有可能的就是web_getData这个调用了,因为只有这个的传参是和请求细节高度相关的。

看看这个调用,发现是个动态库的接口。

全局搜一下,发现这个调用来源是libmosquitto.so

但这不是MQTT嘛,难道这路由器的内部数据流动是靠的MQTT推送?

搜一下调用看看。

发现刚才那个upgrade.so库也调用过这个MQTT库。

那会不会这个upgrade.so库就是负责处理这个固件升级消息的呢?

结果发现这里面真有一个setUpgradeFW函数。

然后最关键的数据FileName是给了v8,然后v8出现的地方就这么三处。

下面两处本质上是一样的,只不过前置条件不同而已,而且这里确实是直接把v8填充进命令就去system执行了。

那不用多说,肯定是最后这个最好触发嘛,毕竟在逻辑最外层,只需要固件小于1000字节就行。

而且还不需要走里面的固件格式检查,相当于说咱们之前抓包用的那个数据包就符合要求。

但这就奇怪了,如果是把 pwd>/web_cste/1.txt 填充进rm -f %s 1>/dev/null 2>&1的话,那就是rm -f pwd>/web_cste/1.txt 1>/dev/null 2>&1,这很明显是可以执行的嘛。

那么问题出在哪呢?难道是文件名被编码了?

尝试修改命令内容,直接输出文件名看看到底啥情况。

但是这个动态库是谁调的咱还不知道,去全局搜一下。

完全没有啊,难道是以文件夹为单位的动态加载?

发现有个cste_sub可执行文件,看看有没有这个进程,毕竟如果数据走MQTT的话,肯定是有后台进程的嘛。

还真有一个,那就替换一下刚才改的动态库,然后kill掉这个进程自己前台跑一下,看看有没有输出。

结果发现文件名是/var/uImage.img

看看内容是啥。

这不是整个请求体嘛...为啥没解析就全存了,难道这个功能本来就有问题?

全局搜一下看看uImage这文件名是哪来的。

这...上传的文件名是写死的固定值,那咋实现可控的?

回头看看cgi,发现了个小细节。

如果get传参为空的话,最后会落到124行的v3 = cJSON_Parse(v29);,那么基于get传参为空这个前提,v29是否可控呢?

似乎确实可以,这个stdin应该就是请求体,因为上面CONTENT_LENGTH正好作为read的长度用了。

那么就想想怎么构造请求呗,既然v29完全是可以自己传的,那咱们只要把上面{"topicurl":"setting/setUpgradeFW","FileName":"%s","Flags":"%d","ContentLength":"%s"}填充一下然后通过post传进去好像就行?

看看输出的文件名,确实过来了。

那咱们把刚才那个动态库给替换回原来的(不然命令注入点没了),然后重新跑一下看看。

成功执行了pwd,再看看别的命令,比如说ls -al /

漏洞成功复现。


The End
退出移动版