1use regex::Regex;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub enum AttackTactic {
9 InitialAccess,
10 Execution,
11 Persistence,
12 PrivilegeEscalation,
13 DefenseEvasion,
14 CredentialAccess,
15 Discovery,
16 LateralMovement,
17 Collection,
18 Exfiltration,
19 Impact,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AttackTechnique {
25 pub id: String,
26 pub name: String,
27 pub tactic: AttackTactic,
28 pub description: String,
29}
30
31#[derive(Debug, Clone)]
33pub struct DetectionPattern {
34 pub technique: AttackTechnique,
35 pub pattern: Regex,
36 pub severity: ThreatSeverity,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40pub enum ThreatSeverity {
41 Low,
42 Medium,
43 High,
44 Critical,
45}
46
47pub struct MitreAttackDetector {
49 patterns: Vec<DetectionPattern>,
50}
51
52impl MitreAttackDetector {
53 #[allow(clippy::vec_init_then_push)]
55 pub fn new() -> Self {
56 let mut patterns = Vec::new();
57
58 patterns.push(DetectionPattern {
60 technique: AttackTechnique {
61 id: "T1110".to_string(),
62 name: "Brute Force".to_string(),
63 tactic: AttackTactic::CredentialAccess,
64 description: "Multiple failed authentication attempts".to_string(),
65 },
66 pattern: Regex::new(r"(?i)(failed|invalid|incorrect).*(login|auth|password)").unwrap(),
67 severity: ThreatSeverity::High,
68 });
69
70 patterns.push(DetectionPattern {
72 technique: AttackTechnique {
73 id: "T1059".to_string(),
74 name: "Command and Scripting Interpreter".to_string(),
75 tactic: AttackTactic::Execution,
76 description: "Execution of commands or scripts".to_string(),
77 },
78 pattern: Regex::new(r"(?i)(cmd\.exe|powershell|bash|sh|python|perl)").unwrap(),
79 severity: ThreatSeverity::Medium,
80 });
81
82 patterns.push(DetectionPattern {
84 technique: AttackTechnique {
85 id: "T1190".to_string(),
86 name: "Exploit Public-Facing Application".to_string(),
87 tactic: AttackTactic::InitialAccess,
88 description: "SQL injection or code injection attempts".to_string(),
89 },
90 pattern: Regex::new(r"(?i)(union\s+select|or\s+1\s*=\s*1|<script|eval\(|exec\()")
91 .unwrap(),
92 severity: ThreatSeverity::Critical,
93 });
94
95 patterns.push(DetectionPattern {
97 technique: AttackTechnique {
98 id: "T1078".to_string(),
99 name: "Valid Accounts".to_string(),
100 tactic: AttackTactic::InitialAccess,
101 description: "Use of valid accounts from unusual locations".to_string(),
102 },
103 pattern: Regex::new(r"(?i)(login|auth|signin).*(success|successful)").unwrap(),
104 severity: ThreatSeverity::Low,
105 });
106
107 patterns.push(DetectionPattern {
109 technique: AttackTechnique {
110 id: "T1071".to_string(),
111 name: "Application Layer Protocol".to_string(),
112 tactic: AttackTactic::InitialAccess,
113 description: "C2 communication over standard protocols".to_string(),
114 },
115 pattern: Regex::new(r"(?i)(http|https|ftp|dns|smtp).*(beacon|c2|command)").unwrap(),
116 severity: ThreatSeverity::High,
117 });
118
119 patterns.push(DetectionPattern {
121 technique: AttackTechnique {
122 id: "T1003".to_string(),
123 name: "OS Credential Dumping".to_string(),
124 tactic: AttackTactic::CredentialAccess,
125 description: "Attempts to dump credentials from OS".to_string(),
126 },
127 pattern: Regex::new(r"(?i)(mimikatz|lsass|sam|ntds|hashdump)").unwrap(),
128 severity: ThreatSeverity::Critical,
129 });
130
131 patterns.push(DetectionPattern {
133 technique: AttackTechnique {
134 id: "T1057".to_string(),
135 name: "Process Discovery".to_string(),
136 tactic: AttackTactic::Discovery,
137 description: "Enumeration of running processes".to_string(),
138 },
139 pattern: Regex::new(r"(?i)(tasklist|ps\s|get-process)").unwrap(),
140 severity: ThreatSeverity::Low,
141 });
142
143 patterns.push(DetectionPattern {
145 technique: AttackTechnique {
146 id: "T1083".to_string(),
147 name: "File and Directory Discovery".to_string(),
148 tactic: AttackTactic::Discovery,
149 description: "Enumeration of files and directories".to_string(),
150 },
151 pattern: Regex::new(r"(?i)(dir\s|ls\s|find\s|tree\s)").unwrap(),
152 severity: ThreatSeverity::Low,
153 });
154
155 patterns.push(DetectionPattern {
157 technique: AttackTechnique {
158 id: "T1486".to_string(),
159 name: "Data Encrypted for Impact".to_string(),
160 tactic: AttackTactic::Impact,
161 description: "Ransomware encryption activity".to_string(),
162 },
163 pattern: Regex::new(r"(?i)(ransom|encrypt|crypto|\.locked|\.encrypted)").unwrap(),
164 severity: ThreatSeverity::Critical,
165 });
166
167 patterns.push(DetectionPattern {
169 technique: AttackTechnique {
170 id: "T1041".to_string(),
171 name: "Exfiltration Over C2 Channel".to_string(),
172 tactic: AttackTactic::Exfiltration,
173 description: "Data exfiltration over command and control channel".to_string(),
174 },
175 pattern: Regex::new(r"(?i)(exfil|upload|post|send).*(data|file|document)").unwrap(),
176 severity: ThreatSeverity::High,
177 });
178
179 Self { patterns }
180 }
181
182 pub fn detect(&self, message: &str) -> Vec<ThreatDetection> {
184 let mut detections = Vec::new();
185
186 for pattern in &self.patterns {
187 if pattern.pattern.is_match(message) {
188 detections.push(ThreatDetection {
189 technique: pattern.technique.clone(),
190 severity: pattern.severity.clone(),
191 matched_text: message.to_string(),
192 timestamp: chrono::Utc::now(),
193 });
194 }
195 }
196
197 detections
198 }
199
200 pub fn get_techniques(&self) -> Vec<&AttackTechnique> {
202 self.patterns.iter().map(|p| &p.technique).collect()
203 }
204
205 pub fn get_techniques_by_tactic(&self, tactic: &AttackTactic) -> Vec<&AttackTechnique> {
207 self.patterns
208 .iter()
209 .filter(|p| &p.technique.tactic == tactic)
210 .map(|p| &p.technique)
211 .collect()
212 }
213}
214
215impl Default for MitreAttackDetector {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct ThreatDetection {
224 pub technique: AttackTechnique,
225 pub severity: ThreatSeverity,
226 pub matched_text: String,
227 pub timestamp: chrono::DateTime<chrono::Utc>,
228}
229
230impl ThreatDetection {
231 pub fn to_alert_message(&self) -> String {
233 format!(
234 "[{:?}] MITRE ATT&CK {} ({}) detected: {}",
235 self.severity, self.technique.id, self.technique.name, self.matched_text
236 )
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[test]
245 fn test_brute_force_detection() {
246 let detector = MitreAttackDetector::new();
247 let message = "Failed login attempt for user admin";
248
249 let detections = detector.detect(message);
250 assert!(!detections.is_empty());
251 assert_eq!(detections[0].technique.id, "T1110");
252 }
253
254 #[test]
255 fn test_sql_injection_detection() {
256 let detector = MitreAttackDetector::new();
257 let message = "GET /api/users?id=1 OR 1=1";
258
259 let detections = detector.detect(message);
260 assert!(!detections.is_empty());
261 assert_eq!(detections[0].technique.id, "T1190");
262 assert_eq!(detections[0].severity, ThreatSeverity::Critical);
263 }
264
265 #[test]
266 fn test_credential_dumping_detection() {
267 let detector = MitreAttackDetector::new();
268 let message = "mimikatz.exe executed on WORKSTATION-01";
269
270 let detections = detector.detect(message);
271 assert!(!detections.is_empty());
272 assert_eq!(detections[0].technique.id, "T1003");
273 }
274
275 #[test]
276 fn test_ransomware_detection() {
277 let detector = MitreAttackDetector::new();
278 let message = "Files encrypted by ransomware, pay bitcoin to decrypt";
279
280 let detections = detector.detect(message);
281 assert!(!detections.is_empty());
282 assert_eq!(detections[0].technique.id, "T1486");
283 assert_eq!(detections[0].severity, ThreatSeverity::Critical);
284 }
285
286 #[test]
287 fn test_command_execution_detection() {
288 let detector = MitreAttackDetector::new();
289 let message = "powershell.exe -ExecutionPolicy Bypass -File malware.ps1";
290
291 let detections = detector.detect(message);
292 assert!(!detections.is_empty());
293 assert_eq!(detections[0].technique.id, "T1059");
294 }
295
296 #[test]
297 fn test_no_detection() {
298 let detector = MitreAttackDetector::new();
299 let message = "User login successful";
300
301 let detections = detector.detect(message);
302 assert!(!detections.is_empty());
304 assert!(detections.iter().any(|d| d.technique.id == "T1078"));
305 }
306
307 #[test]
308 fn test_get_techniques_by_tactic() {
309 let detector = MitreAttackDetector::new();
310 let credential_access = detector.get_techniques_by_tactic(&AttackTactic::CredentialAccess);
311
312 assert!(!credential_access.is_empty());
313 assert!(credential_access.iter().any(|t| t.id == "T1110"));
314 }
315
316 #[test]
317 fn test_alert_message_generation() {
318 let detection = ThreatDetection {
319 technique: AttackTechnique {
320 id: "T1110".to_string(),
321 name: "Brute Force".to_string(),
322 tactic: AttackTactic::CredentialAccess,
323 description: "Test".to_string(),
324 },
325 severity: ThreatSeverity::High,
326 matched_text: "Failed login".to_string(),
327 timestamp: chrono::Utc::now(),
328 };
329
330 let alert = detection.to_alert_message();
331 assert!(alert.contains("T1110"));
332 assert!(alert.contains("Brute Force"));
333 assert!(alert.contains("Failed login"));
334 }
335
336 #[test]
337 fn test_multiple_detections() {
338 let detector = MitreAttackDetector::new();
339 let message = "mimikatz failed login powershell";
340
341 let detections = detector.detect(message);
342 assert!(detections.len() >= 2);
344 }
345}