本文首发于
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.
——NISTTotolink多款路由器产品的setUpgradeFW函数的FileName参数存在命令注入漏洞。攻击者可利用该漏洞执行任意命令。
——阿里云漏洞库
固件模拟
这个漏洞影响范围还挺广的,上报的就有6个型号,那么大概率还有其他型号也有类似漏洞。
所以咱们就以A3000RU V5.9c.5185_B20201128
为例,去模拟真实环境。(其实是正好这个型号的这个版本之前已经模拟成功过 )
至于模拟的话呢,这个型号其实挺好模拟的,直接FirmAE
一键跑就能起来。不过因为系统是Linux
的,TOTOLINK
的憨批前端直接给当成移动端UA特征去显示了。
不过跑起来大概一分钟左右,就会莫名其妙访问不了。
去看了系统日志,发现有个reboot
调用,好家伙,居然是watchdog
调的。
那么就想办法改改watchdog
呗,主要两个思路:
- 直接干掉
watchdog
- 改掉重启的前置条件
第一个思路的话,可能有些路由器干掉watchdog
就会崩,所以算是简单粗暴的下策,但是这回咱们测了下(启动后马上手动kill -9
掉)发现直接干掉问题不大,没啥负面影响,所以干就完了。
咱们把watchdog
重命名成watchdog_
,然后写个shell
当成假的watchdog
。
然后重新模拟一遍,成功干掉。
漏洞复现
先默认账号密码admin@admin
登录,根据漏洞描述的点位setUpgradeFW
推测,应该是固件升级的接口,尝试抓包。
似乎和描述的setUpgradeFW
和FileName
参数不太一样。
尝试直接全局搜索关键词,发现cstecgi.cgi
确实是相关的,以及一个cstecgi.cgi
。
那先看看这个cgi的代码吧。
这处确实和漏洞描述高度相关,那往上找找前置条件的v28
是啥吧,看起来是get
传参,因为上面咱们抓包的时候就见过一模一样的get
传参。
是从QUERY_STRING
拿的,那就没猜错,确实就是get
传参的。
那么回头看看填充FileName
的v20
是啥。
发现是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 /
。
漏洞成功复现。
Comments NOTHING