☰
XERJ 多智能体逐文件零日清扫:当结构化图漏掉一个「缺失的兄弟守卫」时
2026/10/17 6:57:07 网站建设 项目流程

【免费下载链接】xerj

XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.

项目地址:https://gitcode.com/GitHub_Trending/xe/xerj
点击查看免费下载

在对 WordPress 核心完成结构化的认证/污点/组合审计、且所有表面均报告「干净」之后,XERJ 案例研究团队又做了一次更彻底的检验:一个覆盖全部 883 个安全相关文件的多智能体逐文件零日清扫(per-file zero-day sweep)。这篇技术指南完整还原该工作流的四阶段设计、222 个智能体的规模与成本账、以及它发现的 5 条候选漏洞——其中一条角色注入(role injection)漏洞正是结构图漏掉的真实授权缺口;读完你可以理解为什么「结构性覆盖」与「逐文件精读」必须互补,以及如何在自己的代码库上复现这套方法。

为什么要在「全绿」之后再做逐文件清扫

本案例研究的前置阶段(详见 README.md 与 COVERAGE-AUDIT.md)已经完成了大量结构化工作:用 tree-sitter 对 1,492 个 PHP 文件(约 61.9 万行)做了 100% 解析率的安全清单普查,把 275 个危险 PHP 内置函数 / 28 个类别的 11,191 个调用点全部入索引,并对认证、IDOR、SSRF、反序列化 gadget、SQL 双 prepare、清洗器组合等表面给出了「干净」或「已验证」的结论。

但「结构图说干净」与「真的没有遗漏」是两回事。结构化方法有一个天生的盲区:它能问「这里有没有能力检查」,却看不见「兄弟分支上本该有的检查少了一个」。于是有了这次逐文件清扫——它是结构方法是否真实存在盲区的诚实测试。结果证明:盲区确实存在,它找到了一条真实的结构图漏掉的授权(authz)缺口。

工作流:Scout → Review → Verify → Synthesize 四阶段

清扫不是把文件丢给模型让它「找 bug」,而是严格分四阶段的多智能体流水线:

  1. Scout(侦察):从 XERJ 索引生成 883 个安全相关文件的工作清单。XERJ 的结构化索引(wpsinks/wpaudit/wpauthz/wphooks)在此的作用是精确圈定范围:883 个安全相关文件,而不是盲目分块全部 1,492 个文件。
  2. Review(审查):177 个审查智能体,每个分到 5 个文件,阅读真实代码 + XERJ 事实,专门猎杀以下类别:逻辑 bug、缺失/不充分/==破环的授权检查、二阶(second-order)漏洞、TOCTOU、反序列化、类型混淆(type-confusion)——并且只上报有具体 source→sink 链或破环检查的候选。
  3. Verify(对抗验证):每个候选分配一个独立智能体,唯一任务是驳倒它:可达吗?有守卫吗?是死代码吗?是接收者类型误报(receiver-type FP)吗?只部署时才存在吗?
  4. Synthesize(综合):只有对抗验证失败(即无法被驳倒)的候选才保留下来。

关键的纪律点:Verify 阶段是「独立」的,不是同一个智能体给自己找台阶。这就是 IMPROVEMENTS.md 中第 19 条改进项「对抗性验证」的实践落地,也是整个案例研究「验证靠读、再靠执行」原则的延伸。

规模账:222 个智能体(177 审查 + 44 验证 + 综合),约1,080 万 token,约31 分钟;44 个候选经对抗验证后5 条确认。8 个验证智能体在运行尾声撞上了周用量限额,对应候选标记为「未验证」,不计数。

五条确认发现

#file:line类别严重度说明
1wp-admin/user-new.php:100角色注入(wp_ensure_editable_role应用不一致)Medium(多站点 + 过滤后的角色)NEW——结构图漏掉了它。详见 FINDINGS.md #4
2wp-includes/http.php:598SSRF169.254缺口Medium对 FINDINGS.md #1 的独立再发现(验证了清扫的有效性)
3akismet/class.akismet-admin.php:1212CSRF(GET 视图动作缺 nonce)Low插件;需登录管理员 + 伪造 GET
4ID3/module.audio-video.flv.php:716资源耗尽 DoSLowauthor+ 上传构造的.flv;循环次数由攻击者设置
5build/pages/font-library/page.php:308缺少能力检查Low低权限访问字体库管理页

诚实的定级:发现 3–5 处于工作流的对抗验证置信度层面;只有 #1(以及已知的 #2)由项目负责人二次精读。#3–#5 是低危且插件/边缘性质,报告为「待负责人复核的候选」,而不是断言漏洞。整个列表的基调就是案例研究的核心纪律——不制造发现,把「干净」的否定结果也如实报告。

