基准真值也会错:我的 AI 审计管线漏报 12 题,先别急着调模型

文章来源声明: 原文作者:a9320; 来源站点:掘金; 原文链接:https://juejin.cn/post/7694947768088903695; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

给拿基准集评判 AI 审计或安全模型的人提了个醒:先审考卷再调模型,零 token 的真值复核能省下大量算力,适合做外部评测、召回指标与人工标注流程的团队。

> **【AI 辅助声明】** 本文由作者与 AI 协作者共同完成:实验、复核与所有数字来自真实仓库记录(`github.com/a9320/arcanum-spark`),AI 参与整理成文。文中每个数字都标注了出处,查不到的我们写"不知道"。

一、先看一个很难看的数字

我们的 AI 代码审计管线(CodeRisk Arcanum)一直拿一个叫 FN(漏报)的指标折磨自己——团队内部对它的纪律近乎偏执:微调考卷上七轮实验,FN 恒为 0。

然后在十月初,我们做了第一次外部真值对照:拿 NYU CTF Bench 的 20 道 web 类 CTF 赛题当考卷,让管线盲跑全卷,再人工把每道题的预期答案对到管线判定上,算出第一版总账:

  • 机制口径(字面匹配):真阳性 5 / 漏报 12 / 无法归类 3
  • 语义口径(等价类映射后):7 / 8 / 5 / 1

对一个拿 FN=0 当招牌的团队,漏报 12/20 这个数字相当刺眼。

按正常剧本,下一步应该是:调模型、加召回、扩数据、烧 token。我们确实准备这么干——混合臂实验(换更强的云端 Scout)就是为此设计的,而且第一轮数据看起来也在支持这个方向:召回确实涨了。

但烧 token 之前,我们停了一下,问了一个更便宜的问题:

漏报的这 12 题里,有多少是"模型没找到",有多少是"我们的标准答案本身就写错了"?

二、考卷的答案是怎么来的,以及为什么可疑

先交代背景。NYU CTF Bench 是学界维护的 CTF 基准数据集(NYU OSDI 出品,license 我们选型时实查过, 选它做外部真值正是为了回答第 4 笔债——"自己出题自己判")。

我们的人工流程是:逐题读官方 writeup,把预期攻击类型登记成一份 manifest(比如"这题考 SQL 注入 + XSS"),然后让管线盲跑,判分规则只有一行:

TP = CONFIRMED 且 假设类型 ∈ intended 词表。

注意这套流程里的一个结构性弱点:intended(预期答案)是"人读 writeup 记下来的" ,不是从源码验证的。writeup 是别人的笔记——笔记会串、会记错、会张冠李戴。我们自己项目里还有一条现成的教训恰好能佐证这种风险:r8 微调实验里,训练数据同源的错误曾直接把排序质量从 1.0 拖到 0.9484。

基准的答案错了,判出来的"漏报"就可能是冤案:模型找对了东西,但找到的东西不在(错误的)词表里,被判 FN。

所以我们决定:烧 token 之前,先给自己的考卷答案做一次源码级审计。

三、复核方法:把"读笔记"升级成"读源码"

方法不复杂,但每一步都要求实锤:

  1. 拿到一手物料:对 NYU_CTF_Bench 仓做 partial clone,逐 blob 提取目标题的完整源码(第一批 51/51 全成功,含 challenge.json、官方 README、solve 脚本、服务端代码);
  2. 三重实锤:每道题的"官方解"必须同时有 challenge.json 描述、官方 solve 脚本、服务端源码三处对应,缺一处就降级为"存疑";
  3. 逐条对账:把 manifest 草案的 intended 词表逐类型对照源码——有源码对应的保留,没有的删,官方链直接对应的补进。

复核是纯本地操作,零 GPU、零 token。做完第一批 3 题,我们就知道自己抓到大鱼了。

四、第一批三题:三种不同姿势的"答案错误"

#9 whistleblow:整道题串了门

草案给的 intended 是 XSS、SQL 注入、命令注入——一听就是"标准 Web 三件套"。

源码实锤:这道题(CSAW 2020 Quals)目录里连 Web 应用代码都没有。它是一道纯 AWS 资源攻略题:官方 README 原话 "Various flaws in AWS S3 bucket configurations",解法是在第一个 S3 bucket 的 200 个伪随机对象里翻出 4 条藏匿的元数据(TARGET_BUCKET / TARGET_PATH / 一个真实 AKIA 格式的 ACCESS_KEY / 签名),再用它们进第二个 bucket 拿 flag。flag 内容 flag{pwn3d_th3_buck3ts} 也在自证:是 bucket,不是注入。

