fix(mock): 补全发现页今日改动的 demo 数据

- findingStats 返回 tasks(按任务筛选下拉);findingsPage 支持 task_id 过滤
- PATCH finding 支持改 name/vulnclass(行内编辑)
- 给 f-1/f-17 补详细报告 Markdown,行内看报告在 demo 里可见(3 条)
This commit is contained in:
Autumn-27 committed 2026-08-18 13:33:04 -04:00
1 parent d9474814b5
commit e67ff26e90
2 files changed
+17 -3

No files matched your search

+2 -2
View File
@@ -161,7 +161,7 @@ export const assetCounts: Record<string, number> = assets.reduce<Record<string,
// ── Findings ─────────────────────────────────────────────────────────────────
export const findings: Finding[] = [
{ id: "f-1", vulnclass: "SQL Injection", name: "官网搜索接口报错型 SQL 注入", severity: "high", status: "pending", summary: "www.acme.com/search q 参数存在报错型 SQL 注入", evidence: "GET /search?q=1' AND 1=CONVERT(int,@@version)-- → 返回 MSSQL 版本报错,可读库结构。", intent_id: "i-2", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-26T01:20:00Z") },
{ id: "f-1", vulnclass: "SQL Injection", name: "官网搜索接口报错型 SQL 注入", severity: "high", status: "pending", report: "## 漏洞概述\n\n`www.acme.com/search` 的 `q` 参数存在**报错型 SQL 注入**(MSSQL),可读取数据库版本、库结构乃至敏感数据。\n\n## 影响\n\n- 可读取 `acme_prod` 库的表结构与用户/订单数据\n- 报错回显便于快速构造利用,风险高\n\n## 复现步骤\n\n1. 触发报错注入:\n\n```\nGET /search?q=1' AND 1=CONVERT(int,@@version)--\n```\n\n2. 响应回显 MSSQL 版本报错,确认可注入\n3. 进一步枚举库:`sqlmap -u \"https://www.acme.com/search?q=1\" --dbs`\n\n## 修复建议\n\n- 全部改用参数化查询/预编译语句,杜绝字符串拼接\n- 关闭生产环境详细报错回显\n- 数据库账号最小权限,禁用 `xp_cmdshell` 等危险扩展\n", summary: "www.acme.com/search q 参数存在报错型 SQL 注入", evidence: "GET /search?q=1' AND 1=CONVERT(int,@@version)-- → 返回 MSSQL 版本报错,可读库结构。", intent_id: "i-2", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-26T01:20:00Z") },
{ id: "f-2", vulnclass: "IDOR", severity: "high", status: "pending", summary: "api.acme.com/v1/orders?id= 可越权读取他人订单", evidence: "将 id=1001 改为 id=1002 返回他人订单(含收货地址、手机号),无归属校验。", intent_id: "i-5", task_id: "t-acme-api", task_description: "api.acme.com 越权与注入测试", ts: T("2026-07-26T02:44:00Z") },
{ id: "f-3", vulnclass: "Weak JWT", severity: "high", status: "pending", summary: "API JWT 使用弱密钥、可离线爆破伪造", evidence: "HS256,密钥 'secret',john 5 秒破解 → 可伪造任意 sub 越权。", task_id: "t-acme-api", task_description: "api.acme.com 越权与注入测试", ts: T("2026-07-26T03:02:00Z") },
{ id: "f-4", vulnclass: "Reflected XSS", severity: "medium", status: "pending", summary: "搜索页对 q 参数未转义,反射型 XSS", evidence: "q=<script>alert(document.domain)</script> 原样回显于结果标题。", 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/[email protected]\nsecretsdump.py acme/[email protected]\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) ──────────────────────────────────────────
+15 -1
View File
@@ -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<string, { description: string; count: number }>();
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)