diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 756fcf4c..25a6864b 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -38,7 +38,7 @@ impl Detection { &common.detection(&event.system, &event_data); //&common.detection(&event.system, &event_data); if channel == "Security" { - &security.detection(event_id, &event.system, event_data); + &security.detection(event_id, &event.system, &event.user_data, event_data); } else if channel == "System" { &system.detection(event_id, &event.system, event_data); } else if channel == "Application" { diff --git a/src/detections/security.rs b/src/detections/security.rs index 16426937..6c40b07f 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -1,21 +1,45 @@ use crate::models::event; use std::collections::HashMap; +// eventlogが用意できていない +// 4674 +// 4756 + #[derive(Debug)] pub struct Security { + max_total_sensitive_privuse: i32, + max_passspray_login: i32, + max_passspray_uniquser: i32, + max_failed_logons: i32, alert_all_admin: i32, total_admin_logons: i32, + total_failed_logons: i32, + total_failed_account: i32, + total_sensitive_privuse: i32, admin_logons: HashMap>, multiple_admin_logons: HashMap, + account_2_failedcnt: HashMap, + passspray_2_user: HashMap, + empty_str: String, } impl Security { pub fn new() -> Security { Security { + max_total_sensitive_privuse: 4, + max_passspray_login: 6, + max_passspray_uniquser: 6, + max_failed_logons: 5, alert_all_admin: 0, total_admin_logons: 0, + total_failed_logons: 0, + total_failed_account: 0, + total_sensitive_privuse: 0, admin_logons: HashMap::new(), multiple_admin_logons: HashMap::new(), + account_2_failedcnt: HashMap::new(), + passspray_2_user: HashMap::new(), + empty_str: String::default(), } } @@ -23,25 +47,55 @@ impl Security { if self.total_admin_logons > 0 { println!("total_admin_logons:{}", self.total_admin_logons); println!("admin_logons:{:?}", self.admin_logons); - println!("multiple_admin_logons:{:?}", self.multiple_admin_logons); + println!("multiple_admin_logons:{:?}\n", self.multiple_admin_logons); + } + + let exceed_failed_logons = self.total_failed_logons > self.max_failed_logons; + let exist_failed_account = self.account_2_failedcnt.keys().count() as i32 > 1; + if exceed_failed_logons && exist_failed_account { + println!("High number of total logon failures for multiple accounts"); + println!( + "Total accounts: {}", + self.account_2_failedcnt.keys().count() + ); + println!("Total logon failures: {}\n", self.total_failed_logons); } } pub fn detection( &mut self, event_id: String, - system: &event::System, + _system: &event::System, + user_data: &Option, event_data: HashMap, ) { - if event_id == "4672" { - &self.se_debug_privilege(event_data); + self.process_created(&event_id, &event_data); + self.se_debug_privilege(&event_id, &event_data); + self.account_created(&event_id, &event_data); + self.add_member_security_group(&event_id, &event_data); + self.failed_logon(&event_id, &event_data); + self.sensitive_priviledge(&event_id, &event_data); + self.attempt_priviledge(&event_id, &event_data); + self.pass_spray(&event_id, &event_data); + self.audit_log_cleared(&event_id, &user_data); + } + + fn process_created(&mut self, event_id: &String, _event_data: &HashMap) { + if event_id != "4688" { + return; } + // TODO Check-Commnad + return; } // // Special privileges assigned to new logon (possible admin access) // - fn se_debug_privilege(&mut self, event_data: HashMap) { + fn se_debug_privilege(&mut self, event_id: &String, event_data: &HashMap) { + if event_id != "4672" { + return; + } + if let Some(privileage_list) = event_data.get("PrivilegeList") { if let Some(_data) = privileage_list.find("SeDebugPrivilege") { // alert_all_adminが有効であれば、標準出力して知らせる @@ -86,4 +140,166 @@ impl Security { } } } + + // account craeted:OK + fn account_created(&mut self, event_id: &String, event_data: &HashMap) { + if event_id != "4720" { + return; + } + + println!("New User Created"); + println!( + "Username: {}", + event_data.get("TargetUserName").unwrap_or(&"".to_string()) + ); + println!( + "User SID:: {}\n", + event_data.get("TargetSid").unwrap_or(&"".to_string()) + ); + } + + // add member to security group + fn add_member_security_group( + &mut self, + event_id: &String, + event_data: &HashMap, + ) { + // check if group is Administrator, may later expand to all groups + if event_data.get("TargetUserName").unwrap_or(&self.empty_str) != "Administrators" { + return; + } + + // A member was added to a security-enabled (global|local|universal) group. + if event_id == "4728" { + println!("User added to global Administrators group"); + } else if event_id == "4732" { + println!("User added to local Administrators group"); + } else if event_id == "4756" { + println!("User added to universal Administrators group"); + } else { + return; + } + + println!( + "Username: {}", + event_data.get("TargetUserName").unwrap_or(&"".to_string()) + ); + println!( + "User SID:: {}\n", + event_data.get("TargetSid").unwrap_or(&"".to_string()) + ); + } + + // An account failed to log on.:OK + // Requires auditing logon failures + // https://technet.microsoft.com/en-us/library/cc976395.aspx + fn failed_logon(&mut self, event_id: &String, event_data: &HashMap) { + if event_id != "4625" { + return; + } + + // see fn disp() + self.total_failed_logons += 1; + let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); + let failed_cnt = self.account_2_failedcnt.get(username).unwrap_or(&0) + 1; + self.account_2_failedcnt + .insert(username.to_string(), failed_cnt); + } + + // Sensitive Privilege Use (Mimikatz) + fn sensitive_priviledge(&mut self, event_id: &String, event_data: &HashMap) { + if event_id != "4673" { + return; + } + + self.total_sensitive_privuse += 1; + // use == operator here to avoid multiple log notices + if self.max_total_sensitive_privuse == self.total_sensitive_privuse { + println!("Sensititive Privilege Use Exceeds Threshold"); + println!( + "Username: {}", + event_data.get("SubjectUserName").unwrap_or(&self.empty_str) + ); + println!( + "Domain Name: {}", + event_data + .get("SubjectDomainName") + .unwrap_or(&self.empty_str) + ); + } + } + + fn attempt_priviledge(&mut self, _event_id: &String, _event_data: &HashMap) { + // event log cannot get... + } + + // A logon was attempted using explicit credentials. + fn pass_spray(&mut self, event_id: &String, event_data: &HashMap) { + if event_id != "4648" { + return; + } + + let targetusername = event_data.get("TargetUserName").unwrap_or(&self.empty_str); + let spray_cnt = self.passspray_2_user.get(targetusername).unwrap_or(&0) + 1; + self.passspray_2_user + .insert(targetusername.to_string(), spray_cnt); + + // check targetuser's attempt count. + if self.passspray_2_user.get(targetusername).unwrap_or(&0) <= &self.max_passspray_login { + return; + } + + // check exceeded targetuser count. + let spray_uniq_user = self + .passspray_2_user + .values() + .filter(|value| value > &&self.max_passspray_login) + .count() as i32; + if spray_uniq_user <= self.max_passspray_uniquser { + return; + } + + let usernames: String = self.passspray_2_user.keys().fold( + self.empty_str.to_string(), + |mut acc: String, cur| -> String { + acc.push_str(cur); + acc.push_str(" "); + return acc; + }, + ); + + println!("Distributed Account Explicit Credential Use (Password Spray Attack)"); + println!("The use of multiple user account access attempts with explicit credentials is "); + println!("an indicator of a password spray attack."); + println!("Target Usernames: {}", usernames.trim()); + println!( + "Accessing Username: {}", + event_data.get("SubjectUserName").unwrap_or(&self.empty_str) + ); + println!( + "Accessing Host Name: {}\n\n", + event_data + .get("SubjectDomainName") + .unwrap_or(&self.empty_str) + ); + + // reset + self.passspray_2_user = HashMap::new(); + } + + fn audit_log_cleared(&mut self, event_id: &String, user_data: &Option) { + if event_id != "1102" { + return; + } + + println!("Audit Log Clear"); + println!("The Audit log was cleared."); + + let username = user_data.as_ref().and_then(|u| { + u.log_file_cleared + .as_ref() + .and_then(|l| l.subject_user_name.as_ref()) + }); + println!("Security ID: {}", username.unwrap_or(&"".to_string())); + } } diff --git a/src/models/event.rs b/src/models/event.rs index 113157b6..aaea9312 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -72,12 +72,32 @@ pub struct EventData { pub data: Option>, } +#[derive(Debug, Deserialize, PartialEq)] +pub struct UserData { + #[serde(rename = "LogFileCleared")] + pub log_file_cleared: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct LogFileCleared { + #[serde(rename = "SubjectUserSid")] + pub subject_user_sid: Option, + #[serde(rename = "SubjectUserName")] + pub subject_user_name: Option, + #[serde(rename = "SubjectDomainName")] + pub subject_domain_name: Option, + #[serde(rename = "SubjectLogonId")] + pub subject_logon_id: Option, +} + #[derive(Debug, Deserialize, PartialEq)] pub struct Evtx { #[serde(rename = "System")] pub system: System, #[serde(rename = "EventData")] pub event_data: Option, + #[serde(rename = "UserData")] + pub user_data: Option, } impl Evtx {