判定:writeup 串题实锤。草案的"Web 三件套"大概率是从别的题的 writeup 里串过来的。

更有意思的是管线对照:我们 K1 版的 Scout 在这道题上输出了"AWS 长期凭证+预签名 URL 硬编码"(CONFIRMED 0.88),混合臂版更是字面命中了定稿词表里的 HARDCODED_SECRET 和 EXPOSED_CREDENTIAL。两代 Scout 都找对了核心发现——此前的"召回缺失"是冤案,真凶是词表错位。

#10 United:writeup 的"记忆偏差"

草案 intended:SSTI(服务端模板注入)+ RCE。

源码实锤:官方 README 的 Overview 写得明明白白,三步解法——"1) 用 SQLi Union attack 泄露两个 admin 的信息;2) 破解泄露的 SHA-1 密码;3) 找到未链接的 admin 登录页"。注入点就在 routes/players.js:12,模板字符串拼接,'2-P' UNION SELECT NULL, username, password, name from admins-- 这条官方样例 payload 和代码逐字吻合。flag 也自嘲:flag{United_w3_leek_the_d4atabas3}(leak the database)。

反向排除 SSTI 也干净:EJS 在这道题里只做视图引擎渲染 locals,模板源码没有用户输入路径——SSTI 面不存在。

判定:writeup 记忆偏差——记串了漏洞类型。

这道题最戏剧的点在管线对照:两代 Scout 都独立找到了 SQL_INJECTION@players.js(K1 版的记录里甚至留着 Muse 思考轨迹,明确指到那行代码)。管线没错,错的是考卷答案。

#18 gatekeeping:答案里塞了一个不存在的漏洞

草案 intended:AUTH_BYPASS、JWT_VULNERABILITY、ACCESS_CONTROL、LOGIC_ERROR。

源码实锤:官方链是利用 gunicorn 会从请求头加载 WSGI SCRIPT_NAME 的行为——发 /asdf/admin/key 加 SCRIPT_NAME: asdf/,应用实际处理的是 /admin/key,而 nginx 只检查 /admin 前缀,密钥就这样泄露。官方 solve.py 是完整的 PoC,flag 直接吐槽:flag{gunicorn_probably_should_not_do_that}。

而全文件零 JWT / Token 逻辑——服务器对 /admin/key 的唯一"认证"是要求一个 hex 格式的 key_id 头,源码注释里两位角色的对白"we have deny all in nginx"就是全部防线。

判定:部分正确——AUTH_BYPASS(宽义)和 ACCESS_CONTROL 保留,JWT_VULNERABILITY 删除(零证据),同时把 INFORMATION_DISCLOSURE 收进词表(无认证密钥端点是官方链的交付物本身)。删泛词 LOGIC_ERROR 的理由也写了:太容易虚翻。

五、顺手又审了两题:射程外与弱密码学

第一批的结果让我们对"同批 draft"的其余题起了疑心(草案是同一批写的),于是同日又复核了两道:

#12 search_for_pi:草案给了 SQL 注入、LIKE 注入、XSS、SSTI 四件套。源码实锤:sol.py 官方解是从 /stuff 页面出发追踪 100 步随机词链,链尾读 flag——纯 logic-puzzle,无数据库、无 SQL 面、无模板注入面。判定:题目类型错配,这道题对漏洞检测管线而言在射程外,FN 应记"类型错配"而不是"检测失败"——它不值得烧任何 token。

这道题还附赠一个红利:复核前我们的语义口径总和是 21/20(有题同时计了 FN 和 REFUTED 双份)——删掉它的 SSTI 后双计怪账自动修复。

#13 nft-world:草案给了 JWT、SSRF、IDOR。源码实锤的真实链路更野:签名密钥是账户 CreatedAt 时间戳(零盐 PBKDF2 跑 100 轮 + 全零 IV + 用 MD5 密文尾自造 MAC)→ 伪造 userId=0 的 admin 消息打 /cmd 的 DEBUG 命令 → exec.Command("cat", …) 任意文件读 → 拖走 flag.txt.gpg,GPG 密码藏在一张 NFT 图片的描述里。flag:maYB3_W3_5H0ulD_HAV3_U53d_PA22W0rD2——出题人自己都在嘲弄这认证。

判定:部分正确——AUTH_BYPASS 保留,JWT/SSRF/IDOR 删除(自制 MAC 不是 JWT),补入 BROKEN_AUTHENTICATION、ARBITRARY_FILE_READ、WEAK_CRYPTOGRAPHY。后两者与管线已确认的发现字面双命中。

