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