1use serde::Serialize;
5
6#[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 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#[derive(Clone, Copy, Debug, Serialize)]
39pub struct Mitre {
40 pub id: &'static str,
41 pub name: &'static str,
42}
43
44pub mod mitre {
46 use super::Mitre;
47 pub const KERBEROASTING: Mitre = Mitre {
48 id: "T1558.003",
49 name: "Kerberoasting",
50 };
51 pub const ASREP_ROAST: Mitre = Mitre {
52 id: "T1558.004",
53 name: "AS-REP Roasting",
54 };
55 pub const GOLDEN_TICKET: Mitre = Mitre {
56 id: "T1558.001",
57 name: "Golden Ticket",
58 };
59 pub const SILVER_TICKET: Mitre = Mitre {
60 id: "T1558.002",
61 name: "Silver Ticket",
62 };
63 pub const DCSYNC: Mitre = Mitre {
64 id: "T1003.006",
65 name: "DCSync",
66 };
67 pub const DCSHADOW: Mitre = Mitre {
68 id: "T1207",
69 name: "Rogue Domain Controller",
70 };
71 pub const GPO_MOD: Mitre = Mitre {
72 id: "T1484.001",
73 name: "Group Policy Modification",
74 };
75 pub const TRUST_MOD: Mitre = Mitre {
76 id: "T1484.002",
77 name: "Domain Trust Modification",
78 };
79 pub const CERT_ABUSE: Mitre = Mitre {
80 id: "T1649",
81 name: "Steal or Forge Auth Certificates",
82 };
83 pub const VALID_ACCOUNTS: Mitre = Mitre {
84 id: "T1078",
85 name: "Valid Accounts",
86 };
87 pub const COERCION: Mitre = Mitre {
88 id: "T1187",
89 name: "Forced Authentication",
90 };
91}
92
93#[derive(Clone, Debug, Serialize)]
94pub struct Finding {
95 pub id: String, pub title: String,
97 pub category: Category,
98 pub severity: Severity,
99 pub mitre: Vec<Mitre>,
100 pub affected: Vec<String>,
102 pub detail: String,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub impact: Option<String>,
109 pub remediation: String,
110 #[serde(default)]
112 pub weight_bonus: u32,
113}
114
115impl Finding {
116 pub fn with_impact(mut self, impact: impl Into<String>) -> Self {
119 self.impact = Some(impact.into());
120 self
121 }
122}
123
124impl Finding {
125 pub fn score(&self) -> u32 {
126 self.severity.base_weight() + self.weight_bonus
127 }
128}
129
130#[derive(Clone, Debug, Serialize)]
131pub struct AttackResult {
132 pub command: String,
133 pub success: bool,
134 pub evidence: String,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub finding_id: Option<String>,
137}