一个请求埋雷,另一个请求引爆:固件里最阴的那种洞"

admin 2026-11-10 16:47:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

一个请求埋雷,另一个请求引爆:固件里最阴的那种洞

原创

升斗安全XiuXiu
升斗安全XiuXiu

升斗安全

2026年11月9日 20:04
广东

去阅读

【文章说明】

  • 目的:本文内容仅为网络安全技术研究与教育目的而创作。
  • 红线:严禁将本文知识用于任何未授权的非法活动。使用者必须遵守《网络安全法》等相关法律。
  • 责任:任何对本文技术的滥用所引发的后果自负,与本公众号及作者无关。
  • 免责:内容仅供参考,作者不对其准确性、完整性作任何担保。

阅读即代表您同意以上条款。

一句话导读:最近翻到一套开源的 IoT 固件审计思路,把我这些年踩过的坑基本串起来了。这篇不聊玄的,就讲三件事——路由器里那种「两个请求合起来才成立」的洞长什么样、为什么把固件直接丢给大模型交不出能提交的报告、以及一套能让结论「被别人复核、被机器否决」的流程该怎么搭。没拆过路由器也能看懂,挖过 SRC 的应该会更有感觉。


前几年带新人,有个场景我见过太多次。

小伙子把固件解开,翻了两天 Web 目录,跑回来跟我说:师傅,这机器干净,CGI 里该做的校验都做了。我让他把 rc 那条链子再顺一遍,他愣了半天——他压根没往那边看。

后来那台设备最后怎么收的尾,就不展开了。只说结论:问题不在某一行代码写错了,而在两个入口之间,没人交代清楚那句话是谁塞进去的。

干了十来年渗透,我越来越觉得,固件这块最值钱的洞往往不长得像漏洞。它不会在某个 system() 调用旁边插个牌子写「我这里有命令注入」,它藏在你以为「只是设备自己的配置」的地方。

最近看到看雪学苑那篇开源 Skill 的设计(作者「记忆里的纯真」),把这个事儿讲得挺透,还顺手把一篇顶会论文的数据跑了一遍、把自己文档里的一条判据给改了。我读完有点感触,索性结合自己这些年的经历,把它拆开聊一遍。

一个请求埋雷,另一个请求引爆

先把那个最反直觉的洞摆出来。

以 D-Link DIR882 这类家用路由器为例,它的 Web 后台不是「一个请求干完一件事」那么简单。前台有个 prog.cgi 负责接请求、按动作名分发,比如 SetNetwork、SetVLAN;真要改系统状态了,它会通过 notify\_rc 发一条进程间消息,把活儿交给后台的 rc 进程。

于是链子可以是这样两步:

请求 1  SetNetwork → nvram_safe_set(”lan0_management_link”, <用户能控的内容>)请求 2  SetVLAN    → notify_rc(”start_vlanall”) → rc 进程                   → nvram_safe_get(”lan0_management_link”) → system(buf)

现在你扮审核员,一次只看一步。

只看请求 1:哦,往 nvram 里存了个配置,没调危险函数,过。只看请求 2:哦,数据是从 nvram 读出来的,设备自己的配置,又不是用户直接传进来的参数,也过。

两边都没毛病,链子却是通的。

打个不那么严谨但好懂的比方:甲往办公桌抽屉里塞了张纸条,乙过会儿按纸条上写的去办了事。你查甲,他只是「存了张纸条」;你查乙,他只是「照章办事」。要抓这个,得盯着抽屉问一句:这张纸条最早是谁写的?

这类洞有个特别干脆的判定口径:写它的那个请求,和触发读的那个请求,不是同一个。论文里管它叫「高阶污点」,说白了就是跨请求的接力。

我为什么反复跟新人念叨这个?因为它在 SRC 里最容易被漏,也最容易被误判。你按单请求写报告,审核那边一句「数据来源为内部配置,不可控」就给你打回来了;你不死心补一句「但是前面能写」,又拿不出写点和读点之间的对应关系,照样是无效。不是洞不成立,是你没把它讲成立。

把固件丢给 AI,最容易翻在哪儿

