diff --git a/web/src/lib/mock/data.ts b/web/src/lib/mock/data.ts index 44e3e31..0e54b14 100644 --- a/web/src/lib/mock/data.ts +++ b/web/src/lib/mock/data.ts @@ -161,7 +161,7 @@ export const assetCounts: Record = assets.reducealert(document.domain) 原样回显于结果标题。", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T22:10:00Z") }, @@ -178,7 +178,7 @@ export const findings: Finding[] = [ { id: "f-14", vulnclass: "Network Segmentation", severity: "medium", status: "pending", summary: "DMZ 主机双网卡直连内网,边界隔离失效", evidence: "dmz-web01 第二网卡在 10.10.10.0/24,可从 DMZ 直达域控/文件服务器/Jenkins,越过网络分区。", intent_id: "i9", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T21:00:00Z") }, { id: "f-15", vulnclass: "Unauthenticated RCE", severity: "high", status: "pending", summary: "内部 Jenkins 未授权 Script Console 远程命令执行", evidence: "GET /script 无需登录即可执行 Groovy:'whoami'.execute().text → SYSTEM;并从凭据库导出域账号 acme\\svc_deploy。", intent_id: "i11", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T21:50:00Z") }, { id: "f-16", vulnclass: "Kerberoasting", severity: "high", status: "pending", summary: "域服务账号 svc_sql 可被 Kerberoast 且口令弱", evidence: "GetUserSPNs 请求 svc_sql 的 TGS,hashcat -m 13100 离线破出 Sql@2020,该账号属于 SQL 管理组。", intent_id: "i12", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T23:20:00Z") }, - { id: "f-17", vulnclass: "Domain Compromise", name: "内网域控 DC01 完全控制(Domain Admin)", severity: "critical", status: "confirmed", summary: "拿下域控 DC01(Domain Admin)—— 内部靶标达成", evidence: "svc_deploy 属于 Domain Admins,psexec.py 以其登录 DC01 得 SYSTEM,secretsdump 导出全域 NTLM 哈希(含 krbtgt),完成靶标控制。", intent_id: "i13", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-26T00:20:00Z") }, + { id: "f-17", vulnclass: "Domain Compromise", name: "内网域控 DC01 完全控制(Domain Admin)", severity: "critical", status: "confirmed", report: "## 漏洞概述\n\n经外网 Fastjson RCE 立足 DMZ,再由 sudo 提权、双网卡穿透、Jenkins 未授权 RCE 拿到域账号 `svc_deploy`;该账号属于 **Domain Admins**,最终完全控制域控 **DC01**。\n\n## 影响\n\n- 取得域管理员权限,可控制全域主机与账号\n- `secretsdump` 导出全域 NTLM 哈希(含 `krbtgt`),可制作黄金票据长期潜伏\n- 内部靶标达成\n\n## 复现步骤\n\n1. 以 `svc_deploy` 登录并导出哈希:\n\n```\npsexec.py acme/svc_deploy@10.10.10.10\nsecretsdump.py acme/svc_deploy@10.10.10.10\n```\n\n2. 获得 DC01 的 SYSTEM 权限,导出域内全部哈希\n\n## 修复建议\n\n- 收敛服务账号权限,移出 Domain Admins,落实分层管理(tiering)\n- 连续两次重置 `krbtgt`,轮换服务账号强口令\n- 修复 DMZ→内网边界隔离与前述 RCE/提权链\n", summary: "拿下域控 DC01(Domain Admin)—— 内部靶标达成", evidence: "svc_deploy 属于 Domain Admins,psexec.py 以其登录 DC01 得 SYSTEM,secretsdump 导出全域 NTLM 哈希(含 krbtgt),完成靶标控制。", intent_id: "i13", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-26T00:20:00Z") }, ]; // ── Exploration graph (active task) ────────────────────────────────────────── diff --git a/web/src/lib/mock/handler.ts b/web/src/lib/mock/handler.ts index bda61d0..8b9f313 100644 --- a/web/src/lib/mock/handler.ts +++ b/web/src/lib/mock/handler.ts @@ -75,6 +75,15 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re if (path === "/exploration/frontier") return D.frontier; if (path === "/exploration/findings/stats") { const vulnclasses = Array.from(new Set(D.findings.map((f) => f.vulnclass))).sort(); + // 「按任务」下拉:有漏洞的任务 + 描述 + 条数(mock 任务 id 是字符串,直接当 id 用)。 + const taskMap = new Map(); + for (const f of D.findings) { + if (!f.task_id) continue; + const cur = taskMap.get(f.task_id) ?? { description: f.task_description ?? "", count: 0 }; + cur.count++; + taskMap.set(f.task_id, cur); + } + const tasks = Array.from(taskMap, ([id, v]) => ({ id, description: v.description, count: v.count })); return { total: D.findings.length, pending: D.findings.filter((f) => f.status === "pending").length, @@ -83,9 +92,10 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re medium: D.findings.filter((f) => f.severity === "medium").length, low: D.findings.filter((f) => f.severity === "low").length, vulnclasses, + tasks, }; } - // 单条 finding:GET 详情 / PATCH 改状态或严重度(demo 直接改内存对象)。 + // 单条 finding:GET 详情 / PATCH 改状态/严重度/名称/类别(demo 直接改内存对象)。 if ( seg[0] === "exploration" && seg[1] === "findings" && @@ -98,6 +108,8 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re if (typeof b.status === "string") f.status = b.status as typeof f.status; if (typeof b.severity === "string") f.severity = b.severity as typeof f.severity; + if (typeof b.name === "string") f.name = b.name; + if (typeof b.vulnclass === "string") f.vulnclass = b.vulnclass; } return { ...f, finding_id: f.id }; } @@ -117,9 +129,11 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re const fSev = q.get("severity"); const fStatus = q.get("status"); const fVuln = q.get("vulnclass"); + const fTask = q.get("task_id"); if (fSev) list = list.filter((f) => f.severity === fSev); if (fStatus) list = list.filter((f) => f.status === fStatus); if (fVuln) list = list.filter((f) => f.vulnclass === fVuln); + if (fTask) list = list.filter((f) => f.task_id === fTask); list.sort((a, b) => q.get("sort") === "severity" ? sev[b.severity] - sev[a.severity] || +new Date(b.ts) - +new Date(a.ts)