package project import ( "fmt" "strings" ) // 事实 category 常量(写入 upsert_project_fact 的 category 字段)。 const ( FactCategoryTarget = "target" FactCategoryAuth = "auth" FactCategoryInfra = "infra" FactCategoryBusiness = "business" FactCategoryFinding = "finding" FactCategoryChain = "chain" FactCategoryExploit = "exploit" FactCategoryPOC = "poc" FactCategoryNote = "note" ) // RequiresAttackChainBody 判断该事实是否应携带可复现的攻击链 / exploit 详情(写在 body,非仅 summary)。 func RequiresAttackChainBody(category, factKey string) bool { c := strings.ToLower(strings.TrimSpace(category)) switch c { case FactCategoryFinding, FactCategoryChain, FactCategoryExploit, FactCategoryPOC, "vuln": return true } key := strings.ToLower(strings.TrimSpace(factKey)) for _, prefix := range []string{"finding/", "chain/", "exploit/", "poc/"} { if strings.HasPrefix(key, prefix) { return true } } return false } // IsSparseFactBody 攻击链类事实 body 过短或缺少关键段落时返回 true(软校验,不阻断写入)。 func IsSparseFactBody(category, factKey, body string) bool { if !RequiresAttackChainBody(category, factKey) { return false } body = strings.TrimSpace(body) if body == "" { return true } lower := strings.ToLower(body) // 至少应包含可复现线索:步骤/请求/命令/代码块 之一 hasSteps := strings.Contains(lower, "攻击链") || strings.Contains(lower, "## 攻击") || strings.Contains(lower, "## exploit") || strings.Contains(lower, "## poc") hasHTTP := strings.Contains(lower, "```http") || strings.Contains(lower, "```bash") || strings.Contains(lower, "curl ") || strings.Contains(lower, "get ") || strings.Contains(lower, "post ") hasReq := strings.Contains(lower, "请求") || strings.Contains(lower, "响应") || strings.Contains(lower, "payload") // 无攻击链/POC/请求等结构线索,视为仅结论性描述(不论长短) return !(hasSteps || hasHTTP || hasReq) } // FactBodyTemplate 按 category 返回建议的 body Markdown 骨架(供 Agent 填入真实内容)。 func FactBodyTemplate(category, factKey string) string { if RequiresAttackChainBody(category, factKey) { return attackChainFactBodyTemplate } return envFactBodyTemplate } const attackChainFactBodyTemplate = `## 结论(可验证,一句话) <勿仅写「存在漏洞」;写明类型 + 位置 + 触发条件> ## 目标与入口 - 目标: - 入口: <路径 / 接口 / 参数> - 前置条件: <匿名 / 角色 / Cookie / 其他依赖> ## 攻击链(逐步可复现) 1. <侦察/发现> 2. <利用/触发> 3. <影响证明(读文件、RCE 回显、越权数据等)> ## Exploit / POC ### 请求 ` + "```http\n HTTP/1.1\nHost: ...\n...\n\n\n```" + ` ### 响应 / 现象 <关键响应片段、状态码、差异点> ### 命令 / 脚本(如有) ` + "```bash\n\n```" + ` ## 关键证据 - <工具输出摘要 / 截图路径 / 会话或消息 ID> ## 关联 - related_vulnerability_id: <可选,对应 record_vulnerability 的 id> - 依赖事实: ## 备注与不确定性 <待验证假设、环境差异、绕过尝试记录>` const envFactBodyTemplate = `## 摘要 <该事实的核心认知> ## 细节 <端口/版本/路径/凭据特征/业务规则等> ## 来源与证据 <命令输出、响应片段、发现时间> ## 关联 - 相关 fact_key: <可选>` // FactRecordingGuidanceBlock 写入系统提示:要求事实沉淀攻击链上下文而非仅结论。 func FactRecordingGuidanceBlock() string { return `### 事实写入规范(审计复现 / 知识沉淀) - **summary**:索引用一行,须含「什么 + 在哪 + 如何触发/验证」要点,禁止只写结论(如仅写「存在 SQLi」)。 - **body**:完整可复现上下文,写入 ` + "`upsert_project_fact`" + ` 的 body 字段;索引不含 body,后续会话须靠 ` + "`get_project_fact`" + ` 取回。 - **category / fact_key 建议**: - 环境认知:` + "`target/`" + `、` + "`auth/`" + `、` + "`infra/`" + `、` + "`business/`" + `(body 用环境模板即可) - 发现与利用:` + "`finding/`" + `、` + "`chain/`" + `、` + "`exploit/`" + `、` + "`poc/`" + `(**必须**用攻击链模板填满 body:入口、逐步攻击链、原始请求/响应或命令、证据、关联漏洞 ID) - **与漏洞记录分工**:` + "`record_vulnerability`" + ` 记可交付 findings;事实记**复现所需的全部上下文**(含失败尝试、绕过、依赖会话),二者可各记一次。 - 更新同一发现时保持相同 ` + "`fact_key`" + ` 覆盖写入,勿散落多个 key 导致上下文丢失。` } // SparseBodyWarning 攻击链类事实 body 不足时的工具返回提示(不阻断保存)。 func SparseBodyWarning(category, factKey string) string { if !IsSparseFactBody(category, factKey, "") { return "" } return fmt.Sprintf( "\n\n⚠ 提示:category=%q / fact_key=%q 属于攻击链类事实,但 body 为空或过简。请补充完整攻击链与 POC(参考模板),便于后续审计复现。\n建议 body 骨架:\n%s", category, factKey, FactBodyTemplate(category, factKey), ) } // SparseBodyWarningIfNeeded 根据实际 body 判断是否追加警告。 func SparseBodyWarningIfNeeded(category, factKey, body string) string { if !IsSparseFactBody(category, factKey, body) { return "" } return SparseBodyWarning(category, factKey) }