CVE-2023-26315
复现环境
OS:Archlinux
binwalk:3.1.0
qemu:11.0.2
漏洞概要
https://www.cnvd.org.cn/flaw/show/CNVD-2024-23093
https://www.cve.org/CVERecord?id=CVE-2023-26315
固件版本 < 1.0.168
小米
AX9000路由器在1.0.168版本及之前存在二进制漏洞(命令注入),该漏洞由于未对非法的appid做出有效限制而引起。已授权登录的攻击者在成功利用此漏洞后,可在远程目标设备上执行任意命令,并获得设备的最高控制权,造成权限提升
qemu模拟
小米固件最外面使用的是UBIFS文件系统,固件没有被加密
在解这种文件系统之前需要安装ubi_reader,链接如下:
1 | https://github.com/onekey-sec/ubi_reader |
先用binwalk解出一个.ubi文件,然后在ubifs-root解出三个.ubifs文件,然后对其中xxx-ubi_rootfs.ubifs用binwalk再解开,即可得到里面的SquashFS文件系统

宿主机tap配置
1 | sudo ip tuntap add dev tap0 mode tap |
virbr0是virt-manager虚拟管理程序默认配置的,不需要我们自己手动的配置,将tap0挂上去即可。
1 | sudo ip link set virbr0 up |
启动up
这里用了winmt师傅提供的脚本和环境https://drive.google.com/file/d/1FcbCkfGuHlvohGlzA-HRRyM8izqhHALE/view
启动脚本
1 |
|
然后在qemu直接dhcp自动获取ip地址即可
1 | dhclient |


tap0和virbr0为up就可以通信了
将squashfs-root传上去,这里我用scp上传的时候输入正确密码,反复给我错误,使用如下命令即可快速上传:
1 | virt-copy-in -a debian-3607-aarch64.qcow2 squashfs-root.tar.gz /root |
然后就是起手了
1 | tar -xvf squashfs-root.tar.gz |
根据openwrt的初始化流程,先/etc/preinit,再去执行/sbin/init去初始化,但是执行preinit的时候,qemu直接重启了,那么只能先执行/sbin/init中的/sbin/proc,启动进程管理器
启动httpd服务
查找httpd服务
1 | find /etc -name "*http*" |

有sysapihttpd(小米定制)、mihttpd、uhttpd
sysapihttpd配置
etc/sysapihttpd/sysapihttpd.conf
监听80, 8098, 8080, 8999, 443




mihttpd配置
etc/mihttpd/mihttpd.conf
mihttpd启动,只监听8198,定义了一些文件上传下载的API,可以不启动

uhttpd配置
etc/uhttpd/uhttpd.conf
uhttpd默认不启动

启动sysapihttpd
1 | /etc/init.d/sysapihttpd start |

缺失这个procd_sysapihttpd.lock文件,创建对应的目录和文件即可
1 | mkdir -p /var/lock |

这个报错需要连接ubus(总线通信),启动/sbin/ubusd &即可
1 | /sbin/ubusd & |

可以看到报错是缺少文件,但是没有给出缺失哪个文件,用ida将ubusd进行反编译
执行的是ubusd,而不是软链接tbusd,所以v8被赋值为/var/run/ubus.sock,之后会将其作为参数传入usock函数中,然后当usock函数返回错误时,就会走到perror("usock")进行报错


创建之后可以正常启动
1 | mkdir -p /var/run |

可以看到程序和端口已经启动了


nmap可以扫到端口

访问时,会出问题

通过查看进程,可以发现sysapihttpd的主进程号没变,但是子进程号已经变了,可以说明的是当进行访问的时候server崩溃了,子进程crash,主进程(守护进程)有开了新的子进程

缺少/etc/TZ文件(时区相关的配置文件)

1 | echo "WAUST-8WAUDT" > /etc/TZ |
之后的系统调用没显示,就参考winmt师傅的报错吧

将if判断绕过

总结一下命令:
1 | mkdir -p /var/run |

跳过初始化配置
因为我们只启动了部分服务,其中有部分配置与硬件有关,那么接下来想办法绕过初始化配置
我这里grep太慢了,直接使用ripgrep来查询
1 | rg "init.html" |

定位这个usr/lib/lua/luci/view/web/sysauth.htm文件中,进行分析

需要绕过这个if判断,使之不重定向到/init.html
getInitInfo方法来自这个xiaoqiang.util.XQSysUtil模块

通过去访问不是源码形式,可以寻找工具进行反编译
由于小米的前端是lua写的,但是其中的lua文件不是源码,不是直接可以反编译的文件,需要使用unluac_miwifi进行反编译,对lua反编译的常用工具是unluac和luadec,但是小米对lua的解释器进行了魔改,很好的是有师傅对小米固件写反编译工具unluac_miwifi和luaadec_miwifi,在这里说声感谢
将工具放置在任意目录下
1 | git clone https://github.com/NyaMisty/unluac_miwifi.git |
然后使用脚本一键处理lua反编译,硬编码地址,需要自己修改
1 | import os |
反编译如下:

这样也不是很好看,由于小米各种型号路由器的lua部分变化不大,可以寻找小米别的版本的固件,这里的源码来自于小米路由器4Pro 稳定版,这个版本的lua没有编译
又使用到这个函数
usr/lib/lua/xiaoqiang/XQPreference.lua

key为INITTED
1 | local value = uci:get("xiaoqiang", "common", "INITTED") |
uci是openwrt的配置系统,uci:get(配置文件名, 节名, 选项名)
设置uci配置项即可,标记为已初始化
使用uci命令来通用配置
1 | uci set xiaoqiang.common.INITTED=1 |
重新访问

设置登录密码
由于是默认3那么在uci配置系统肯定有相关的配置信息,直接搜索
1 | uci show | grep admin |