关键发现 #4:角色注入的完整攻击链

这条是清扫最大的价值证明。wp-admin/user-new.php里有三个向请求提供角色赋值/存储的 sink,其中两个有守卫、一个没有(WP 7.0.2):

// line 73 (adduser, noconfirmation 分支) -> GUARDED wp_ensure_editable_role( $_REQUEST['role'] ); add_existing_user_to_blog([... 'role'=>$_REQUEST['role']]); // line 231 (createuser 分支) -> GUARDED wp_ensure_editable_role( $_REQUEST['role'] ); // line 95-102 (adduser, email-invitation else 分支) -> NOT GUARDED add_option( 'new_user_'.$key, [ 'user_id'=>$user_id, 'email'=>..., 'role'=>$_REQUEST['role'] ] ); $roles = get_editable_roles(); $role = $roles[ $_REQUEST['role'] ]; // AFTER the store; email only; warning not wp_die

第 100 行把$_REQUEST['role']原样存储到new_user_{key}选项中,没有调用wp_ensure_editable_role()。而存储的角色在邀请确认时会以二阶方式被应用(详见 ATTACK-SCENARIO-role-injection.md):

POST /wp-admin/network/user-new.php (多站点) Cookie: <登录的受限站点管理员会话> Content-Type: application/x-www-form-urlencoded action=adduser &_wpnonce_add-user=<表单有效 nonce> &email=existing.network.user@example.com &role=administrator # 注入的角色,不在邀请者的 editable_roles 中 # (省略 noconfirmation,使 email-invitation else 分支执行)

服务端在user-new.php:100存储new_user_{key} = { user_id, email, role:'administrator' }(无wp_ensure_editable_role)。二阶触发——被邀请者点击邮件链接:

GET /newbloguser/<key>/ # init hook maybe_add_existing_user_to_blog -> add_user_to_blog -> set_role('administrator')

maybe_add_existing_user_to_blog()(ms-functions.php)在每次请求上运行:读到new_user_{key}中的污染角色后调用add_existing_user_to_blog(),最终add_user_to_blog()里的$user->set_role($role)不再做任何复查(只有默认值为 true 的can_add_user_to_blog过滤器)。完整的 source→sink 链:

$_REQUEST['role']=administrator [POST, 受限站点管理员] └ user-new.php:100 add_option('new_user_{key}',role) ← 无 wp_ensure_editable_role (BUG) └ [DB] ── 二阶 ──> GET /newbloguser/{key}/ └ maybe_add_existing_user_to_blog -> add_existing_user_to_blog └ add_user_to_blog -> $user->set_role('administrator') ← 提权

诚实的定级(这一点必须说清楚):在标准安装下get_editable_roles()返回全部角色,wp_ensure_editable_role是空操作,邀请者本来就能分配该角色——不跨越任何权限边界。这个缺口真正咬人的条件是:多站点+ 存在editable_roles过滤器把邀请者限制在注入角色之下(会员制 / 角色管理插件 / 多站点委派场景常见)。所以它是「无条件成立的真实不一致(3 个 sink 中 2 个有守卫)」+「条件触发的提权」,不是标准安装的 RCE——报告给 WordPress 作为加固修复是合理的,断言为 0-day 则不妥。

为什么结构图漏掉了 #1:诚实复盘

项目负责人的复盘非常直白:认证图问的是「这里有没有能力检查?」——user-new.php里确实有一个(promote_user),所以它通过了。真正的 bug 是不一致性:一个角色专用的守卫(wp_ensure_editable_role,6.8.0 加入)被应用在 3 个兄弟角色 sink 中的 2 个上,第三个漏掉了。

「检查存在性」的模型无法看到「兄弟分支守卫缺失」;而逐文件精读把三个分支放在一起对比,就能看见。这是两种方法的本质差异:

结构性覆盖与逐文件精读是互补的——图负责收窄范围并证明覆盖,精读负责抓住事实没有编码进索引的逻辑与不一致缺口。

这次清扫因此被正式纳入了方法论(见 PLAYBOOK.md 第 4 步的「改进循环」:添加完整性批判者 + 对抗验证)。

用量限额中断后的恢复:journal 驱动的精确盘点

运行在尾声撞上周用量限额。这里的价值在于:通过读取工作流journal.jsonl(每个智能体一行result),可以精确恢复到底完成了什么:

  • Review 阶段:177/177 批全部完成 → 全部 883 个文件都被分析过。没有任何文件漏审。(这是最重要的恢复结论:审查的覆盖是完整的。)
  • Verify 阶段:44 个候选中 36 个已验证,8 个在限额处被丢弃。所以缺口是 8 个未验证候选,而不是未分析文件——可以直接读代码补上(不需要智能体)。

