Files
AetherForge/server/internal/vuln/correlator.go

134 lines
4.7 KiB
Go

package vuln
import (
"strings"
)
// Finding mirrors agent vuln_findings JSON for server-side enrichment.
type Finding struct {
CVEID string `json:"cve_id"`
Severity string `json:"severity"`
Component string `json:"component"`
Patched bool `json:"patched"`
ExploitableInFleetContext bool `json:"exploitable_in_fleet_context"`
Detail string `json:"detail,omitempty"`
}
// CatalogEntry is a lightweight embedded CVE rule for fleet correlator.
type CatalogEntry struct {
ID string `json:"id"`
Name string `json:"name"`
Component string `json:"component"`
Severity string `json:"severity"`
FleetPorts []int `json:"fleet_ports,omitempty"`
PatchKBs []string `json:"patch_kbs,omitempty"`
}
// EmbeddedCatalog is served by GET /api/v1/vuln/catalog (cached JSON).
var EmbeddedCatalog = []CatalogEntry{
{ID: "CVE-2021-44228", Name: "Log4Shell", Component: "Apache Log4j", Severity: "critical"},
{ID: "CVE-2021-26855", Name: "ProxyLogon", Component: "Microsoft Exchange", Severity: "critical", FleetPorts: []int{443, 80}, PatchKBs: []string{"KB5000871"}},
{ID: "CVE-2020-1472", Name: "Zerologon", Component: "Microsoft Netlogon", Severity: "critical", FleetPorts: []int{445, 135}, PatchKBs: []string{"KB4577015"}},
{ID: "CVE-2019-19781", Name: "Citrix ADC", Component: "Citrix ADC/Gateway", Severity: "critical", FleetPorts: []int{443}},
{ID: "CVE-2019-11510", Name: "Pulse Secure", Component: "Ivanti Pulse Connect Secure", Severity: "critical", FleetPorts: []int{443}},
{ID: "CVE-2020-5902", Name: "F5 BIG-IP", Component: "F5 BIG-IP", Severity: "critical", FleetPorts: []int{443, 8443}},
{ID: "CVE-2022-1388", Name: "F5 iControl", Component: "F5 BIG-IP", Severity: "critical", FleetPorts: []int{443, 8443}},
{ID: "CVE-2021-26084", Name: "Confluence OGNL", Component: "Atlassian Confluence", Severity: "critical", FleetPorts: []int{8090, 8443}},
{ID: "CVE-2022-26134", Name: "Confluence RCE", Component: "Atlassian Confluence", Severity: "critical", FleetPorts: []int{8090, 8443}},
{ID: "CVE-2021-40539", Name: "ManageEngine", Component: "Zoho ManageEngine ADSelfService Plus", Severity: "critical", FleetPorts: []int{9251}},
{ID: "CVE-2018-13379", Name: "FortiOS path traversal", Component: "Fortinet FortiGate/FortiOS", Severity: "critical", FleetPorts: []int{443, 10443}},
{ID: "CVE-2021-34527", Name: "PrintNightmare", Component: "Windows Print Spooler", Severity: "high", FleetPorts: []int{445, 135}, PatchKBs: []string{"KB5004945"}},
{ID: "CVE-2020-0688", Name: "Exchange RCE", Component: "Microsoft Exchange", Severity: "high", FleetPorts: []int{443}},
{ID: "CVE-2021-21972", Name: "vCenter RCE", Component: "VMware vCenter", Severity: "critical", FleetPorts: []int{443}},
}
// FleetContext carries server-known signals for correlator enrichment.
type FleetContext struct {
ListenPortCount int
PathTracerPorts []int
SSHAvailable bool
OSVersion string
}
// EnrichFindings applies fleet-context rules (open ports from Path Tracer when available).
func EnrichFindings(findings []Finding, ctx FleetContext) []Finding {
if len(findings) == 0 {
return findings
}
portSet := make(map[int]bool)
for _, p := range ctx.PathTracerPorts {
portSet[p] = true
}
rules := catalogByID()
out := make([]Finding, len(findings))
copy(out, findings)
for i := range out {
if out[i].Patched || out[i].ExploitableInFleetContext {
continue
}
rule, ok := rules[out[i].CVEID]
if !ok {
continue
}
for _, p := range rule.FleetPorts {
if portSet[p] {
out[i].ExploitableInFleetContext = true
out[i].Detail = strings.TrimSpace(out[i].Detail + " · Path Tracer hop port " + itoa(p) + " open")
break
}
}
if !out[i].ExploitableInFleetContext && ctx.SSHAvailable && ctx.ListenPortCount > 0 {
for _, p := range rule.FleetPorts {
if p == 22 || p == 445 || p == 443 {
out[i].ExploitableInFleetContext = true
out[i].Detail = strings.TrimSpace(out[i].Detail + " · fleet SSH/listener context")
break
}
}
}
}
return out
}
func catalogByID() map[string]CatalogEntry {
m := make(map[string]CatalogEntry, len(EmbeddedCatalog))
for _, e := range EmbeddedCatalog {
m[e.ID] = e
}
return m
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var b [12]byte
i := len(b)
for n > 0 {
i--
b[i] = byte('0' + n%10)
n /= 10
}
return string(b[i:])
}
// RiskScore computes a 0-100 score from enriched findings.
func RiskScore(findings []Finding) int {
score := 0
for _, f := range findings {
if f.ExploitableInFleetContext {
if f.Severity == "critical" {
score += 25
} else {
score += 12
}
} else if !f.Patched {
score += 8
}
}
if score > 100 {
return 100
}
return score
}