现在大模型这么猛,很多人第一反应是:把固件丢进去,说一句「帮我找漏洞」,等着收报告。

这事我试过,也看不少团队试过。结果基本稳定复现两类翻车,巧了,正好都不是「模型算错了」,而是流程给了错误信号。

第一种叫假装覆盖。模型会非常自然地说「已完成全量审计」。但你去看它实际读过哪些文件,会发现非常规格式的那部分被悄悄跳过了——UBIFS 不是 squashfs、MIPS16e 的指令、厂商自己糊的 Web 框架,工具链在某一环卡住,卡住之前的结论看起来还挺完整。人审最怕这个:不是报告里有个错,是你根本不知道它哪儿没看。

第二种更常见,拿 CVE 版本号当漏洞。busybox 1.31 一扫命中几十条 CVE,模型兴冲冲列进「漏洞详情」。可这个镜像可能把相关 applet 裁掉了,可能厂商已经 backport 过补丁,也可能那条 CVE 需要一个编译期根本没打开的选项才触发。

对挖 SRC 的人来说,第二条是致命的。你交上去,审核一眼看穿,判无效都是轻的,连着几回,账号在平台那边的信用就不好看了。我早年吃过这个亏,后来立了条规矩:报告里每一条「漏洞」,都必须能回答「我怎么把它跑出来的」,答不上来就挪到「组件风险」那一栏,别混进统计表。

所以那套 Skill 的设计重心挺对我胃口——它不是想办法让模型「想得更多」,是给结论套约束。作者那句话我抄下来贴过:报告的措辞不能成为证据。

老审计员的「骨架」,其实是一堆工地规矩

那到底该怎么组织?我按自己的理解翻译一遍,尽量不说黑话。

每一步都得留下单据。

整套流程从 Phase 0 排到 Phase 5:先量一量这个固件到底是什么、边界在哪;再解包侦察;然后给文件分层、排谁先读谁后读;中间专门有个环节做「覆盖矩阵」,把打算看的地方先列出来、看完逐项打勾;接着才是三层审计;再对每条候选做动态验证;最后过门禁、出报告。

关键在于:每一阶段的中间结果都落成文件,JSON 也好、Markdown 也好,不放在模型的「记忆」里。跑一半崩了,从产物接着跑;几个 Agent 分工,交接靠文件,不靠「我上一轮跟你说过」。

这听着像废话,但真踩过坑的都懂。我以前接手别人半截的项目,最怕的就是对方一句「我记得当时看过」,然后什么都对不上。

估工作量别看镜像多大。

一个 40MB 的固件,里头可能有 30MB 是证书、字体和多语言包。所以人家不用文件大小,用 ELF 的 .text 段大小来估「要读多少代码」,脚本和配置文件按行数折算(大概 40 行算 1KB 的认知量),再乘上一个优先级权重:监听服务、cgi-bin/、协议解析器、etc/init.d/、\*.sh、nvram 默认值这些给满权重,.so、.ko、非监听守护进程减半,文档字体静态资源直接跳过。有个细节我挺喜欢:没匹配上任何规则的文件,保守归到中等优先级,绝不降级——宁可多读,不能漏。

算出来的数拿去排序、切分几个 Agent 并行干,不拿来当工期承诺。作者还老老实实标了,这个估算目前只是启发式,真实工时样本攒够 20 条才算「实验级」,攒够 60 条且误差压到 0.30 以内才敢叫「已校准」。写这么一句,比满屏「智能估算」让人踏实多了。

三层分工,别指望一层包打天下。

第一层不给模型,正则加结构级扫描(ast-grep)把全域过一遍,出一份候选清单。但它只是参考,不是唯一入口——真读代码的还得逐个文件看过去,不然那些「工具认不出来」的逻辑洞会被成片漏掉,越权、认证绕过基本都属于这类。

第二层才是模型主场,三条道同时走:

  • 从危险调用往回追,看数据从哪儿来(Sink 驱动);
  • 从入口往下走,查「本该有的那道检查在不在」(Control 驱动);
  • 专门盯前面说的跨请求接力。