8 个被丢弃候选的人工复核结果

负责人直接读真实代码逐个复核:

候选负责人裁定
blocks/cover.php:102(oEmbed KSES 绕过)驳回——该 block 构建的是固定<iframe>模板并用esc_url($iframe_src),不会输出 provider 的原始 HTML,不可注入。
feed-rss2-comments.php:107(]]>CDATA 逃逸)疑似成立、低–中危、未确认。comment_text()输出位于<![CDATA[…]]>内;wp_kses_post不会转义字面]]>,因此含]]>的评论可闭合 CDATA 向评论 RSS 流注入标记。触发:启用评论流时匿名评论;影响面在未经消毒渲染的订阅端/聚合器——不是 WP 站点沦陷。需订阅器测试确认。
block-supports/custom-css.php:258(edit_css 作用域)疑似成立、中危、未确认。custom-CSS 剥离挂在content_save_pre/content_filtered_save_pre(文章内容保存)上。若 block-widget 经 REST widgets 路径保存时不经过这些过滤器,持有edit_theme_options而无edit_css/unfiltered_html的用户可能存入 custom CSS。需 widget 保存路径追踪确认。
rest .../widgets-controller.php:178(context=edit 作用域)候选——show_in_rest侧栏的读作用域问题,低危,待确认。
rest .../global-styles-controller.php:256(per-block CSS 未校验)候选——validate_custom_css只作用于顶层styles.css,低危。
rest .../abilities-v1-run-controller.php:163(404-vs-403 信息泄露)候选——能力名枚举,信息泄露,低危。
wp-mail.php:170(From 头 → 文章作者)设计层面——按文档(Post-by-Email 信任邮箱),非代码 bug。
wp-trackback.php:75(UTF-7 字符集过滤器)WordPress有意在此拦截UTF-7 trackback——这段代码是缓解措施而非 bug,疑似误报。

