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 往返断言。
100 lines
2.5 KiB
Go
100 lines
2.5 KiB
Go
// Package report renders a deliverable Markdown report from the exploration
|
||
// graph: confirmed findings (with severity/evidence) (docs §15 P5).
|
||
package report
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/Autumn-27/artex/db"
|
||
)
|
||
|
||
// Input bundles what the report needs.
|
||
type Input struct {
|
||
Title string
|
||
Goal string
|
||
GeneratedAt time.Time
|
||
AssetCounts map[string]int
|
||
Findings []*db.Node
|
||
}
|
||
|
||
type findingView struct {
|
||
VulnClass string
|
||
Name string
|
||
Severity string
|
||
Summary string
|
||
PoC string
|
||
}
|
||
|
||
func parseFinding(n *db.Node) findingView {
|
||
var p struct {
|
||
VulnClass string `json:"vulnclass"`
|
||
Name string `json:"name"`
|
||
Severity string `json:"severity"`
|
||
Summary string `json:"summary"`
|
||
Evidence struct {
|
||
PoC string `json:"poc"`
|
||
} `json:"evidence"`
|
||
}
|
||
_ = json.Unmarshal(n.Payload, &p)
|
||
return findingView{p.VulnClass, p.Name, p.Severity, p.Summary, p.Evidence.PoC}
|
||
}
|
||
|
||
var sevRank = map[string]int{"critical": 0, "high": 1, "medium": 2, "low": 3, "": 4}
|
||
|
||
// Markdown renders the report.
|
||
func Markdown(in Input) string {
|
||
var b strings.Builder
|
||
fmt.Fprintf(&b, "# 渗透测试报告 — %s\n\n", nz(in.Title, "未命名任务"))
|
||
fmt.Fprintf(&b, "- **任务目标**:%s\n", nz(in.Goal, "(未指定)"))
|
||
fmt.Fprintf(&b, "- **生成时间**:%s\n\n", in.GeneratedAt.Format("2006-01-02 15:04:05"))
|
||
|
||
// summary
|
||
fmt.Fprintf(&b, "## 摘要\n\n")
|
||
fmt.Fprintf(&b, "- 确认发现:**%d** 个\n", len(in.Findings))
|
||
fmt.Fprintf(&b, "- 资产:")
|
||
var types []string
|
||
for t := range in.AssetCounts {
|
||
types = append(types, t)
|
||
}
|
||
sort.Strings(types)
|
||
for i, t := range types {
|
||
if i > 0 {
|
||
b.WriteString("、")
|
||
}
|
||
fmt.Fprintf(&b, "%s %d", t, in.AssetCounts[t])
|
||
}
|
||
b.WriteString("\n\n")
|
||
|
||
// findings
|
||
fmt.Fprintf(&b, "## 发现\n\n")
|
||
if len(in.Findings) == 0 {
|
||
b.WriteString("_本次未确认漏洞。_\n\n")
|
||
} else {
|
||
fs := make([]findingView, 0, len(in.Findings))
|
||
for _, n := range in.Findings {
|
||
fs = append(fs, parseFinding(n))
|
||
}
|
||
sort.SliceStable(fs, func(i, j int) bool { return sevRank[fs[i].Severity] < sevRank[fs[j].Severity] })
|
||
for i, f := range fs {
|
||
fmt.Fprintf(&b, "### %d. [%s] %s\n\n", i+1, strings.ToUpper(nz(f.Severity, "info")), nz(f.Name, nz(f.VulnClass, "未分类")))
|
||
fmt.Fprintf(&b, "%s\n\n", nz(f.Summary, ""))
|
||
if f.PoC != "" {
|
||
fmt.Fprintf(&b, "**PoC / 证据:**\n\n```\n%s\n```\n\n", f.PoC)
|
||
}
|
||
}
|
||
}
|
||
|
||
return b.String()
|
||
}
|
||
|
||
func nz(s, d string) string {
|
||
if strings.TrimSpace(s) == "" {
|
||
return d
|
||
}
|
||
return s
|
||
}
|