第二条我要单独拎出来说一句。认证绕过、OTA 不验签、越权改配置,这些洞不是「某行代码有问题」,是「某个入口少了检查」。你从system()往回追,一辈子也追不到它,因为压根没有那个危险调用可追。新人做固件审计,八成时间都花在第一条道上,漏的就全是这一挂。

第三层做语义验证,用 LSP 或者反编译交叉确认数据到底走不走得到。工具没有怎么办?允许退化成 grep 加人工追,但必须在报告里写明「这里降级了」。这一条我觉得比技术本身值钱:能力可以打折,交代不能省。

「审过了」三个字,凭什么信

自动化审计最阴的坑,是它会在你最放心的时候给你亮绿灯。原文列了三种,我按自己的话重讲一遍。

空集通过。「漏洞详情」整节压根没写,字段校验器遍历 0 个条目,返回「全部完整」——0 条当然条条完整。修法土得可爱:先判这一节在不在,缺了直接判失败。

没跑算通过。入口级覆盖、绕过矩阵、跨请求溯源这些高级检查,只在启用时才进判据表。结果 exit 0 一回来,看起来像全过了,其实其中几项根本没执行。修法是把判据从「过/不过」改成四态:PASS / FAIL / WARN / SKIPPED。SKIPPED 明明白白写着「这次没跑」,严格档位下,必需项只要 SKIPPED,直接判失败。同时落一份 gate-config.json,把档位、门禁版本、各项阈值、每项检查状态、以及所有输入文件的 sha256都记下来——换台机器能复算同一次判定。

版本命中当漏洞。就是前面 busybox 那事儿。所以 CVE 被拆成三态:cve-hit(只是版本落在区间里)、cve-reachable(定位到受影响函数,而且确认可达)、cve-exploitable(动态复现过了)。门禁会盯着几类违规:把版本命中写成漏洞断言的、把没验证的组件 CVE 塞进「漏洞统计」的、同一条 CVE 在「组件风险」和「漏洞详情」里同时出现的(该移动,不该复制)。

七道门禁从头到尾只读纯文本和 JSON,不读模型的自述。说白了,门禁不关心你报告写得多漂亮,它只认有没有那份证据文件。

最精彩的一段:数据把作者自己的判据打脸了

这部分是我读得最过瘾的,也顺便回答一个问题:这类跨请求的洞,学术界是怎么找的。

IEEE S&P 2026 有篇叫 Bridge 的论文,思路拆成三步。先找出前端的动作入口(AEH)和真正解析用户输入的那些函数(IPF),识别阈值不是拍脑袋写的,是从代码里数出来的——比如某个基本块入度 ≥10 就当汇聚点、同一个函数里 strcmp 被调用 ≥10 次、某个函数被调用 ≥100 次且常量实参占比 ≥2/3,等等。然后画两张图:一张 BDG 追数据怎么从写点流到读点(同一套污点引擎跑两遍,只是把源和汇对调,最后按 sink 地址反查存储键拼起来);一张 ADG 跨二进制追控制流,补上「到底是哪个前端动作,触发了后台那次读」。控制值实在恢复不出来的,扔给欠约束符号执行,单次 600 秒超时,超时就丢,绝不拿半截结果凑数。

口径上,论文在 44 个固件里报了 1168 个真阳性,其中 566 个是高阶。

听着很顺,对吧。有意思的来了。

那位作者没停留在「我读懂了」,他在一台 4 核 / 15GB 的 Ubuntu 上,把论文公开的镜像真跑了一遍(DIR878 的 MIPS32 样本)。前端动作名匹配 128/152,结果文件里 1188 个键,其中 35 个带着 other\_get——意思是「读发生在另一个二进制里」。动机样例的输出跟论文给的样例逐字一致。

按原文档那条判据,这 35 个都该算高阶。可他翻着翻着发现不对:SysLogRemote\_IPAddress 这条同样带 other\_get,读点同样在 rc,但写它和触发读的都是SetSysLogSettings同一个动作。一个请求自己写完自己触发,这是一阶,不是接力。

