Skip to main content

adhammer_core/
finding.rs

1//! The output vocabulary: a Finding is one rule firing, tagged with a PingCastle-style
2//! category, a severity, and one or more MITRE ATT&CK techniques.
3
4use serde::Serialize;
5
6/// The four PingCastle top-level categories.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
8pub enum Category {
9    PrivilegedAccounts,
10    Trusts,
11    StaleObjects,
12    Anomalies,
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
16pub enum Severity {
17    Info = 0,
18    Low = 1,
19    Medium = 2,
20    High = 3,
21    Critical = 4,
22}
23
24impl Severity {
25    /// Base weight fed into the risk engine (overridable via config).
26    pub fn base_weight(self) -> u32 {
27        match self {
28            Severity::Info => 0,
29            Severity::Low => 5,
30            Severity::Medium => 15,
31            Severity::High => 30,
32            Severity::Critical => 50,
33        }
34    }
35}
36
37/// MITRE ATT&CK technique reference, e.g. ("T1558.003", "Kerberoasting").
38#[derive(Clone, Copy, Debug, Serialize)]
39pub struct Mitre {
40    pub id: &'static str,
41    pub name: &'static str,
42}
43
44/// Common techniques, referenced by checks so the mapping lives in one place.
45pub mod mitre {
46    use super::Mitre;
47    pub const KERBEROASTING: Mitre = Mitre { id: "T1558.003", name: "Kerberoasting" };
48    pub const ASREP_ROAST: Mitre = Mitre { id: "T1558.004", name: "AS-REP Roasting" };
49    pub const GOLDEN_TICKET: Mitre = Mitre { id: "T1558.001", name: "Golden Ticket" };
50    pub const SILVER_TICKET: Mitre = Mitre { id: "T1558.002", name: "Silver Ticket" };
51    pub const DCSYNC: Mitre = Mitre { id: "T1003.006", name: "DCSync" };
52    pub const DCSHADOW: Mitre = Mitre { id: "T1207", name: "Rogue Domain Controller" };
53    pub const GPO_MOD: Mitre = Mitre { id: "T1484.001", name: "Group Policy Modification" };
54    pub const TRUST_MOD: Mitre = Mitre { id: "T1484.002", name: "Domain Trust Modification" };
55    pub const CERT_ABUSE: Mitre = Mitre { id: "T1649", name: "Steal or Forge Auth Certificates" };
56    pub const VALID_ACCOUNTS: Mitre = Mitre { id: "T1078", name: "Valid Accounts" };
57    pub const COERCION: Mitre = Mitre { id: "T1187", name: "Forced Authentication" };
58}
59
60#[derive(Clone, Debug, Serialize)]
61pub struct Finding {
62    pub id: String,            // stable rule id, e.g. "P-KerberoastAdmin"
63    pub title: String,
64    pub category: Category,
65    pub severity: Severity,
66    pub mitre: Vec<Mitre>,
67    /// DNs / SIDs the finding points at.
68    pub affected: Vec<String>,
69    pub detail: String,
70    pub remediation: String,
71    /// Extra weight beyond the severity base (e.g. per-object scaling).
72    #[serde(default)]
73    pub weight_bonus: u32,
74}
75
76impl Finding {
77    pub fn score(&self) -> u32 {
78        self.severity.base_weight() + self.weight_bonus
79    }
80}