首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我把 D-Link 老路由的认证 CGI 拆开,栈溢出就藏在 password 拷贝里

我把 D-Link 老路由的认证 CGI 拆开,栈溢出就藏在 password 拷贝里

原创
作者头像
Lethehong
发布于 2026-11-15 15:14:07
发布于 2026-11-15 15:14:07
220
举报
文章被收录于专栏:网络安全网络安全

9.3 分,远程,不用登录,不用受害者点任何东西。这篇带你看清 authenticationcgi_main 里那个 id/password 参数是怎么从 HTTP 请求一路流到栈上的,以及今天你能做什么。

图 1 本文配图:围绕本文主题的氛围配图

先看评分,再看它凭什么拿 9.3

CVE-2026-129353,CVSS 4.0 基础分 9.3,CRITICAL。NVD 收录时间是 2026 年 11 月 15 日,状态标的是 Deferred。

这个分数不是拍脑袋来的。向量里 AV:N 意味着网络可达,你不需要先摸进内网。AC:L 意味着攻击复杂度低,条件稳定,试一次和试一百次没区别。

PR:N 最要命,不需要任何身份。UI:N 意味着不用骗谁去点链接。三个 H 分别是 VC、VI、VA,机密性、完整性、可用性全高影响。

翻译成人话,一台 DIR-600L 只要管理界面在网络上够得着,攻击者不需要账号,不需要交互,一个请求打过去就可能把栈写坏。

但这里我得先泼一盆冷水。CVSS 4.0 的向量里 E:P 表示利用成熟度是概念验证级别,不是「已经在野外大规模打」。NVD 写的是 exploit is now public,公开可用,不等于已经有人拿它扫全网。

这两个判断差很远。前者要求你今天就看资产,后者要求你今天就看资产并且假设已经被打过。我倾向于按后者准备,因为公开 PoC 到批量扫描的窗口,这些年越来越短。

问题不在密码比对,在拷贝之前

官方描述说得很克制,authenticationcgi_main 这个函数,处理 id/password 参数,导致栈缓冲区溢出。

我把这句话拆成三段来读。第一段,入口是 authentication.cgi,一个 CGI 程序。第二段,处理参数的是 authenticationcgi_main。第三段,触发条件是 manipulation of the argument id/password。

这里有个细节值得单独说。它写的是 id/password,中间一个斜杠。我的理解是这两个参数被放在同一个处理路径上,可能是拼在一起做校验,也可能是同一个缓冲区先后接了两个来源。

公开信息没有披露这个斜杠到底是参数名的一部分,还是两个参数并列。这一点我不敢下结论。

但栈溢出这件事的因果链是清楚的。CGI 程序从 HTTP 请求里取参数,取的时候如果用的是 strcpy、sprintf、memcpy 这类不检查目标容量的函数,而目标又是栈上的固定长度数组,长度超过数组边界就会覆盖返回地址或者相邻变量。

为什么会写在认证函数里?因为认证函数是最早接触用户输入的代码。写它的人当时的注意力在「密码对不对」,不在「密码有多长」。

图 2 本文示意图:四层里,最该做长度校验的那一层是空的。

这是嵌入式 Web 管理界面里非常典型的一种错位。校验逻辑和内存安全逻辑被放在了同一个函数里,前者写得很认真,后者根本没人管。

从 HTTP 到栈,中间只隔了一层 CGI

我把这条链路画成四步。浏览器或者 curl 发一个请求到 authentication.cgi,Web 服务器把请求体和查询串交给 CGI 进程,CGI 进程解析出 id 和 password,然后调 authenticationcgi_main。

推演触发条件(示意代码,用伪代码说明长度不检查时溢出怎么发生)

代码语言:txt
复制
/* 推演用,不是真实代码,不构成利用工具 */
#include <string.h>

int authenticationcgi_main(const char *id, const char *password) {
    char stored_pw[64];
    char stored_id[32];

    /* 真实实现里这里大概率是 strcpy 或 sprintf */
    strcpy(stored_id, id);
    strcpy(stored_pw, password);

    /* 只有到这里才开始关心密码对不对 */
    if (check_password(stored_id, stored_pw) == 0) {
        return 200;
    }
    return 401;
}

/* 触发条件,password 长度超过 63 字节且无前置长度检查
   后果,stored_pw 之后的栈内容被覆盖
   能否控制返回地址,取决于编译选项和栈布局,公开信息未披露 */

关键在第三步和第四步之间。解析出来的值,长度完全由请求方决定。CGI 规范本身不限制参数长度,限制来自具体实现里那个接收缓冲区有多大。

如果 authenticationcgi_main 里声明的是 char password[64] 这种栈上数组,而拷贝前没有 strlen 判断,那么第 65 个字节开始就越界。

越界写什么由攻击者决定,这就从「程序崩溃」升级成「可能控制执行流」。CWE-121 是栈缓冲区溢出,CWE-119 是不当的内存缓冲区边界限制,两个分类都指向同一件事。

图 3 本文示意图:从 HTTP 请求到栈溢出,中间只有 CGI 解析和一次无边界拷贝。

为什么以前没人发现?我的推断是,DIR-600L 是偏老的消费级型号,2.17 和 2.17b02 这两个固件版本不是当前主推。研究者的注意力通常在新型号或者有云端管理面的设备上。

