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 82e23f71..ec3d72d1 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -1,92 +1,308 @@ -use crate::models::event; -use std::collections::HashMap; - -#[derive(Debug)] -pub struct Security { - alert_all_admin: i32, - total_admin_logons: i32, - admin_logons: HashMap>, - multiple_admin_logons: HashMap, -} - -impl Security { - pub fn new() -> Security { - Security { - alert_all_admin: 0, - total_admin_logons: 0, - admin_logons: HashMap::new(), - multiple_admin_logons: HashMap::new(), - } - } - - pub fn disp(&self) { - 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); - } - } - - pub fn detection( - &mut self, - event_id: String, - system: &event::System, - event_data: HashMap, - ) { - if event_id == "4672" { - &self.se_debug_privilege(event_data); - } - } - - // - // Special privileges assigned to new logon (possible admin access) - // - fn se_debug_privilege(&mut self, event_data: HashMap) { - - if let Some(privileage_list) = event_data.get("PrivilegeList") { - if let Some(_data) = privileage_list.find("SeDebugPrivilege") { - // alert_all_adminが有効であれば、標準出力して知らせる - // DeepBlueCLIでは必ず0になっていて、基本的には表示されない。 - if self.alert_all_admin == 1 { - println!("Logon with SeDebugPrivilege (admin access)"); - println!("Username:{}", event_data["SubjectUserName"]); - println!("Domain:{}", event_data["SubjectDomainName"]); - println!("User SID:{}", event_data["SubjectUserSid"]); - println!("Domain:{}", event_data["PrivilegeList"]); - } - - self.total_admin_logons += 1; - - // admin_logons配列にusernameが含まれているか確認 - match self.admin_logons.get(&event_data["SubjectUserName"]) { - Some(sid) => { - // 含まれていれば、マルチユーザが管理者としてログインしているか確認 - // マルチログオンのデータをセット - if event_data["SubjectUserName"] != event_data["SubjectUserSid"] { - // One username with multiple admin logon SIDs - self.multiple_admin_logons - .insert(event_data["SubjectUserName"].to_string(), 1); - - let mut count_hash: HashMap = HashMap::new(); - count_hash.insert( - event_data["SubjectUserSid"].to_string(), - sid[&event_data["SubjectUserSid"]] + 1, - ); - self.admin_logons.insert( - event_data["SubjectUserName"].to_string(), - count_hash, - ); - } - } - None => { - // admin_logons配列にセットUserNameとSIDとカウンタをセット - let mut count_hash: HashMap = HashMap::new(); - count_hash.insert(event_data["SubjectUserSid"].to_string(), 1); - self.admin_logons - .insert(event_data["SubjectUserName"].to_string(), count_hash); - } - } - } - } - } -} +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(), + } + } + + pub fn disp(&self) { + if self.total_admin_logons > 0 { + println!("total_admin_logons:{}", self.total_admin_logons); + println!("admin_logons:{:?}", self.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, + user_data: &Option, + event_data: HashMap, + ) { + self.process_craeted(&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_craeted(&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_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が有効であれば、標準出力して知らせる + // DeepBlueCLIでは必ず0になっていて、基本的には表示されない。 + if self.alert_all_admin == 1 { + println!("Logon with SeDebugPrivilege (admin access)"); + println!("Username:{}", event_data["SubjectUserName"]); + println!("Domain:{}", event_data["SubjectDomainName"]); + println!("User SID:{}", event_data["SubjectUserSid"]); + println!("Domain:{}", event_data["PrivilegeList"]); + } + + self.total_admin_logons += 1; + + // admin_logons配列にusernameが含まれているか確認 + match self.admin_logons.get(&event_data["SubjectUserName"]) { + Some(sid) => { + // 含まれていれば、マルチユーザが管理者としてログインしているか確認 + // マルチログオンのデータをセット + if event_data["SubjectUserName"] != event_data["SubjectUserSid"] { + // One username with multiple admin logon SIDs + self.multiple_admin_logons + .insert(event_data["SubjectUserName"].to_string(), 1); + + let mut count_hash: HashMap = HashMap::new(); + count_hash.insert( + event_data["SubjectUserSid"].to_string(), + sid[&event_data["SubjectUserSid"]] + 1, + ); + self.admin_logons + .insert(event_data["SubjectUserName"].to_string(), count_hash); + } + } + None => { + // admin_logons配列にセットUserNameとSIDとカウンタをセット + let mut count_hash: HashMap = HashMap::new(); + count_hash.insert(event_data["SubjectUserSid"].to_string(), 1); + self.admin_logons + .insert(event_data["SubjectUserName"].to_string(), count_hash); + } + } + } + } + } + + // 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..cb91779c 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -39,7 +39,7 @@ pub struct System { #[serde(rename = "EventID")] pub event_id: String, #[serde(rename = "Version")] - version: Option, + pub version: Option, #[serde(rename = "Level")] level: String, #[serde(rename = "Task")] @@ -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 {