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)]
98pub struct Evidence {
99 pub source: String,
104 pub value: String,
106}
107
108impl Evidence {
109 pub fn new(source: impl Into<String>, value: impl Into<String>) -> Self {
110 Self {
111 source: source.into(),
112 value: value.into(),
113 }
114 }
115}
116
117#[derive(Clone, Debug, Serialize)]
118pub struct Finding {
119 pub id: String, pub title: String,
121 pub category: Category,
122 pub severity: Severity,
123 pub mitre: Vec<Mitre>,
124 pub affected: Vec<String>,
126 pub detail: String,
128 #[serde(default, skip_serializing_if = "Vec::is_empty")]
132 pub evidence: Vec<Evidence>,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
137 pub impact: Option<String>,
138 pub remediation: String,
139 #[serde(default)]
141 pub weight_bonus: u32,
142}
143
144impl Finding {
145 pub fn with_impact(mut self, impact: impl Into<String>) -> Self {
148 self.impact = Some(impact.into());
149 self
150 }
151
152 pub fn with_evidence(mut self, source: impl Into<String>, value: impl Into<String>) -> Self {
154 self.evidence.push(Evidence::new(source, value));
155 self
156 }
157
158 pub fn with_evidences(mut self, ev: impl IntoIterator<Item = Evidence>) -> Self {
160 self.evidence.extend(ev);
161 self
162 }
163}
164
165impl Finding {
166 pub fn score(&self) -> u32 {
167 self.severity.base_weight() + self.weight_bonus
168 }
169}
170
171#[derive(Clone, Debug, Serialize)]
172pub struct AttackResult {
173 pub command: String,
174 pub success: bool,
175 pub evidence: String,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub finding_id: Option<String>,
178}