134 lines
4.7 KiB
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
|
|
}
|