国内某知名路由器固件存在隐藏后门,可用任意用户名登管理员账户

admin 2026-07-24 04:17:51 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文章披露了某知名路由器固件在Web管理服务器login()函数中存在隐藏后门,攻击者利用配置项sys.rzadmin.password中的隐藏密码,可在username字段填写任意字符串的情况下获取管理员权限。受影响固件包括多个型号版本,后门在管理界面不可见且无法通过修改密码消除。文章提供了关闭远程管理、修改默认IP等缓解措施,并给出了固件资产自动化核查的检测脚本。 综合评分: 90 文章分类: 漏洞分析,逆向分析,安全工具,安全意识,实战经验


cover_image

国内某知名路由器固件存在隐藏后门,可用任意用户名登管理员账户

原创

suntiger suntiger

二进制空间安全

2026年7月20日 15:25 北京

在小说阅读器读本章

去阅读

将二进制空间安全设为”星标⭐️”

第一时间收到文章更新

事件背景

#

根据公开报道(https://kb.cert.org/vuls/id/213560),该路由器厂商至少有5个版本固件在Web管理服务器/bin/httpd的login()函数中,嵌入了未向用户披露的第二条认证通道。攻击者如果掌握配置项sys.rzadmin.password中的隐藏密码,可在 username填写任务字符串的情况下获得role=2的管理员权限,并创建合法session,该步骤与用户自己设置的管理员口令没有任何联系。

对于普通企业和家庭用户而言,该后门在管理界面中完全不可见、关不掉,甚至修改管理员登录密码也无法消除风险,在开启远程Web管理时,攻击暴露面将从局域网扩散到互联网,高风险大幅度增加。

#

login()双路径认证架构

 根据公开信息, 该后门位于Web服务器二进制/bin/httpd的login()函数,当用户访问路由器管理页面,例如:192.168.0.1, 提交用户名和密码后,login()函数理论上会走合规正常流程,例如:

用户 password    │    ▼PasswordToMd5()          ← MD5 哈希    │    ▼check_rand_key()         ← 随机数/会话密钥校验    │    ▼prod_encode64()          ← 与存储值比对    │    ▼成功 → 创建 Session → 分配 role

但如果上述正常验证流程失败,函数并不总是返回密码错误,而是进入隐藏分支:

if (normal_auth_failed) {    char backdoor_pwd[256];    GetValue("sys.rzadmin.password", backdoor_pwd);
    if (strcmp(backdoor_pwd, user_supplied_password) == 0) {        /* username 在此分支不参与校验 */        session.role = 2;   /* 管理员 */        create_valid_session();        return SUCCESS;    }}return AUTH_FAILED;

当攻击者拿到role=2的管理员账号权限后,可以更改路由器的DNS、防火墙、端口转发、无线密钥等, 为进一步进行横向渗透提供了有利条件。

受影响固件与暴露面评估

目前已经确认的固件版本包括:

| | | | — | — | | 型号系列 | 固件版本字符串 | | FH1201 | US_FH1201V1.0BR_V1.2.0.14(408)_EN_TD | | W15E | US_W15EV1.0br_V15.11.0.5(1068_1567_841)_EN_TDE | | AC10 | US_AC10V1.0re_V15.03.06.46_multi_TDE01 | | AC5 | US_AC5V1.0RTL_V15.03.06.48_multi_TDE01 | | AC6 V2 | US_AC6V2.0RTL_V15.03.06.51_multi_T |

需要注意的是,上面未被列出的固件并不能视为安全,同厂商/bin/httpd代码树可能在其它区域版本复用。

根据该漏洞形成原理,概念型攻击链流程如下:

(1).通过固件逆向、配置提取、内部泄露等方式,获取到目标版本固件的隐藏密码。

(2).访问https:///管理口路径。

(3).POST login:username=任意,password=<隐藏密码>。

(4).获取role=2管理员权限Session,登录后获取Web管理权限。

企业分层防御与缓解措施

在无官方补丁的前提下,企业策略必须区分缓解和根治,如果要根治,目前只能依赖换硬件或等待厂商固件更新。官方推荐的缓解措施主要包括:关闭远程Web管理和修改默认LAN IP。目的是阻断WAN侧利用以及降低对192.168.0.1的扫描命中。

在网络层之外,安全团队可在现有的SIEM/NDR能力之上叠加以下低误报导向的检测线索,主要包括:

(1).同一源IP对管理口多次失败后单次成功。

(2).非变更窗口的DNS服务器修改、静态路由、DMZ、端口转发新增。

(3).无线SSID密码或WPS状态异常变更。

(4).管理Session创建时间与已知变更工单不匹配。

企业对自身资产隐患进行排查阶段性工作可按以下方案进行:

阶段一:资产盘点

该阶段目标是把”可能受影响”变成可枚举资产列表,动作包括:从CMDB、DHCP指纹、网关ARP表收集Tenda设备,登录管理页收集完整固件版本字符串,标记是否曾开启远程Web管理、是否使用默认192.168.0.1。最终收集完成三项物料清单,分别为:受影响/待确认/不受影响,以及责任人。

阶段二:暴露面收敛

对已命中或待确认设备:立即关闭远程管理;将管理口迁入独立 VLAN,ACL 仅允许 jump host / 运维网段;修改LAN网关地址降低扫描噪音;对解包得到的 /bin/httpd 运行strings特征扫描

阶段三:根治与持续运营

根治路径只有更换已确认不受影响设备应用厂商修复固件,持续运营建议:新采购路由器纳入固件strings抽检;禁止Internet-facing远程Web管理写入基线;关注官方补丁,

务必对 /bin/httpd 做 bindiff,确认 login() 中 sys.rzadmin.password 分支已移除,而非仅改默认密码值。

固件与资产自动化核查

安全团队从设备或官网下载固件,解包得到/bin/httpd,需批量筛查是否包含rzadmin配置键,运行实例如下:

cd&nbsp;tendaBackdoorArticle/srcpython tenda_backdoor_assessor.py --scan-firmware ./firmware_samples/httpd

预期扫描出结果如下:

=== Tenda CVE-2026-11405 Assessment: FAIL ===Target: firmware_samples/httpd
[1] [CRITICAL] backdoor_indicator: 检测到后门配置键 sys.rzadmin.password(httpd)&nbsp; &nbsp; → 标记为 CVE-2026-11405 疑似受影响固件,禁止上线并计划换机

在内网盘点时确认网关管理口是否可从运维网段意外暴露,并尝试从 HTTP 首页提取固件版本线索。

python&nbsp;tenda_backdoor_assessor.py --probe&nbsp;192.168.0.1:80

预期输出结果如下:

[1]&nbsp;[MEDIUM]&nbsp;exposure: 管理口 TCP&nbsp;80&nbsp;可达&nbsp; &nbsp; → 确认是否应从WAN暴露;建议关闭远程Web管理

检测脚本代码如下:

#!/usr/bin/env python3# -*- coding: utf-8 -*-"""Tenda CVE-2026-11405 固件与资产自动化核查工具(防御向)功能:&nbsp; 1. 固件二进制 strings 扫描 — 检测 sys.rzadmin.password 等后门特征&nbsp; 2. 受影响固件版本字符串匹配&nbsp; 3. 路由器管理口暴露面探测(HTTP 可达性,不尝试后门密码)&nbsp; 4. 综合 JSON 报告 + 控制台 PASS/FAIL用法:&nbsp; python tenda_backdoor_assessor.py --config routers.yaml&nbsp; python tenda_backdoor_assessor.py --scan-firmware ./firmware_samples/httpd&nbsp; python tenda_backdoor_assessor.py --probe 192.168.0.1:80"""from&nbsp;__future__&nbsp;import&nbsp;annotationsimport&nbsp;argparseimport&nbsp;jsonimport&nbsp;reimport&nbsp;socketimport&nbsp;sysfrom&nbsp;dataclasses&nbsp;import&nbsp;asdict, dataclass, fieldfrom&nbsp;pathlib&nbsp;import&nbsp;Pathfrom&nbsp;typing&nbsp;import&nbsp;Anyfrom&nbsp;urllib.error&nbsp;import&nbsp;URLErrorfrom&nbsp;urllib.request&nbsp;import&nbsp;Request, urlopentry:&nbsp; &nbsp;&nbsp;import&nbsp;yamlexcept&nbsp;ImportError:&nbsp; &nbsp; yaml =&nbsp;None&nbsp;&nbsp;# type: ignoreDEFAULT_INDICATORS = (&nbsp; &nbsp;&nbsp;"sys.rzadmin.password",&nbsp; &nbsp;&nbsp;"rzadmin",&nbsp; &nbsp;&nbsp;"GetValue",&nbsp; &nbsp;&nbsp;"PasswordToMd5",&nbsp; &nbsp;&nbsp;"check_rand_key",&nbsp; &nbsp;&nbsp;"prod_encode64",)DEFAULT_AFFECTED_FIRMWARE = [&nbsp; &nbsp;&nbsp;"US_FH1201V1.0BR_V1.2.0.14(408)_EN_TD",&nbsp; &nbsp;&nbsp;"US_W15EV1.0br_V15.11.0.5(1068_1567_841)_EN_TDE",&nbsp; &nbsp;&nbsp;"US_AC10V1.0re_V15.03.06.46_multi_TDE01",&nbsp; &nbsp;&nbsp;"US_AC5V1.0RTL_V15.03.06.48_multi_TDE01",&nbsp; &nbsp;&nbsp;"US_AC6V2.0RTL_V15.03.06.51_multi_T",]FIRMWARE_VERSION_RE = re.compile(&nbsp; &nbsp;&nbsp;r"US_(?:FH1201|W15E|AC10|AC5|AC6)[^\s\"']{8,80}",&nbsp; &nbsp; re.IGNORECASE,)@dataclassclass&nbsp;Finding:&nbsp; &nbsp; category:&nbsp;str&nbsp; &nbsp; severity:&nbsp;str&nbsp;&nbsp;# CRITICAL | HIGH | MEDIUM | LOW&nbsp; &nbsp; message:&nbsp;str&nbsp; &nbsp; recommendation:&nbsp;str&nbsp;=&nbsp;""&nbsp; &nbsp; context:&nbsp;dict[str,&nbsp;Any] = field(default_factory=dict)@dataclassclass&nbsp;AssessmentReport:&nbsp; &nbsp; target:&nbsp;str&nbsp; &nbsp; findings:&nbsp;list[Finding] = field(default_factory=list)&nbsp; &nbsp; passed:&nbsp;bool&nbsp;=&nbsp;True&nbsp; &nbsp;&nbsp;def&nbsp;add(self, finding: Finding) ->&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.findings.append(finding)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;finding.severity&nbsp;in&nbsp;("CRITICAL",&nbsp;"HIGH",&nbsp;"MEDIUM"):&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;self.passed =&nbsp;Falsedef&nbsp;load_config(path: Path) ->&nbsp;dict[str,&nbsp;Any]:&nbsp; &nbsp;&nbsp;if&nbsp;yaml&nbsp;is&nbsp;None:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit("需要 PyYAML: pip install pyyaml")&nbsp; &nbsp;&nbsp;with&nbsp;path.open(encoding="utf-8")&nbsp;as&nbsp;f:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;yaml.safe_load(f)&nbsp;or&nbsp;{}def&nbsp;extract_strings(data:&nbsp;bytes, min_len:&nbsp;int&nbsp;=&nbsp;6) ->&nbsp;list[str]:&nbsp; &nbsp;&nbsp;"""从二进制中提取可打印 ASCII 字符串。"""&nbsp; &nbsp; pattern = re.compile(rb"[\x20-\x7e]{%d,}"&nbsp;% min_len)&nbsp; &nbsp;&nbsp;return&nbsp;[m.group().decode("ascii", errors="ignore")&nbsp;for&nbsp;m&nbsp;in&nbsp;pattern.finditer(data)]def&nbsp;scan_firmware_file(path: Path, indicators:&nbsp;tuple[str, ...]) -> AssessmentReport:&nbsp; &nbsp; report = AssessmentReport(target=str(path))&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; data = path.read_bytes()&nbsp; &nbsp;&nbsp;except&nbsp;OSError&nbsp;as&nbsp;exc:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="firmware_io",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="HIGH",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"无法读取固件文件:&nbsp;{exc}",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="确认路径与文件权限",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;report&nbsp; &nbsp; strings = extract_strings(data)&nbsp; &nbsp; joined =&nbsp;"\n".join(strings)&nbsp; &nbsp; hits = [s&nbsp;for&nbsp;s&nbsp;in&nbsp;indicators&nbsp;if&nbsp;s&nbsp;in&nbsp;joined]&nbsp; &nbsp;&nbsp;if&nbsp;"sys.rzadmin.password"&nbsp;in&nbsp;hits:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="backdoor_indicator",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="CRITICAL",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"检测到后门配置键 sys.rzadmin.password({path.name})",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="标记为 CVE-2026-11405 疑似受影响固件,禁止上线并计划换机",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context={"matched_indicators": hits},&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;elif&nbsp;len(hits) >=&nbsp;3:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="backdoor_indicator",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="HIGH",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"检测到&nbsp;{len(hits)}&nbsp;个 login() 相关特征字符串",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="对 /bin/httpd 做 bindiff 或逆向确认是否存在隐藏认证分支",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context={"matched_indicators": hits},&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;for&nbsp;ver&nbsp;in&nbsp;FIRMWARE_VERSION_RE.findall(joined):&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="firmware_version",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="MEDIUM",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"固件内嵌版本字符串:&nbsp;{ver}",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="与 CERT/CC 受影响列表交叉核对",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context={"version_string": ver},&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;report.findings:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="backdoor_indicator",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="LOW",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message="未命中已知后门特征(不代表安全,可能为不同 SDK 分支)",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="结合设备型号与供应链信息继续排查",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;return&nbsp;reportdef&nbsp;scan_firmware_dirs(dirs:&nbsp;list[str], indicators:&nbsp;tuple[str, ...]) -> AssessmentReport:&nbsp; &nbsp; report = AssessmentReport(target="firmware_batch")&nbsp; &nbsp; paths:&nbsp;list[Path] = []&nbsp; &nbsp;&nbsp;for&nbsp;d&nbsp;in&nbsp;dirs:&nbsp; &nbsp; &nbsp; &nbsp; base = Path(d)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;base.exists():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="firmware_io",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="MEDIUM",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"固件目录不存在:&nbsp;{base}",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="创建目录并放入待测 httpd 或完整固件镜像",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; paths.extend(p&nbsp;for&nbsp;p&nbsp;in&nbsp;base.rglob("*")&nbsp;if&nbsp;p.is_file())&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;paths:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="firmware_io",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="MEDIUM",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message="固件目录为空,跳过 strings 扫描",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="将 /bin/httpd 或 squashfs 解包文件放入 firmware_scan_dirs",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;report&nbsp; &nbsp;&nbsp;for&nbsp;path&nbsp;in&nbsp;paths:&nbsp; &nbsp; &nbsp; &nbsp; sub = scan_firmware_file(path, indicators)&nbsp; &nbsp; &nbsp; &nbsp; report.findings.extend(sub.findings)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;sub.passed:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; report.passed =&nbsp;False&nbsp; &nbsp;&nbsp;return&nbsp;reportdef&nbsp;match_affected_firmware(version:&nbsp;str, affected:&nbsp;list[str]) ->&nbsp;bool:&nbsp; &nbsp; version_norm = version.strip()&nbsp; &nbsp;&nbsp;return&nbsp;any(version_norm == item&nbsp;or&nbsp;item&nbsp;in&nbsp;version_norm&nbsp;for&nbsp;item&nbsp;in&nbsp;affected)def&nbsp;probe_mgmt_port(host:&nbsp;str, port:&nbsp;int, timeout:&nbsp;float&nbsp;=&nbsp;3.0) ->&nbsp;dict[str,&nbsp;Any]:&nbsp; &nbsp;&nbsp;"""探测管理口 TCP 可达性与 HTTP 响应头(不提交登录表单)。"""&nbsp; &nbsp; result:&nbsp;dict[str,&nbsp;Any] = {"host": host,&nbsp;"port": port,&nbsp;"tcp_open":&nbsp;False,&nbsp;"http_status":&nbsp;None}&nbsp; &nbsp; sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)&nbsp; &nbsp; sock.settimeout(timeout)&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; sock.connect((host, port))&nbsp; &nbsp; &nbsp; &nbsp; result["tcp_open"] =&nbsp;True&nbsp; &nbsp;&nbsp;except&nbsp;OSError:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;result&nbsp; &nbsp;&nbsp;finally:&nbsp; &nbsp; &nbsp; &nbsp; sock.close()&nbsp; &nbsp; scheme =&nbsp;"https"&nbsp;if&nbsp;port ==&nbsp;443&nbsp;else&nbsp;"http"&nbsp; &nbsp; url =&nbsp;f"{scheme}://{host}:{port}/"&nbsp; &nbsp;&nbsp;try:&nbsp; &nbsp; &nbsp; &nbsp; req = Request(url, headers={"User-Agent":&nbsp;"TendaAssessor/1.0 (defensive inventory)"})&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;with&nbsp;urlopen(req, timeout=timeout)&nbsp;as&nbsp;resp:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result["http_status"] = resp.status&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; body = resp.read(8192).decode("utf-8", errors="ignore")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;"tenda"&nbsp;in&nbsp;body.lower():&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result["vendor_hint"] =&nbsp;"Tenda-like UI detected"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fw = re.search(r"(?:firmware|version)[^<]{0,40}([0-9A-Za-z_().]+)", body, re.I)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;fw:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result["firmware_guess"] = fw.group(1)[:80]&nbsp; &nbsp;&nbsp;except&nbsp;URLError&nbsp;as&nbsp;exc:&nbsp; &nbsp; &nbsp; &nbsp; result["http_error"] =&nbsp;str(exc.reason)&nbsp;if&nbsp;hasattr(exc,&nbsp;"reason")&nbsp;else&nbsp;str(exc)&nbsp; &nbsp;&nbsp;return&nbsp;resultdef&nbsp;assess_router_target(&nbsp; &nbsp; host:&nbsp;str,&nbsp; &nbsp; port:&nbsp;int,&nbsp; &nbsp; affected:&nbsp;list[str],&nbsp; &nbsp; expect_remote:&nbsp;bool,) -> AssessmentReport:&nbsp; &nbsp; report = AssessmentReport(target=f"{host}:{port}")&nbsp; &nbsp; probe = probe_mgmt_port(host, port)&nbsp; &nbsp;&nbsp;if&nbsp;probe.get("tcp_open"):&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="exposure",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="MEDIUM"&nbsp;if&nbsp;not&nbsp;expect_remote&nbsp;else&nbsp;"HIGH",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"管理口 TCP&nbsp;{port}&nbsp;可达",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="确认是否应从 WAN 暴露;CERT/CC 建议关闭远程 Web 管理",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context=probe,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="exposure",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="LOW",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"管理口&nbsp;{host}:{port}&nbsp;不可达(可能离线或非 Tenda)",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context=probe,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;report&nbsp; &nbsp; fw_guess = probe.get("firmware_guess",&nbsp;"")&nbsp; &nbsp;&nbsp;if&nbsp;fw_guess&nbsp;and&nbsp;match_affected_firmware(fw_guess, affected):&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="affected_firmware",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="CRITICAL",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message=f"管理页疑似匹配受影响固件:&nbsp;{fw_guess}",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="立即关闭远程管理、隔离 VLAN,计划换机或等待官方补丁",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; context={"firmware_guess": fw_guess},&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;if&nbsp;expect_remote:&nbsp; &nbsp; &nbsp; &nbsp; report.add(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Finding(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; category="exposure",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; severity="CRITICAL",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; message="配置标记 expect_remote_mgmt=true — WAN 侧管理口暴露",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recommendation="关闭远程 Web 管理并修改默认 LAN IP(CERT/CC 官方缓解)",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp; &nbsp; &nbsp; )&nbsp; &nbsp;&nbsp;return&nbsp;reportdef&nbsp;run_full_assessment(config_path: Path) -> AssessmentReport:&nbsp; &nbsp; cfg = load_config(config_path)&nbsp; &nbsp; affected =&nbsp;list(cfg.get("affected_firmware", []))&nbsp; &nbsp; indicators =&nbsp;tuple(cfg.get("backdoor_indicators", DEFAULT_INDICATORS))&nbsp; &nbsp; report = AssessmentReport(target=str(config_path))&nbsp; &nbsp; fw_report = scan_firmware_dirs(cfg.get("firmware_scan_dirs", []), indicators)&nbsp; &nbsp; report.findings.extend(fw_report.findings)&nbsp; &nbsp;&nbsp;for&nbsp;item&nbsp;in&nbsp;cfg.get("router_targets", []):&nbsp; &nbsp; &nbsp; &nbsp; host = item.get("host",&nbsp;"")&nbsp; &nbsp; &nbsp; &nbsp; port =&nbsp;int(item.get("mgmt_port",&nbsp;80))&nbsp; &nbsp; &nbsp; &nbsp; expect =&nbsp;bool(item.get("expect_remote_mgmt",&nbsp;False))&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;host:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; &nbsp; &nbsp; sub = assess_router_target(host, port, affected, expect)&nbsp; &nbsp; &nbsp; &nbsp; report.findings.extend(sub.findings)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;sub.passed:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; report.passed =&nbsp;False&nbsp; &nbsp;&nbsp;if&nbsp;fw_report.passed&nbsp;and&nbsp;all(&nbsp; &nbsp; &nbsp; &nbsp; f.severity&nbsp;not&nbsp;in&nbsp;("CRITICAL",&nbsp;"HIGH",&nbsp;"MEDIUM")&nbsp;for&nbsp;f&nbsp;in&nbsp;report.findings&nbsp; &nbsp; ):&nbsp; &nbsp; &nbsp; &nbsp; report.passed =&nbsp;True&nbsp; &nbsp;&nbsp;return&nbsp;reportdef&nbsp;print_report(report: AssessmentReport) ->&nbsp;None:&nbsp; &nbsp; status =&nbsp;"PASS"&nbsp;if&nbsp;report.passed&nbsp;else&nbsp;"FAIL"&nbsp; &nbsp;&nbsp;print(f"\n=== Tenda CVE-2026-11405 Assessment:&nbsp;{status}&nbsp;===")&nbsp; &nbsp;&nbsp;print(f"Target:&nbsp;{report.target}\n")&nbsp; &nbsp;&nbsp;for&nbsp;i, f&nbsp;in&nbsp;enumerate(report.findings,&nbsp;1):&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"[{i}] [{f.severity}]&nbsp;{f.category}:&nbsp;{f.message}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;f.recommendation:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f" &nbsp; &nbsp;→&nbsp;{f.recommendation}")&nbsp; &nbsp;&nbsp;print()def&nbsp;main() ->&nbsp;int:&nbsp; &nbsp; parser = argparse.ArgumentParser(description="Tenda CVE-2026-11405 防御向核查工具")&nbsp; &nbsp; parser.add_argument("--config",&nbsp;type=Path,&nbsp;help="YAML 配置文件路径")&nbsp; &nbsp; parser.add_argument("--scan-firmware",&nbsp;type=Path,&nbsp;help="单个固件/httpd 文件 strings 扫描")&nbsp; &nbsp; parser.add_argument("--probe",&nbsp;help="探测管理口 host:port,例 192.168.0.1:80")&nbsp; &nbsp; parser.add_argument("--json-out",&nbsp;type=Path,&nbsp;help="输出 JSON 报告路径")&nbsp; &nbsp; args = parser.parse_args()&nbsp; &nbsp;&nbsp;if&nbsp;args.scan_firmware:&nbsp; &nbsp; &nbsp; &nbsp; report = scan_firmware_file(args.scan_firmware, DEFAULT_INDICATORS)&nbsp; &nbsp;&nbsp;elif&nbsp;args.probe:&nbsp; &nbsp; &nbsp; &nbsp; host, _, port_str = args.probe.partition(":")&nbsp; &nbsp; &nbsp; &nbsp; port =&nbsp;int(port_str&nbsp;or&nbsp;"80")&nbsp; &nbsp; &nbsp; &nbsp; report = assess_router_target(host, port, DEFAULT_AFFECTED_FIRMWARE,&nbsp;False)&nbsp; &nbsp;&nbsp;elif&nbsp;args.config:&nbsp; &nbsp; &nbsp; &nbsp; report = run_full_assessment(args.config)&nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; parser.print_help()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1&nbsp; &nbsp; print_report(report)&nbsp; &nbsp;&nbsp;if&nbsp;args.json_out:&nbsp; &nbsp; &nbsp; &nbsp; payload = {"passed": report.passed,&nbsp;"target": report.target,&nbsp;"findings": [asdict(f)&nbsp;for&nbsp;f&nbsp;in&nbsp;report.findings]}&nbsp; &nbsp; &nbsp; &nbsp; args.json_out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"JSON report written to&nbsp;{args.json_out}")&nbsp; &nbsp;&nbsp;return&nbsp;0&nbsp;if&nbsp;report.passed&nbsp;else&nbsp;2if&nbsp;__name__ ==&nbsp;"__main__":&nbsp; &nbsp; sys.exit(main())

(全文完)


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:二进制空间安全 suntiger suntiger《国内某知名路由器固件存在隐藏后门,可用任意用户名登管理员账户》

    安永数据泄露事件 网络安全文章

    安永数据泄露事件

    文章总结: 安永律师事务所(EY)在2026年春季遭遇数据泄露,未经授权第三方通过其IT支持工单平台在3月28日至4月12日期间下载了包含客户税务数据的文件,4
    评论:0   参与:  0