根子在哪儿?后台的读点几乎总在 rc / librcm.so 里,从来就不在边界二进制 prog.cgi 里。所以「读发生在别的进程」这事儿,对所有洞都成立,根本不能拿来当高阶的证据。原来的文档把「读点宿主」当成了「读的触发动作」,这一混,所有一阶都会被算成高阶。

改完之后的链子长这样,是个五元组:

write.action --key--> 存储 --key--> read.trigger_action                                   └─ via(控制值) → read.binary:addr → sink

触发动作来自 ADG(前端那张图),读点地址来自 BDG(进程那张图)。两个都写清楚,一阶和高阶才不会搅在一起。

我盯着这段看了两遍。这种错,你把论文读一百遍也读不出来,只有真把数据跑一遍它才会自己冒出来。这些年我审新人报告,最常问的不是「你结论对不对」,是「你这个结论凭什么成立、换个样本还成立吗」。很多看起来特别通顺的推理,一上真数据就散架。愿意把自己刚写好的判据当场推翻,这比多报十个洞更让我信得过。

顺带说,论文那套实现吃 IDA Pro + angr + 40 核,普通机器跑不动。所以这个 Skill 没去复刻工具链,只把判据落了地:一张 assets/ipc-functions.json 存十类 IPC 机制的读写原语、键参数位置、阈值和 sink 分类;一个 scripts/taint\_graph.py 负责从反编译伪代码恢复常量键(四条确定性规则,拼不出来就老实记 unknown,不做概率猜测)、再把写点读点触发动作控制值和 sink 组成图,按 C1–C5 校验,产出 taint-graph.json;门禁的 G7 直接吃这张图,strict 档下报告要是声称有高阶漏洞却拿不出图,直接拦掉。

连文档自己都得被测一遍

还有个细节,我觉得是整套东西里最值得抄走的。

文档的 Phase 2 示例命令曾经写错:--tier 是个布尔开关,却被当成了路径参数跟了一串目录(真正吃路径的是 --rootfs)。照着抄必报错。可所有单元测试都是绿的——因为测试根本不读文档。

于是他们把文档也变成了被测对象。自检脚本第 14 组会去翻 README.md、SKILL.md、references/、assets/ 里所有 bash 代码块,把调用 scripts/\*.py 的行抽出来(续行符先拼好),对每个脚本跑一遍 --help(有子命令的 build、recover、stats 挨个问),拿到它真实支持的参数列表,再回头核对文档里写的每个 flag 在不在表里;另外用 AST 静态抽出 action=\"store\_true\" 的开关,专门检查布尔开关后面有没有多跟一个值。前一条抓「写了不存在的参数」,后一条才抓得住这次那个真实事故。

两条断言都做了注入式负向测试:把 bug 故意注回文档,测试必须变红;正式版必须绿。

配套还有几个小守卫:安装章节不许引用第三方安装器、徽章上的数字得跟 git ls-files | wc -l 和实际行数对得上、gate-config.json 里记的检查项数量得跟判据表一致。整套自检202 条断言 / 21 组,纯 Python 标准库,不装 binwalk、IDA、angr 也能跑,任何平台一句 python scripts/selftest.py 就有结果。

说句得罪人的:市面上不少「AI 挖洞工具」,装完你才发现是个提示词合集。判断能不能交接给别人用,看它敢不敢给自己的文档写测试,基本就够了。

人家没吹的牛,我也照抄一遍

这部分我建议别跳过,尤其打算真拿去用的。

基准是合成的,assets/bench 里 11 个样本(dev 6 / holdout 5),真实固件那一栏还标着 pending。所以文档里那个 precision/recall,只能证明评测流水线能跑通,不能当成真实固件上的检出率。工作量估算前面说了,还是启发式。ADG 很贵,4 核机器单固件要 70 分钟,没 IDA/angr 的环境别硬上,正确姿势是静态近似加把结论降级标成 HYPOTHESIS,并写清楚缺了哪一环。容器隔离目前是 fail-closed(容器起不来就退出码 3,不会偷偷回宿主机跑),但完整的 Linux + Docker 场景还没验完。黑板只做到 M1,动态探索和 EMBA / Qiling 适配器都还在路线图上,明明白白写着「尚未实现」。

