rust_threat_detector/
mitre_attack.rs

1//! MITRE ATT&CK framework pattern detection
2
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5
6/// MITRE ATT&CK tactic
7#[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/// MITRE ATT&CK technique
23#[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/// Detection pattern
32#[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
47/// MITRE ATT&CK detector
48pub struct MitreAttackDetector {
49    patterns: Vec<DetectionPattern>,
50}
51
52impl MitreAttackDetector {
53    /// Create new detector with common patterns
54    #[allow(clippy::vec_init_then_push)]
55    pub fn new() -> Self {
56        let mut patterns = Vec::new();
57
58        // T1110 - Brute Force
59        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        // T1059 - Command and Scripting Interpreter
71        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        // T1190 - Exploit Public-Facing Application
83        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        // T1078 - Valid Accounts
96        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        // T1071 - Application Layer Protocol
108        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        // T1003 - OS Credential Dumping
120        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        // T1057 - Process Discovery
132        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        // T1083 - File and Directory Discovery
144        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        // T1486 - Data Encrypted for Impact
156        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        // T1041 - Exfiltration Over C2 Channel
168        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    /// Detect threats in log message
183    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    /// Get all supported techniques
201    pub fn get_techniques(&self) -> Vec<&AttackTechnique> {
202        self.patterns.iter().map(|p| &p.technique).collect()
203    }
204
205    /// Get techniques by tactic
206    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/// Threat detection result
222#[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    /// Generate alert message
232    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        // Should detect valid account usage (T1078) - matches pattern: (login|auth|signin).*(success|successful)
303        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        // Should match multiple patterns
343        assert!(detections.len() >= 2);
344    }
345}