这类老固件的漏洞往往是批量挖掘工具扫出来的,不是有人盯着它手工审。这也解释了为什么同一天 NVD 上还出现了 DIR-600L 的另一个洞 CVE-2026-129354,以及 TRENDnet TEW-657BRM 的两个洞。同一批工具,同一批老设备。

今天先做的一件事,把资产摸出来

别急着找 PoC。先搞清楚你手上有没有 DIR-600L,固件是不是 2.17 或 2.17b02。

这两个版本号是官方描述里唯一点名的受影响区间。2.17b02 带 b 后缀,说明是 2.17 的一个修订构建,两个都在影响范围内。

如果你管的是企业网络,消费级路由通常不在正式资产清单里,但总有人插在工位下面当 AP 用。这类影子设备才是重点。

我写了一段自检脚本,思路是从管理面拿版本信息,跟受影响区间比对。它不攻击,只读。

摸清管理面暴露(示意代码,筛出哪些设备的 80 端口对非管理网段可达)

代码语言:txt
复制
#!/bin/bash
# 只做可达性检查,不发送任何攻击载荷
TARGETS="192.168.1.1 192.168.0.1 10.0.0.1"
MGMT_NET="10.10.0.0/24"

for ip in $TARGETS; do
  code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "" 2>/dev/null)
  if [ "$code" = "200" ] || [ "$code" = "401" ]; then
    echo "$ip 管理面可达,HTTP $code"
  else
    echo "$ip 未响应或非管理面"
  fi
done

# 在企业网里,把 TARGETS 换成从 DHCP 租约或 ARP 表导出的网关地址
# 重点看结果里有没有不属于 MGMT_NET 的来源能打到这些地址

够不着管理面,反而是好消息

这个漏洞的前提是 authentication.cgi 能被访问到。DIR-600L 的默认管理地址是内网口,广域网口默认不应该暴露管理界面。

图 4 本文示意图:公网可达加高影响那一格,是今天必须处理的。

所以第一优先级不是打补丁,是确认管理面没有暴露在公网。这一步比升级固件更快,也更有效。

如果你在防火墙或者路由器上做了管理面隔离,只允许特定管理网段访问 80 和 443,那么这个洞对你的实际风险会掉一个数量级。

但注意,这是缓解不是修复。内网里只要有一台机器被控,攻击者横向过来照样能打。而且这类设备的管理密码经常是默认的 admin/admin,或者贴在机身标签上。

过渡办法有三个。第一,把管理面收到单独 VLAN,只允许运维跳板机访问。第二,如果设备支持,关掉远程管理,只留 LAN 侧。第三,实在动不了,就在上游交换机做 ACL,把到设备 80 端口的流量掐掉。

这三个都不如换设备。DIR-600L 是 802.11n 时代的产品,2.17 固件也老了。如果它还在生产环境里承担接入,这件事本身就是个更大的问题。

我没查到的那部分,得说清楚

公开信息没有披露 authenticationcgi_main 里那个栈缓冲区具体多大,也没有披露溢出后能不能稳定控制返回地址。

所以「远程代码执行」这个结论,我只能说到「栈溢出可被远程触发,理论上可导向执行流控制」。再往下就是猜了。

我也没有验证 exploit 的实际可用性。NVD 说公开可用,但我没有拿到具体的 PoC 代码,也没有在真机上跑过。这篇里所有关于触发路径的描述,都是基于官方描述和同类 CGI 漏洞的通用规律做的推断。

还有一个空白,D-Link 有没有发固件更新。NVD 状态是 Deferred,这个状态通常意味着厂商没有回应或者已经停止支持。如果确实没有补丁,那这台设备的处置结论就只剩「下线或者隔离」两条路。

同一天那批 D-Link 和 TRENDnet 的洞,我建议当成一个信号看,不是四个独立事件。老消费级固件正在被批量翻出来,你今天不处理,下个月可能还有新的编号落在同一台设备上。

这类洞的根源,是认证和内存被写在一个函数里

我见过不少嵌入式 Web 管理界面的代码,认证函数的典型写法是先取参数,再比对,比对失败就返回 401。

问题在于「取参数」和「比对」之间没有一道长度闸门。写代码的人默认参数不会太长,因为正常浏览器表单不会发超长密码。

但攻击者不用浏览器。curl 可以发任意长度。这就是为什么 UI:N 在这个场景里成立,不需要骗人,攻击者自己就是用户。

同类实现里,做得好的会在拷贝前做一次长度检查,或者干脆用带边界的函数。做得差的,就是这次这样。

这不是某一个厂商的问题,是一代嵌入式开发习惯的问题。认证逻辑被当成业务逻辑写,内存安全被当成「编译器会管」的事。

所以看到 9.3 分不要只想着这一个 CVE。你手上所有还在跑老固件的网络设备,都值得用同样的眼光过一遍。

写在最后

一个 9.3 分的洞,真正难的不是理解它,是接受你手上还有一台 2013 年的路由在替人上网。今天该做的不是等 PoC,是先确认那台设备的管理面到底谁能摸到。摸不到,你就赢了一半。

以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~

谢谢你看我的文章,我们,下次再见。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 先看评分,再看它凭什么拿 9.3
  • 问题不在密码比对,在拷贝之前
  • 从 HTTP 到栈,中间只隔了一层 CGI
  • 今天先做的一件事,把资产摸出来
  • 够不着管理面,反而是好消息
  • 我没查到的那部分,得说清楚
  • 这类洞的根源,是认证和内存被写在一个函数里
  • 写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档