feat: 漏洞新增 severity=critical(严重) 与 name(漏洞名称)字段
severity 严重级: - report_finding 工具 severity 说明改为 critical|high|medium|low - DB 排序 CASE、report 报告 sevRank、FindingStats 增加 critical - 前端 Severity 类型、status.ts 新增 rose 色调(实心高强调,视觉高于高危)、 发现页严重度筛选/统计卡、任务发现 Tab 排序、dashboard 汇总均加入严重 name 漏洞名称: - findings 表加 name 列(schema + ALTER TABLE ADD COLUMN IF NOT EXISTS 补旧库) - report_finding 工具新增 name 入参(非必填),写入 node payload 与独立 findings 行 - DBFinding/AddFinding/AddStandaloneFinding、FindingDTO、findingPayload、report 全链路透传 - 发现列表页与任务发现 Tab 展示 name,为空时回退 vulnclass;vulnclass 移到展开详情「类型」 - report 报告标题优先 name,回退 vulnclass 无 CHECK 约束,旧库仅需补 name 列;critical/name 均向后兼容。 db 测试补充 critical 排序/统计与 name 往返断言。
This commit is contained in:
1 parent
7b3eec2166
commit
ff3d41ea35
14 files changed
+96
-56
No files matched your search
@@ -239,7 +239,7 @@ export default function DashboardPage() {
|
||||
}, [tasks]);
|
||||
|
||||
const findingsBySev = React.useMemo(() => {
|
||||
const m = { high: 0, medium: 0, low: 0 };
|
||||
const m = { critical: 0, high: 0, medium: 0, low: 0 };
|
||||
for (const f of findings) {
|
||||
if (f.severity in m) m[f.severity as keyof typeof m]++;
|
||||
}
|
||||
@@ -491,7 +491,8 @@ export default function DashboardPage() {
|
||||
{findings.length}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-2.5 text-[10px]">
|
||||
<CardContent className="flex flex-wrap gap-2.5 text-[10px]">
|
||||
<span className="text-rose-500">严重 {findingsBySev.critical}</span>
|
||||
<span className="text-red-400">高危 {findingsBySev.high}</span>
|
||||
<span className="text-amber-400">中危 {findingsBySev.medium}</span>
|
||||
<span className="text-slate-400">低危 {findingsBySev.low}</span>
|
||||
|
||||
@@ -52,6 +52,7 @@ const FINDING_STATUSES: FindingStatus[] = [
|
||||
const EMPTY_STATS: FindingStats = {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
critical: 0,
|
||||
high: 0,
|
||||
medium: 0,
|
||||
low: 0,
|
||||
@@ -156,6 +157,7 @@ export default function FindingsPage() {
|
||||
const statCards: { label: string; value: number; tone?: string }[] = [
|
||||
{ label: "发现总数", value: stats.total },
|
||||
{ label: "待处理", value: stats.pending, tone: "text-amber-500" },
|
||||
{ label: "严重", value: stats.critical, tone: "text-rose-600" },
|
||||
{ label: "高危", value: stats.high, tone: "text-red-500" },
|
||||
{ label: "中危", value: stats.medium, tone: "text-amber-500" },
|
||||
{ label: "低危", value: stats.low, tone: "text-slate-500" },
|
||||
@@ -168,7 +170,7 @@ export default function FindingsPage() {
|
||||
<p className="text-muted-foreground text-sm">跨任务漏洞汇总</p>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col gap-4 md:gap-6">
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5">
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
||||
{statCards.map((s) => (
|
||||
<Card key={s.label} className="gap-1 py-4">
|
||||
<CardHeader className="px-4">
|
||||
@@ -186,6 +188,7 @@ export default function FindingsPage() {
|
||||
{(
|
||||
[
|
||||
["all", "全部"],
|
||||
["critical", "严重"],
|
||||
["high", "高危"],
|
||||
["medium", "中危"],
|
||||
["low", "低危"],
|
||||
@@ -293,7 +296,7 @@ export default function FindingsPage() {
|
||||
<TableCell className="max-w-md">
|
||||
<div className="flex min-w-0 flex-col gap-0.5">
|
||||
<span className="truncate font-medium">
|
||||
{f.vulnclass || "未分类"}
|
||||
{f.name || f.vulnclass || "未分类"}
|
||||
</span>
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{f.summary}
|
||||
@@ -380,6 +383,14 @@ export default function FindingsPage() {
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
<ShieldAlertIcon className="size-3.5" />
|
||||
证据 · {f.id}
|
||||
{f.vulnclass && (
|
||||
<span>
|
||||
· 类型:
|
||||
<code className="rounded bg-muted px-1.5 py-0.5 font-mono">
|
||||
{f.vulnclass}
|
||||
</code>
|
||||
</span>
|
||||
)}
|
||||
{f.param_id && (
|
||||
<code className="rounded bg-muted px-1.5 py-0.5 font-mono">
|
||||
{f.param_id}
|
||||
|
||||
@@ -52,7 +52,7 @@ function Row({
|
||||
<StatusBadge domain="severity" value={f.severity} dot />
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="truncate font-medium">
|
||||
{f.vulnclass || "未分类"}
|
||||
{f.name || f.vulnclass || "未分类"}
|
||||
</span>
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
{f.summary}
|
||||
@@ -161,7 +161,7 @@ export function FindingsTab({ taskId }: { taskId: string }) {
|
||||
const items = findings
|
||||
.filter((f) => f.task_id === taskId)
|
||||
.sort((a, b) => {
|
||||
const order = { high: 0, medium: 1, low: 2 };
|
||||
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
||||
return order[a.severity] - order[b.severity];
|
||||
});
|
||||
|
||||
|
||||
@@ -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", 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", 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") },
|
||||
@@ -173,12 +173,12 @@ export const findings: Finding[] = [
|
||||
{ id: "f-10", vulnclass: "Outdated Component", severity: "medium", status: "pending", summary: "shop 使用存在已知 RCE 的老版本组件", evidence: "指纹识别到组件 v2.3.1,对应 CVE-2024-xxxx 反序列化 RCE。", task_id: "t-shop-pay", task_description: "shop.acme.com 支付与订单链路", ts: T("2026-07-23T15:00:00Z") },
|
||||
// ── 外网→内网纵深链路上的高危发现(active task)──
|
||||
{ id: "f-11", vulnclass: "Hardcoded Credentials", severity: "high", status: "pending", summary: "泄露源码中硬编码数据库凭据 sa/Acme@2021", evidence: "git-dumper 还原 www.acme.com/.git 得到 config.php,内含明文 DB 口令,后续被证实可在内网复用。", intent_id: "ig", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T20:36:00Z") },
|
||||
{ id: "f-12", vulnclass: "Deserialization RCE", severity: "high", status: "pending", summary: "shop.acme.com Fastjson 1.2.24 反序列化远程命令执行", evidence: "POST /api/import 提交 JNDI payload(@type:JdbcRowSetImpl)→ 回连 dnslog 并落地反弹 shell(www-data@dmz-web01)。", intent_id: "i7", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T15:40:00Z") },
|
||||
{ id: "f-12", vulnclass: "Deserialization RCE", name: "shop 商城 Fastjson 反序列化远程命令执行", severity: "critical", status: "confirmed", summary: "shop.acme.com Fastjson 1.2.24 反序列化远程命令执行", evidence: "POST /api/import 提交 JNDI payload(@type:JdbcRowSetImpl)→ 回连 dnslog 并落地反弹 shell(www-data@dmz-web01)。", intent_id: "i7", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T15:40:00Z") },
|
||||
{ id: "f-13", vulnclass: "Privilege Escalation", severity: "high", status: "pending", summary: "DMZ 立足点 sudo NOPASSWD 错配可本地提权 root", evidence: "sudo -l 显示 (ALL) NOPASSWD: /usr/bin/python3 → sudo python3 -c 'os.setuid(0);os.system(\"/bin/sh\")' 直接拿 root。", intent_id: "i8", task_id: "t-acme-web", task_description: "Acme 官网与后台外部渗透", ts: T("2026-07-25T16:20:00Z") },
|
||||
{ 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", severity: "high", status: "pending", 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", 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) ──────────────────────────────────────────
|
||||
|
||||
@@ -78,6 +78,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
return {
|
||||
total: D.findings.length,
|
||||
pending: D.findings.filter((f) => f.status === "pending").length,
|
||||
critical: D.findings.filter((f) => f.severity === "critical").length,
|
||||
high: D.findings.filter((f) => f.severity === "high").length,
|
||||
medium: D.findings.filter((f) => f.severity === "medium").length,
|
||||
low: D.findings.filter((f) => f.severity === "low").length,
|
||||
@@ -88,7 +89,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
if (task) return D.findings.filter((f) => f.task_id === task);
|
||||
// 全局:带 page/limit → 分页对象;否则裸数组(dashboard)。
|
||||
if (!q.has("page") && !q.has("limit")) return D.findings;
|
||||
const sev = { high: 3, medium: 2, low: 1 } as const;
|
||||
const sev = { critical: 4, high: 3, medium: 2, low: 1 } as const;
|
||||
let list = D.findings.slice();
|
||||
const fSev = q.get("severity");
|
||||
const fStatus = q.get("status");
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Tone =
|
||||
| "green"
|
||||
| "amber"
|
||||
| "red"
|
||||
| "rose"
|
||||
| "violet"
|
||||
| "slate";
|
||||
|
||||
@@ -19,6 +20,8 @@ export const toneClasses: Record<Tone, string> = {
|
||||
amber:
|
||||
"bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/20",
|
||||
red: "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20",
|
||||
// rose 用作「严重」——实心高强调,视觉上明显高于「高危」的软红描边。
|
||||
rose: "bg-rose-600 text-white border-rose-600 dark:bg-rose-600 dark:text-white",
|
||||
violet:
|
||||
"bg-violet-500/15 text-violet-600 dark:text-violet-400 border-violet-500/20",
|
||||
slate:
|
||||
@@ -31,6 +34,7 @@ export const toneDot: Record<Tone, string> = {
|
||||
green: "bg-emerald-500",
|
||||
amber: "bg-amber-500",
|
||||
red: "bg-red-500",
|
||||
rose: "bg-white",
|
||||
violet: "bg-violet-500",
|
||||
slate: "bg-slate-500",
|
||||
};
|
||||
@@ -58,6 +62,7 @@ const task: Record<string, StatusMeta> = {
|
||||
};
|
||||
|
||||
const severity: Record<string, StatusMeta> = {
|
||||
critical: { label: "严重", tone: "rose" },
|
||||
high: { label: "高危", tone: "red" },
|
||||
medium: { label: "中危", tone: "amber" },
|
||||
low: { label: "低危", tone: "slate" },
|
||||
|
||||
@@ -237,7 +237,7 @@ export interface TaskNode {
|
||||
}
|
||||
|
||||
// ---- Findings ----
|
||||
export type Severity = "high" | "medium" | "low";
|
||||
export type Severity = "critical" | "high" | "medium" | "low";
|
||||
|
||||
// 漏洞处置状态:待处理 / 处理中 / 已确认 / 已处理 / 误报 / 忽略 / 重复 / 风险接受。
|
||||
export type FindingStatus =
|
||||
@@ -261,6 +261,7 @@ export interface Finding {
|
||||
id: string;
|
||||
finding_id?: string; // 独立 findings 表的行 id,状态更新的句柄(任务内旧节点可能缺失)
|
||||
vulnclass: string;
|
||||
name?: string; // 漏洞名称;为空时展示回退到 vulnclass
|
||||
severity: Severity;
|
||||
status: FindingStatus;
|
||||
summary: string;
|
||||
@@ -285,6 +286,7 @@ export interface FindingsPage {
|
||||
export interface FindingStats {
|
||||
total: number;
|
||||
pending: number;
|
||||
critical: number;
|
||||
high: number;
|
||||
medium: number;
|
||||
low: number;
|
||||
|
||||
Reference in new issue
Block a user