恢复后的净结果:负责人确认的新增发现仍然只有user-new.php角色注入(FINDINGS.md #4);cover.php、wp-mail.php、wp-trackback.php被驳回/按设计处理;feed-rss2-comments.php和custom-css.php是「疑似但未确认」的候选,值得在用量预算重置后做后续跟进(订阅器测试 / widget 保存路径追踪)。

为什么 XERJ 让这件事可发现:对比全量上下文窗口

朴素替代方案——把整个代码库喂给模型然后问「找 bug」——在三个轴上都失败了,而这次清扫把这三个失败点暴露得淋漓尽致:

  1. 装不下。WP 核心约 520 万 token;200k 窗口只能装下约 4%。你必须分块,而分块正是跨过程序 bug(user-new.php的角色流向两个文件之外的add_user_to_blog)被拆散丢失的地方。XERJ 把整个调用图作为事实保存(结构化审计仅约 2.6 万 token),智能体无需加载 883 个文件的正文即可在其上推理。
  2. 没有图就无法对比兄弟分支。user-new.php的 bug 是「缺失的兄弟守卫」——只有把三个角色 sink 放在一起才能看见。XERJ 的wpsinks/wpauthz索引用一条查询就能返回「所有带 role 参数的add_option/add_existing_user_to_blog调用」,把三个兄弟分支直接交到审查者手里对比;而窗口化扫描会在不同的块里读到它们,永远无法对齐。
  3. 每次都把一切重读一遍。全上下文扫描每个问题都要支付全部 token 成本。XERJ 只建一次索引,之后的每次查询(sinks、模式、authz 姿态、gadget 可达性、污点)都是几百到几千 token。逐文件深度清扫(约 1,080 万 token、222 个智能体)是彻底的兜底;XERJ 结构化扫描(约 2.6 万)是日常主力——而且正是 XERJ 让清扫能精准瞄准那 883 个安全相关文件,而不是盲目分块 1,492 个。

值得一提的是,这套「事实化索引」本身也有过教训:逆向调用图查询曾因 XERJ 引擎的term数组匹配 bug 少报结果,被审计过程暴露并修复,完整记录在 xerj-keyword-array-term-fix.md——修复后一条terms查询即可拿到精确调用者,审计成本从 O(索引规模) 降到 O(命中结果)。

分工结论:图与读缺一不可

这套方法论的分工一句话可以概括:

XERJ 负责收窄范围、证明覆盖、把正确的「邻居」递给审查者;逐文件精读负责抓住事实没有编码的逻辑与不一致缺口。单独任何一方都找不到user-new.php——结构图放过了它(能力检查确实存在),盲目的全上下文扫描会把它的跨文件流拆散。两者合起来才抓住了它。

这与 README.md 中「XERJ 不负责找 bug,负责把不可读的干草堆变成可以逐根读完的短名单」的定位完全一致。

复现:把清扫指向你自己的代码库

环境与索引构建(与整个案例研究共用,见 REPRODUCE.md):

pip install -r docs/examples/ast-vuln-graph/requirements.txt # tree-sitter 语法 xerj --insecure --data-dir ./wpdata & # ES 兼容端口 :9200 cd docs/examples/ast-vuln-graph python3 wp_audit_index.py ./wordpress # -> wpaudit (11,990 fns) + wphooks (1,343) python3 wp_authz_graph.py ./wordpress # -> wpauthz python3 wp_compose_index.py ./wordpress # -> wpcompose

清扫工作流的脚本在会话的workflows/scripts/下;重跑点是把同一套审查/验证提示词指向任意文件清单(review_files.json)。也就是说,这个工作流是「数据驱动」的:换一份文件清单就是一次新的清扫。

清扫后想对单个 sink 做端到端追踪(不访问本地文件),用 trace_sink.py:

cd docs/case-studies/wordpress-security-audit/sink-census python3 trace_sink.py wp-includes/rest-api/endpoints/class-wp-rest-widgets-controller.php 589

它依次连接wpsinks(sink + AI 富化裁定)→wpaudit(包围函数的事实)→wpauthz(cap/nonce 守卫)→ 逆向调用者,输出形如「SOURCE→SINK candidate (confirm arg flow + guards)」的追踪裁定——这正是「图指路、人精读」循环的 API 化形态。

想要一套可直接安装的自动化工作流,skill/SKILL.md 提供了完整的 Claude Code skill:先sink_census.py做覆盖保证普查,再pattern_census.py扫非函数模式,用enrich_pipeline.py把 AI 裁定写回wpsinks,最后按本案例的方法逐类深挖。

成本结构:两种模式各司其职

约 1,080 万 token 的穷尽式逐文件扫描是刻度盘的「彻底」端——在怀疑有遗漏时适用;而早期整个结构化审计约 2.6 万 token 的成本是「日常主力」。它们不是二选一,而是同一工作流里的两层:日常用结构化查询压成本,周期性用深度清扫做「我们有没有漏什么」的兜底。

token 账的对照也很直观(见 wordpress-audit-with-xerj.md 的实测):同样的问题「请求输入到达 SQL」,整树加载约 520 万 token、grep+读 51 个整文件约 86.4 万 token、XERJ 智能体路径约 2,150 token(约 400×),且后者能逐条精读全部候选而不是扫读。清扫阶段能瞄准 883 个文件而不是盲分 1,492 个,正是靠这份索引的圈定能力。

给审计者的实践清单

把这次清扫沉淀为可复用的方法,核心就五条:

  1. 先结构、后精读,两层都保留:结构图收窄范围并证明覆盖(wpsinks11,191 个调用点、wpaudit11,990 个函数),逐文件清扫抓「缺失的兄弟守卫」这类事实不可编码的漏洞。
  2. 每个候选都要对抗验证:独立智能体专职驳倒,驳不倒才保留。
  3. journal 驱动恢复:用量限额中断后读journal.jsonl精确盘点「哪些批次完成、哪些候选未验证」,未验证候选直接读代码补,不需要智能体。
  4. 诚实定级:区分「标准安装不跨越边界 / 条件触发(多站点 + filtered editable_roles)」与「真实无条件不一致」,区分「疑似候选」与「负责人确认」,不制造发现。
  5. 把学到的不变量编译成指纹:像sanitizer_seq、SSRF 区间完整性检查那样,让下一次审计用一条查询免费复用——这是方法论的复利所在(详见 IMPROVEMENTS.md)。

这套多智能体清扫是 XERJ 安全审计方法论的「诚实测试仪」:它证明结构方法的盲区确实存在且可被补上,也证明了当图与读分工明确时,883 个文件、44 个候选、5 条确认可以在约 31 分钟内完成——并且每一条结论都有file:line可查、可复核、可复现。

【免费下载链接】xerj

XERJ is the new way for AI to search data. Its autoindex capability activates agents to know your data without the token waste of grep and sed. One command indexes code, docs, logs and PDFs for search, RAG, security audits and agent memory, using 40x fewer tokens than grep. Elasticsearch compatible, so existing clients just work.

项目地址:https://gitcode.com/GitHub_Trending/xe/xerj
点击查看免费下载

相关推荐

上一篇:DeepSeek Harness 结果时刻文件 diff 呈现:基于可回放 presentationMeta 的应用 hunk 方案
下一篇:TinyBase 架构选项全解析:从独立内存 Store 到云端同步的七种数据架构方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询