但是这是加密后的值,不知道原密码是什么
所以接下来分析加密加密逻辑
F12查看web源码


可以看到加密逻辑
用户名固定为了admin,密码pwd通过调用oldPwd()加密之后返回的结果
key为a2ffa5c9be07488bbb04a3a47d3c5f6a
加密逻辑如下:
1 | 真实密码 = sha1(用户设置的密码 + a2ffa5c9be07488bbb04a3a47d3c5f6a) |
实例如下:
假如我们设置一个用户密码为:starlight
那么加密后的值如下:
1 | fcb0870235aeae600cd210ea000662e878a392bb=sha1(starlighta2ffa5c9be07488bbb04a3a47d3c5f6a) |
通过uci给account.common.admin配置为fcb0870235aeae600cd210ea000662e878a392bb
1 | uci set account.common.admin=fcb0870235aeae600cd210ea000662e878a392bb |

然后就能使用starlight密码登入进去了

漏洞分析
分析usr/lib/lua/luci/controller/api/xqdatacenter.lua我这里用之前别的版本的固件lua进行分析

usr/lib/lua/luci/dispatcher.lua


当访问/api/xqdatacenter/request这个api端点需要用户登录认证,用户名设置为admin,sysauth_authenticator = "jsonauth"当token不存在或错误时,通过authenticator.jsonauth函数进行登录验证
对于路由入口,entry{}的第五个参数为flag位为0x01代表不需要鉴权,这里没有设置flag位,所以需要鉴权
鉴权代码如下:
usr/lib/lua/luci/dispatcher.lua



接下来分析tunnelRequest
usr/lib/lua/luci/controller/api/xqdatacenter.lua
对传入payload字段内的JSON数据进行Base64编码处理,拼接到THRIFT_TUNNEL_TO_DATACENTER 所指代的命令中并执行,其中formvalue_unsafe函数会获取payload字段的内容,最终调hackCheck函数过滤掉filterChars集合中的字符

usr/lib/lua/xiaoqiang/util/XQSecureUtil.lua


usr/lib/lua/xiaoqiang/common/XQConfigs.lua

最后也就是执行thrifttunnel 0 base64(payload)
然后接下来分析usr/sbin/thrifttunnel执行文件
1 | sub_AB08(int a1, __int64 a2) |
首先会先判断参数个数是否为3,然后将base64后的payload赋值给v10,sub_1B6FC函数的逻辑就是拷贝,然后会判断v10是否为空,如果不是则将base64后的payload赋值给v12,,然后再和v11传进sub_1B9B03

接着就是switch匹配
这里匹配 case 0,然后进入sub_1BAE0函数

创建了socket,然后传入的是json数据,可以判断此处会将payload字段的json数据发送给本地127.0.0.1的9090端口

接下来就要去寻找哪个程序会监听9090端口,根据winmt师傅的文章,是/usr/sbin/datacenter程序正在监听9090端口,所以数据被传到了datacenter(在这里笔者认为winmt师傅当时有真机分析,然后启动的服务和配置比较完整,所以分析的时候,用netstat查看端口就行了,然后init.d有启动脚本)
如图,可以看见datacenter程序创建了非阻塞服务器实例,监听9090端口

接下来就开启服务

这个函数在usr/lib/libthriftnb-0.9.1.so里

1 | libthriftnb::TNonblockingServer::serve() |
(吐槽:从百草园到三味书屋)
接下来看DataCenterHandler::request

其中调用了APIMapping::APIMapping

然后再调用了constructAPIMappingTable
这些函数是API路由映射表构建函数,用来建立API编号->处理函数的映射关系,实现请求路由的分发

有一些api是直接在datacenter中被处理的,有些是被进一步转发到了/usr/sbin/indexservice(9088端口)处理,另外一些则是被转发到了/usr/sbin/plugincenter(9091端口)中进一步处理
在datacenter::PluginApiCollection::sConstructMappingTable中,当api为629时,对应的handler是callPluginCenter

callPluginCenter
此函数的功能是接受json格式的数据,然后转换成json字符串,通过thrift协议转发到plugincenter服务,获得服务端响应并返回

然后将数据转发给本地的9091端口

接下来分析APIMapping::redirectRequest
这个函数的作用就是解析请求中的API编号,在STL map中查找对应的处理函数,然后通过函数指针动态调用


之前分析的,当api为629时,传入的payload字段的数据会被转发给plugincenter程序处理
搜索datacenter::PluginApiMappingExtendCollection::sConstructMappingTable

这个函数也通过map建立了api编号和handler函数的映射关系
parseGetIdForVendor
会将传入的json数据内的appid字段作为参数传递到PluginApi::getIdForVendor
PluginApi::getIdForVendor
这个函数的功能是验证应用ID的有效性,通过系统命令调用外部工具获取供应商ID,将结果封装为JSON格式返回
1 | __int64 __usercall PluginApi::getIdForVendor@<X0>(__int64 a1@<X1>, __int64 a2@<X8>) |
检查的appid不合法进入if中,没有对appid的内容进行过滤,只是做了一个日志记录,然后创建了一个错误响应,之后会执行下面的危险函数
1 | v13[0] = &v14; |
这里的v13应该被优化了,然后a1是用户可控的appid
之后sCallSystem就是一个封装的函数,使用popen执行命令,因为sCallSystem没有对命令的参数进行安全判断,也没有过滤,所以可以控制appid进行命令执行漏洞

漏洞复现
反弹shell:
https://blog.csdn.net/m0_73610345/article/details/151256959

token值为70776419bb5f6f392634da64b9a901e5
启动以下两个服务
1 | /usr/sbin/datacenter & |
1 | import requests |
结果如下:

// EXTERNAL_CHANNEL
COMMENTS