作者说宁可把这些摆到 README 第一屏,也不想让人装完发现「原来只是个提示词合集」。这话我举双手赞成。把没做的写成做了,是这个圈子里最短命的死法。

想上手,命令就这么几行

装和自检:

git clone https://github.com/DroidKali/iot-firmware-vuln-hunting.git \\  ”$HOME/.workbuddy/skills/iot-firmware-vuln-hunting”cd ”$HOME/.workbuddy/skills/iot-firmware-vuln-hunting”uv run scripts/selftest.py

看到「全部通过」再往下走。一次最短的完整审计大概是这个形状:

TARGET=myrouter # Phase 0:先量一量这是个什么固件uv run scripts/fw_triage.py firmware.bin --out fw-audit/$TARGET/00-metrics.json # Phase 1:先看清结构,再挑解包器binwalk firmware.bin # v3 就这一条:列出结构和偏移moria -e firmware.bin # 识别 + 解包# UBIFS 走 ubireader,JFFS2 走 jefferson,squashfs 用 sasquatch 版 unsquashfs # Phase 2:分层 + 排计划(--rootfs 才是路径,--tier 只是个开关)uv run scripts/fw_triage.py --rootfs fw-audit/$TARGET/01-extract \\    --tier --budget 800 \\    --out  fw-audit/$TARGET/02-tiering.json \\    --plan fw-audit/$TARGET/03-audit-plan.md # Phase 3 Layer 1:扫广度uv run scripts/sink_scan.py fw-audit/$TARGET/01-extract \\    --rules assets/sink-rules.json --json fw-audit/$TARGET/10-layer1-sinks.json # Phase 5:过门禁(strict:必需检查没跑就判失败)uv run scripts/coverage_gate.py --profile strict \\    --plan fw-audit/$TARGET/03-audit-plan.md \\    --verified fw-audit/$TARGET/30-findings-verified.md \\    --candidates fw-audit/$TARGET/20-findings-candidates.jsonl \\    --review-list fw-audit/$TARGET/25-review-list.md \\    --tiering fw-audit/$TARGET/02-tiering.json \\    --entrypoints fw-audit/$TARGET/entrypoints.json \\    --taint-graph fw-audit/$TARGET/taint-graph.json \\    --report fw-audit/$TARGET/report.md \\    --out fw-audit/$TARGET/40-coverage.md

fw-audit// 这个目录就是全部中间状态,打包丢给下一个人(或者下一个 Agent)就能接着干。

最后啰嗦一句,冲着赏金猎人说的:上面所有动作,只在书面授权的安全评估、自有设备、以及 SRC 明确划定的范围里做。为了「证明一下能读」顺手多拖几组真实数据,或者往授权范围外的内网横一下,平台那边是扣奖励起步,往下是什么就不展开了。工具给的是复核能力,不是免责牌。

收个尾

回到开头那个抽屉里的纸条。

这些年大家讨论 AI 挖洞,注意力基本都在「模型够不够强、能不能看懂汇编」。但我越来越觉得卡点不在这儿。一个模型说「这里有命令注入」,值不了几个钱;一份报告能让人十分钟内复核清楚——写点在哪、读点在哪、中间经过哪个控制值、最后落到哪个危险函数——才真的能提交、能过审、能拿赏金。

前者靠能力,后者靠约束。而约束是能写进代码里的:状态机、门禁、四态判据、输入哈希、那张污点图。

下次写完报告准备点提交之前,不妨先拿这四个问题问自己一遍。答不上来,就别急着交。我这些年靠这一问,少挨了不少回「无效」的闷棍。


看到这儿了,顺手帮个忙呗 👇

觉得这篇有点用,点个赞让我知道没白写;想继续看这类「老渗透拆洞」的,关注一下,不刷屏,有货才发。

你身边要是有人正准备把固件丢给 AI 等报告,转发给他,说不定能省一次无效提交;也欢迎点个推荐,让更多同好刷到。

    评论:0   参与:  0