dsh-redteam-attack-matrix
v1.2.2
Published
DSH 红队攻击矩阵插件:扫工作区对话映射到 MITRE ATLAS / ATT&CK、OWASP LLM Top 10、NVIDIA AI Kill Chain 四套框架,标出已覆盖与缺口,并给出测试时间线
Maintainers
Readme
攻击矩阵 · DSH 插件
把工作区里的对话(用户消息、模型回复、工具调用与结果)扫成一张攻击矩阵: 每次操作命中哪个框架的哪个技术点,标出已覆盖与缺口, 并在下方给出对本组织发起测试的时间线。
当前支持四套框架,每套一个页面:
| 框架 | 面向 | 收录 | 数据来源 |
|---|---|---|---|
| MITRE ATLAS | AI 系统本身的对抗战术 | 45 技术点 / 16 阶段 | 官方机读数据 atlas-data v2026.09 |
| MITRE ATT&CK Enterprise | 企业 / IT 面战术技术 | 46 技术点 / 15 阶段 | MITRE 官方 TAXII 2.1(企业版 collection) |
| OWASP Top 10 for LLM Applications 2025 | LLM 应用风险 | LLM01–LLM10 | 官方仓库 2_0_vulns |
| NVIDIA AI Kill Chain | AI 应用攻击阶段 | 29 技术点 / 6 阶段 | NVIDIA 官方博客(2025-09-11, Rich Harang) |
ATLAS 和 ATT&CK 是两套东西:ATLAS 打 AI 系统本身,ATT&CK 打企业 IT 面。 红队实操里两者会同时命中(先扫资产、拿凭据,再去打模型),所以并列成两个页面 而不是合并。两个大框架都只收了与「AI/LLM 红队对话」相关的子集,不是全量目录 —— 矩阵的价值在于缺口准,不在于条目多。
它怎么工作
- 扫对话(自动):host 每 20 秒读一次当前工作区里所有会话的事件流(
user/message、assistant/message、tool/call、tool/result),把每次操作抽成一条可读记录。 扫描是增量的(按会话 seq 续点),没有新事件时不落盘。面板上没有扫描按钮 —— 这件事不需要人记得去做。 - 匹配:用每个技术点的特征词表(
detect_keywords)在操作文本里匹配。 命中先记为疑似:自动匹配只负责提示,不负责下结论。 - 判定(AI 自动):疑似命中进待判定队列,host 成批交给模型,模型用
matrix_label逐条下结论 —— 只有「做成了」才记为已确认(利用成功、拿到回显/shell/文件内容、 PoC 跑通、注入或越权确实生效),只是提及或尝试未果保持疑似,误报直接删除。 每条结论都带reason(看到了什么内容),记进矩阵日志并显示在卡片上。 面板上的「确认覆盖 / 排除」保留为人工覆盖 AI 结论的入口(会标成「人工判定」)。 - 落盘:矩阵数据写进工作区自己的
.redteam-attack-matrix.json, 所以切工作区 = 切数据集,互不污染。所有「读—改—写」都串行,避免互相覆盖。 - 时间线:按 框架×技术×会话 分组倒序,每组给出
firstAt → lastAt的区间与可读时长, 时间精确到秒并标明本地时区,并列出这组打过的具体目标(URL / IP[:端口] / 主机名 / nmap 的-p端口表 —— 从操作文本里抽出来)。它的用途是回溯「谁在什么时候对谁做了什么」, 所以要经得起对表。时间线可以整份导出 CSV(列含目标、命中词、判定依据、精确到秒的 本地时间与 epoch),导出取全部桶,不只是面板显示的那最近 200 个。 加这个字段之前扫出来的老数据没有targets,读的时候会从留存的证据片段里现取 —— 不必为了看目标而重扫(重扫会丢掉已有的判定结论)。
界面
┌ 顶栏:工作区下拉 · 重扫 · 清除 · 状态(会话数 · 操作数 · 待判定 · 自动扫描心跳)· 已忽略 N 个会话 / 恢复
├ 框架标签页:ATLAS / ATT&CK / OWASP LLM / NVIDIA 每个框架一页
├ 覆盖率条:已确认 / 疑似 / 总数 · 命中操作次数
├ 技术卡片网格:灰=未覆盖 黄=疑似 **红=已确认**
│ (红色是刻意的:已确认意味着这个技术点**真的打成了**,对防守方是坏消息、
│ 对红队是战果 —— 用绿色会读成「好」,语义正好反了)
│ └ 点卡片 → 右侧详情:技术点说明、判定线索、特征词、
│ 攻击操作日志(按会话聚合,带证据片段与判定依据)、确认 / 排除
└ 时间线:每行是「时间区间 + 目标(IP/端口/URL/主机名)+ 次数 + 判定状态」,按时间倒序;右上角「导出 CSV」清除与重扫的区别
两者都清空命中记录,区别只在扫描续点(每个会话已经扫到哪个 seq):
| | 命中记录与判定结论 | 扫描续点 | 结果 | |---|---|---|---| | 清除 | 清空 | 保留 | 历史事件不会重新入表,从此刻起重来 | | 重扫 | 清空 | 清空 | 按事件重算,历史命中回来 —— 连之前排除掉的桶也一并重建 |
所以「重扫」根治不了噪音:它清完立刻重扫,同一批历史事件会把矩阵原样建回来。 要清噪音得用「清除」。而「清除」是可逆的 —— 想把历史找回来就重扫一次。 清除是破坏性的(矩阵数据没有垃圾箱),所以按钮要点两次才执行。
忽略非目标会话
扫描对象是工作区里的所有会话 —— 包括你正在开发这个插件的那条对话。而它本身 就在不停命中框架关键词:实测写一行含「主机名」的注释,就造出一个 T1082 桶; 把面板上的时间线贴回对话,也会再造一批。逐条排除永远排不完。
所以在卡片详情的攻击操作日志里点「忽略该会话」:把整个会话标成非目标 —— 不再扫描它,并清掉它已有的命中记录。顶栏显示已忽略数量,点「恢复」可撤销 (想让它们的历史命中回来,再点一次「重扫」)。忽略名单优先于扫描水位, 所以「重扫」也不会把它带回来。
安装
dsh plugin --profile web add dsh-redteam-attack-matrix
# 重启 dsh web包自带 dsh.bundle.patch,装完自动挂载,不需要手工编辑配置。
版本
1.1.1(仓库版本)。src/ 是权威源码,lib/ 由它生成,因此不存在「仓库版本与运行版本不一致」。
1.1.1 相对 1.1.0
新增
- 对外服务
redteamAttackMatrix:digest()/names()/storePath()。 红队报告插件要靠它拿到「命中矩阵的内容」—— 技术点名字只在本插件的框架表里, 命中详情的存储结构也只有本插件知道。有了这个服务,报告那边不必去猜文件格式和 id→名字; 没有它时报告会退回直接读存储文件(只有 id、没有名字,仍然可用)。
1.1.0 相对 1.0.1
新增
- 扫描与判定全自动:host 每 20 秒增量扫描(没有新事件时不落盘),疑似命中成批交给模型,
由模型用
matrix_label逐条定论 —— 面板上没有扫描按钮。每条结论带reason(看到了什么) 与来源(AI 自动 / 人工),写进日志并显示在卡片上。人工的「确认覆盖 / 排除」保留为覆盖入口。 - 时间线:每行是
firstAt → lastAt的区间(精确到秒 + 可读时长),并列出该组打过的 具体目标(URL / IP[:端口] / 主机名 / nmap 端口表);可整份导出 CSV (全部桶、17 列、含 BOM 以便 Excel 正确读中文)。 - 清除:清空命中记录但保留扫描续点 —— 历史事件不会重新入表。这与「重扫」互补: 重扫连水位一起清、历史原样重建。两者互为逆操作,所以清除不需要垃圾箱。
- 忽略会话:把整个非目标会话关掉(不再扫描它 + 清掉它已有的记录)。存在的理由很实际: 在「开发这个插件」的工作区里,对话本身会不停命中框架关键词,逐条排除永远排不完。可恢复。
修复
- ATT&CK 标签页一片空白:技术点的
tactic_ids写的是 slug(reconnaissance)、 阶段表写的是TA00xx,46 个技术点被静默丢掉。修正映射,并把 15 个阶段排成标准杀伤链顺序; 同时让「命不中任何阶段」的技术点归入「未归类」而不是从界面上消失。 - 匹配器缺词边界:
rce命中source/resource、dos命中todos(实测单桶最大 395 次 的假阳性)。纯 ASCII 关键词现在要求词边界,含 CJK 的仍走子串(中文没有词边界)。 - 证据片段只留最早的 4 条:判定者看不到后面的关键动作(实测漏掉过一次未授权操作)。 改为最早 2 条 + 滚动保留最新 2 条。
- 自反馈:插件自己的工具调用与研判请求正文不再进矩阵(实测污染过 25 个桶)。
- 并发读改写丢更新:所有「读—改—写」路径串行化。
- 「已确认」改用红色:它意味着这个技术点真的打成了,对防守方是坏消息、对红队是战果 —— 用绿色会读成「好」,语义正好反了。
测试:npm test 跑 host 侧 97 项 + client 侧 87 项(含上述每一条的回归线、对外服务那份数据形状,以及「样式只引用注册过的主题 token」)。
1.0.1 / 1.0.0
1.0.1 修的是客户端 RPC 路径:1.0.0 的 lib/client.js 把它写死成了
/dsh-redteam-asset-graph/rpc,于是本插件去打资产图谱的路由 —— 面板请求全 404,
两者同时安装时还会读到对方的数据。现在由生成器按包名替换 __ROUTE_BASE__,
并把实际请求的 url 钉进冒烟测试(原来只记 method,路径写错成别的插件也测不出来)。
1.0.0 的内容(四套框架矩阵、扫描与确认链路、时间线)见 git log 与 tag
attack-matrix-v1.0.0。
已知限制
- 只能扫进程内活着的会话。DSH 的
sessions是内存态 Service (官方描述:持久化不在这个 store 里),所以重启后已不在内存里的历史会话扫不到。 矩阵自己的数据是落盘的,重启不会丢已有记录,但「重扫」也读不回已经卸载的会话。 - 自动匹配会误报。特征词表刻意选得具体(宁可漏报不刷屏),但仍然只是提示。
覆盖率以判定结论为准,而判定由模型自动做出、逐条带
reason,人可以随时用 「确认覆盖 / 排除」覆盖它。 - 判定是模型的判断,不是证据的证明。判据把「没拿到成功证据」一律压回疑似,
但模型仍可能看漏。要拿去做交付结论时,请抽查若干条
reason对应的证据片段。 - 判定会占用一次模型回合。待判定攒够 6 条、或者最老的一条已经等了 10 分钟才交一批; 每批最多 8 条、两批之间至少隔 5 分钟;交出去 15 分钟还没有结论的条目会自动退回队列 (唤醒可能根本没被接手)。唤醒的是当前会话的 Agent —— 所以面板上会看到研判请求 作为消息进来,那是设计如此。
- 框架数据是内置的,不联网拉取。ATT&CK / ATLAS 这类大框架只收与
AI/LLM 测试对话可能相关的子集,不是全量。每个框架的版本与来源写在
面板标签页的 tooltip 里(
src/frameworks.js的source字段)。 - 框架数据里「阶段 id」与「技术点的 tactic_ids」必须对得上。对不上的技术点会被
归到「未归类」组,而不是从界面上消失 —— 这条是踩过坑的:ATT&CK 的技术点写的是
slug(
reconnaissance)、阶段表写的是TA00xx,46 个技术点被静默丢掉, 切到那个标签页就是一片空白。npm test里有对应的回归线(数据完整性与渲染两层)。
开发
src/ 是权威源码,lib/ 由它生成:
npm run build:lib # 生成 lib/
npm run check:lib # 校验与 src/ 是否漂移(prepack 会跑)
npm test # 扫描链路 + 客户端渲染test:scan 要解析 @deepseek-ai/dsh-tools。解析不到时它会打印一句「跳过」然后
exit 0 —— npm test 照样绿,但主机侧一条断言都没跑。先按仓库根 README 的
「让主机侧测试真的跑起来」软链依赖,跑出来的项数才算数(host 84 + client 85)。
改之前请先读仓库的 ../../docs/DEVELOPMENT.md—— DSH 插件开发有几条硬约束(静态形态下哪些 API 不存在、组合 patch 的语义、 沙箱与依赖解析),那里记的每一条都是实测踩出来的。
| 文件 | 说明 |
|---|---|
| src/host.js | applyHost 的函数体(函数头/垫片/收尾在 lib/parts/,不要重复写) |
| src/frameworks.js | 框架数据:tactics + techniques + detect_keywords |
| src/client.js | 客户端半边,以 return { name, inject, apply } 结尾 |
| lib/parts/ | 生成器模板:垫片与包装都在这里 |
| tools/build-lib.mjs | 生成器(把 src/frameworks.js 内联进产物) |
| cordis.patch.yml | bundle patch,dsh plugin add 靠它自动挂载 |
加技术点 / 改特征词
只改 src/frameworks.js,然后 npm run build:lib。生成器会校验:
框架结构完整、技术点 id 不重复、detect_keywords 是数组,不合格直接终止构建。
加特征词前先问一句:正常开发对话会不会命中它?
一个会命中每次工具调用的词(比如裸的 tool call)会把矩阵变成噪音墙,
而矩阵的价值恰恰在于缺口准。
数据源
面板用的 JSON-RPC 路由已由 lib/parts/host.tail.js 注册(基址 /dsh-redteam-attack-matrix),
src/host.js 里用 harness.handle('method', fn) 补句柄,客户端用 host.call 调。
宿主侧读会话靠两个 Service:
ctx.get('workspaceRegistry')→list()/get(id);Workspace.sessionIds已按 canonical cwd 过滤ctx.get('sessions')→get(id);session.snapshotEvents()拿事件流
会话事件里用来判断的几类:user/message、assistant/message、tool/call、tool/result。
一次「攻击操作」就是其中一条事件,这也是时间线的刻度。