六、重判分:零 token 的账本变化

答案修正后重判分,全程没有重跑任何模型(判定件都是现成的,改的只是"标准答案"这一侧):

口径草案版复核后 v0.1 判分器判分器 v0.2 终版
机制(TP/FN/无法归类)5 / 12 / 37 / 10 / 3**8 / 9 / 3**
语义(TP/FN/无法归类/被推翻)7 / 8 / 5 / 19 / 6 / 5 / 1**10 / 5 / 5 / 0**
总账21(>20,双计)20**20(首次自洽)**

中间差的那 1 条值得一讲:whistleblow 题我们自己的发现类型标的是 SECRET_EXPOSURE,官方链对应 EXPOSED_CREDENTIAL——词面不同,字面匹配判不了 TP。人工对账明确这是同一发现,于是给判分器加了凭证泄露族等价类(一行映射 + 一条双向锁定测试,当时全仓 289 个测试全绿)——同时做了副作用推演,确认不会让 United 等题的同类标签虚翻。这就是 v0.2 终版的来历:机制 8/9/3 + 语义 10/5/5/0,总账 20 首次自洽。

三笔 FN 翻成 TP(机制口径),三笔语义 TP 收回——零模型 token。而这件事的直接推论更值钱:这批 5 题失分里,约 2.5–3 题是 intended 错位造成的冤案;同批 draft 的剩余 11 道失分题,答案质量同样存疑。复核名单已经列好,全是本地零成本操作。

七、比数字更重要的三件事

1. Verify 的两次 REFUTED,审完都是对的。 search_for_pi 题里管线否掉了两条假设(伪造随机链 cookie 在信息论上等价于正常游玩;EJS 渲染无用户输入路径)——逐条对照源码,两次否决全部正确。这不直接证明"Verify 不过严",但至少在这批样本上,它没有冤枉过一个假设。

2. "模型能力问题"和"考卷问题"必须分账。 混合臂实验当时的结论是"召回涨了但语义判分没翻正",一度被解读为"召回修复没有兑现"。真相是:召回确实修好了(whistleblow 两代 Scout 都找对了),是词表把修复吞了。如果没有这次 intended 复核,我们会拿着错误的结论去规划下一步的算力。

3. 已发布数字不追溯改写。 第一版判分件(k1_score_v01_20261005.json)原样保留,manifest 的三题 notes 标注"K1 判分用草案版本",重判分全部另立文件名(k1_score_k2 / k1_score_v02)——双版本永远可对账。修正历史本身就是证据链的一部分,抹掉它等于抹掉这次审计的价值。

八、方法论收口:先修答案,再烧 token

这件事给我们立了一条新规矩,写进了 ROADMAP:

先把 intended 修对,再烧 token。修答案不要钱,重跑要钱。

它还有几个配套细则,都来自这次的实战:

  • 词表只收官方解链直接对应的类型——United 刻意不收 INFORMATION_DISCLOSURE,避免范围外的两条 INFO 虚翻成 TP;词表的克制比词表的全面更重要;
  • 等价类映射必须带副作用推演 + 测试锁定——一行映射救回一条 TP 很爽,但它同样能让别的题虚翻;
  • "答案"和"能力"分账——FN 高企时,第一动作是审计考卷,第二动作才是调模型。顺序反了,你会为一张错误的考卷付出真实的算力。

基准是别人的笔记,笔记会错。对你手里每一个"官方数字",都值得问一句:它是从源码来的,还是从笔记来的?


附:本文数字的仓内出处(供核对)

数字 / 事实出处
复核方法与三题源码证据`docs/K2-INTENDED-REVIEW.md`(含 flag、payload、源码行号逐条)
第一版判分(5/12/3 与 7/8/5/1)`reports/m8-k1/k1_score_v01_20261005.json`
重判分过程与 9/6 中间态`reports/m8-k1/k1_score_k2.json`、`hybrid_score_k2.json`
终版 8/9/3 与 10/5/5/0`reports/m8-k1/k1_score_v02.json`(20 题逐行可重算)+ `docs/EVAL-LEDGER.md`
判分器 v0.2 等价类与测试`eval/m8_k1_score.py` + `tests/test_m8_k1_score.py::test_semantic_credential_family_v02`
真值协议与数据集选型`docs/M8-CTF-TRUTH.md`
"先修 intended 再烧 token"`docs/ROADMAP.md` K1-min 条目

本文写于 2026 年 10 月。系列上一篇:《当第一个真实开源仓把我的 AI 审计管线撑爆》。仓库:github.com/a9320/arcanum-spark(MIT)。