diff --git a/src/detections/security.rs b/src/detections/security.rs index 6c40b07f..97a30ce5 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -1,10 +1,6 @@ use crate::models::event; use std::collections::HashMap; -// eventlogが用意できていない -// 4674 -// 4756 - #[derive(Debug)] pub struct Security { max_total_sensitive_privuse: i32, @@ -44,22 +40,48 @@ impl Security { } 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); + self.disp_admin_logons().and_then(Security::print_console); + self.disp_login_failed().and_then(Security::print_console); + } + + fn disp_admin_logons(&self) -> Option> { + if self.total_admin_logons < 1 { + return Option::None; } - 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); + let mut msges: Vec = Vec::new(); + msges.push(format!("total_admin_logons:{}", self.total_admin_logons)); + msges.push(format!("admin_logons:{:?}", self.admin_logons)); + msges.push(format!( + "multiple_admin_logons:{:?}", + self.multiple_admin_logons + )); + + return Option::Some(msges); + } + + fn disp_login_failed(&self) -> Option> { + 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 { + return Option::None; } + + let mut msges: Vec = Vec::new(); + msges.push(format!( + "High number of total logon failures for multiple accounts" + )); + msges.push(format!( + "Total accounts: {}", + self.account_2_failedcnt.keys().count() + )); + msges.push(format!( + "Total logon failures: {}", + self.total_failed_logons + )); + + return Option::Some(msges); } pub fn detection( @@ -71,13 +93,25 @@ impl Security { ) { 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.account_created(&event_id, &event_data) + .and_then(Security::print_console); + self.add_member_security_group(&event_id, &event_data) + .and_then(Security::print_console); 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); + self.sensitive_priviledge(&event_id, &event_data) + .and_then(Security::print_console); + self.attempt_priviledge(&event_id, &event_data) + .and_then(Security::print_console); + self.pass_spray(&event_id, &event_data) + .and_then(Security::print_console); + self.audit_log_cleared(&event_id, &user_data) + .and_then(Security::print_console); + } + + fn print_console(v: Vec) -> Option> { + v.iter().for_each(|s| println!("{}", s)); + println!("\n"); + return Option::Some(v); } fn process_created(&mut self, event_id: &String, _event_data: &HashMap) { @@ -142,20 +176,24 @@ impl Security { } // account craeted:OK - fn account_created(&mut self, event_id: &String, event_data: &HashMap) { + fn account_created( + &mut self, + event_id: &String, + event_data: &HashMap, + ) -> Option> { if event_id != "4720" { - return; + return Option::None; } - 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()) - ); + let mut msges: Vec = Vec::new(); + msges.push("New User Created".to_string()); + + let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); + msges.push(format!("Username: {}", username)); + let sid = event_data.get("TargetSid").unwrap_or(&self.empty_str); + msges.push(format!("User SID: {}", sid)); + + return Option::Some(msges); } // add member to security group @@ -163,31 +201,30 @@ impl Security { &mut self, event_id: &String, event_data: &HashMap, - ) { + ) -> Option> { // check if group is Administrator, may later expand to all groups if event_data.get("TargetUserName").unwrap_or(&self.empty_str) != "Administrators" { - return; + return Option::None; } // A member was added to a security-enabled (global|local|universal) group. + let mut msges: Vec = Vec::new(); if event_id == "4728" { - println!("User added to global Administrators group"); + msges.push("User added to global Administrators group".to_string()); } else if event_id == "4732" { - println!("User added to local Administrators group"); + msges.push("User added to local Administrators group".to_string()); } else if event_id == "4756" { - println!("User added to universal Administrators group"); + msges.push("User added to universal Administrators group".to_string()); } else { - return; + return Option::None; } - println!( - "Username: {}", - event_data.get("TargetUserName").unwrap_or(&"".to_string()) - ); - println!( - "User SID:: {}\n", - event_data.get("TargetSid").unwrap_or(&"".to_string()) - ); + let username = event_data.get("MemberName").unwrap_or(&self.empty_str); + msges.push(format!("Username: {}", username)); + let sid = event_data.get("MemberSid").unwrap_or(&self.empty_str); + msges.push(format!("User SID: {}", sid)); + + return Option::Some(msges); } // An account failed to log on.:OK @@ -207,36 +244,78 @@ impl Security { } // Sensitive Privilege Use (Mimikatz) - fn sensitive_priviledge(&mut self, event_id: &String, event_data: &HashMap) { + fn sensitive_priviledge( + &mut self, + event_id: &String, + event_data: &HashMap, + ) -> Option> { if event_id != "4673" { - return; + return Option::None; } self.total_sensitive_privuse += 1; + let mut msges: Vec = Vec::new(); // 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) - ); + if self.max_total_sensitive_privuse != self.total_sensitive_privuse { + return Option::None; } + + msges.push("Sensititive Privilege Use Exceeds Threshold".to_string()); + msges.push("Potentially indicative of Mimikatz, multiple sensitive privilege calls have been made".to_string()); + + let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); + msges.push(format!("Username: {}", username)); + + let domainname = event_data + .get("SubjectDomainName") + .unwrap_or(&self.empty_str); + msges.push(format!("Domain Name: {}", domainname)); + + return Option::Some(msges); } - fn attempt_priviledge(&mut self, _event_id: &String, _event_data: &HashMap) { - // event log cannot get... + fn attempt_priviledge( + &mut self, + event_id: &String, + event_data: &HashMap, + ) -> Option> { + if event_id != "4674" { + return Option::None; + } + + // "%%1539" means WRITE_DAC(see detail: https://docs.microsoft.com/ja-jp/windows/security/threat-protection/auditing/event-4663) + let servicename = event_data + .get("ProcessName") + .unwrap_or(&self.empty_str) + .to_uppercase(); + let accessname = event_data.get("AccessMask").unwrap_or(&self.empty_str); + if servicename != r"C:\WINDOWS\SYSTEM32\SERVICES.EXE" || accessname != "%%1539" { + return Option::None; + } + + let mut msges: Vec = Vec::new(); + msges.push("Possible Hidden Service Attempt".to_string()); + msges.push("User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view".to_string()); + + let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); + msges.push(format!("User: {}", username)); + + let servicename = event_data.get("ObjectName").unwrap_or(&self.empty_str); + msges.push(format!("Target service: {}", servicename)); + + msges.push("WRITE_DAC".to_string()); + + return Option::Some(msges); } // A logon was attempted using explicit credentials. - fn pass_spray(&mut self, event_id: &String, event_data: &HashMap) { + fn pass_spray( + &mut self, + event_id: &String, + event_data: &HashMap, + ) -> Option> { if event_id != "4648" { - return; + return Option::None; } let targetusername = event_data.get("TargetUserName").unwrap_or(&self.empty_str); @@ -244,11 +323,6 @@ impl Security { 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 @@ -256,10 +330,14 @@ impl Security { .filter(|value| value > &&self.max_passspray_login) .count() as i32; if spray_uniq_user <= self.max_passspray_uniquser { - return; + return Option::None; } - let usernames: String = self.passspray_2_user.keys().fold( + // let v_username = Vec::new(); + let mut v_username = Vec::new(); + self.passspray_2_user.keys().for_each(|u| v_username.push(u)); + v_username.sort(); + let usernames: String = v_username.iter().fold( self.empty_str.to_string(), |mut acc: String, cur| -> String { acc.push_str(cur); @@ -268,38 +346,928 @@ impl Security { }, ); - 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) + let mut msges: Vec = Vec::new(); + msges.push( + "Distributed Account Explicit Credential Use (Password Spray Attack)".to_string(), ); - println!( - "Accessing Host Name: {}\n\n", - event_data - .get("SubjectDomainName") - .unwrap_or(&self.empty_str) + msges.push( + "The use of multiple user account access attempts with explicit credentials is " + .to_string(), ); + msges.push("an indicator of a password spray attack".to_string()); + + msges.push(format!("Target Usernames: {}", usernames.trim())); + let access_username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); + + msges.push(format!("Accessing Username: {}", access_username)); + + let access_hostname = event_data + .get("SubjectDomainName") + .unwrap_or(&self.empty_str); + msges.push(format!("Accessing Host Name: {}", access_hostname)); // reset self.passspray_2_user = HashMap::new(); + + return Option::Some(msges); } - fn audit_log_cleared(&mut self, event_id: &String, user_data: &Option) { + fn audit_log_cleared( + &mut self, + event_id: &String, + user_data: &Option, + ) -> Option> { if event_id != "1102" { - return; + return Option::None; } - println!("Audit Log Clear"); - println!("The Audit log was cleared."); + let mut msges: Vec = Vec::new(); + msges.push("Audit Log Clear".to_string()); + msges.push("The Audit log was cleared".to_string()); + let username = user_data + .as_ref() + .and_then(|u| u.log_file_cleared.as_ref()) + .and_then(|l| l.subject_user_name.as_ref()); + msges.push(format!( + "Security ID: {}", + username.unwrap_or(&self.empty_str) + )); - 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())); + return Option::Some(msges); + } +} + +#[cfg(test)] +mod tests { + extern crate quick_xml; + + use crate::detections::security; + use crate::models::event; + + // 正しくヒットするパターン + #[test] + fn test_account_created_hit() { + let xml_str = get_account_created_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.account_created( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"New User Created".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: IEUser".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(Option::None, ite.next()); + } + + // event idが異なるパターン + #[test] + fn test_account_created_noteq_eventid() { + let xml_str = + get_account_created_xml().replace("4720", "4721"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.account_created( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + assert_eq!(Option::None, option_v); + } + + // 実在するかどうか不明だが、EventDataの必要なフィールドがないパターン + #[test] + fn test_account_created_none_check() { + let xml_str = r#" + + + + + 4720 + 0 + 0 + 13824 + 0 + 0x8020000000000000 + + 112 + + + Security + IE8Win7 + + + + "#; + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.account_created( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"New User Created".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: ".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(&"User SID: ", ite.next().unwrap_or(&"".to_string())); + assert_eq!(Option::None, ite.next()); + } + + fn get_account_created_xml() -> String { + return r#" + + + + + 4720 + 0 + 0 + 13824 + 0 + 0x8020000000000000 + + 112 + + + Security + IE8Win7 + + + + IEUser + IE8Win7 + S-1-5-21-3463664321-2923530833-3546627382-1000 + S-1-5-18 + WIN-QALA5Q3KJ43$ + WORKGROUP + 0x3e7 + - + IEUserSam + %%1793 + - + %%1793 + %%1793 + %%1793 + %%1793 + %%1793 + %%1794 + %%1794 + 513 + - + 0x0 + 0x15 + + %%2080 + %%2082 + %%2084 + %%1793 + - + %%1797 + + "#.to_string(); + } + + // 正しくヒットするパターン(eventid=4732) + #[test] + fn test_add_member_security_group_hit_4732() { + let xml_str = get_add_member_security_group_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"User added to local Administrators group".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: testnamess".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(Option::None, ite.next()); + } + + // 正しくヒットするパターン(eventid=4728は一行目が変わる) + #[test] + fn test_add_member_security_group_hit_4728() { + let xml_str = get_add_member_security_group_xml() + .replace(r"4732", r"4728"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"User added to global Administrators group".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: testnamess".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(Option::None, ite.next()); + } + + // 正しくヒットするパターン(eventid=4756は一行目が変わる) + #[test] + fn test_add_member_security_group_hit_4756() { + let xml_str = get_add_member_security_group_xml() + .replace(r"4732", r"4756"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"User added to universal Administrators group".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: testnamess".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"User SID: S-1-5-21-3463664321-2923530833-3546627382-1000", + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(Option::None, ite.next()); + } + + // eventidが異なりヒットしないパターン + #[test] + fn test_add_member_security_group_noteq_eventid() { + let xml_str = get_add_member_security_group_xml().replace(r"4732", r"4757"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + assert_eq!(Option::None, option_v); + } + + // グループがAdministratorsじゃなくてHitしないパターン + #[test] + fn test_add_member_security_not_administrators() { + let xml_str = get_add_member_security_group_xml().replace(r"Administrators", r"local"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + assert_eq!(Option::None, option_v); + } + + // hitするけど表示するフィールドがない場合 + #[test] + fn test_add_member_security_group_none() { + let xml_str = r#" + + + + + 4732 + 0 + 0 + 13826 + 0 + 0x8020000000000000 + + 116 + + + Security + IE8Win7 + + + + Administrators + + "#; + let event: event::Evtx = quick_xml::de::from_str(&xml_str) + .map_err(|e| { + println!("{}", e.to_string()); + }) + .unwrap(); + + let mut sec = security::Security::new(); + let option_v = sec.add_member_security_group( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + + let v = option_v.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"User added to local Administrators group".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: ".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"User SID: ", + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!(Option::None, ite.next()); + } + + fn get_add_member_security_group_xml() -> String { + return r#" + + + + + 4732 + 0 + 0 + 13826 + 0 + 0x8020000000000000 + + 116 + + + Security + IE8Win7 + + + + testnamess + S-1-5-21-3463664321-2923530833-3546627382-1000 + Administrators + Builtin + S-1-5-32-544 + S-1-5-18 + WIN-QALA5Q3KJ43$ + WORKGROUP + 0x3e7 + - + + "#.to_string(); + } + + // ユーザー数が一つなら、ログ数が幾らあっても、メッセージは表示されないはず。 + #[test] + fn test_failed_logon_nothit_onlyoneuser() { + let xml_str = get_failed_logon_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + + sec.max_failed_logons = 5; + let ite = [1,2,3,4,5,6,7].iter(); + ite.for_each(|i|{ + sec.failed_logon( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + assert_eq!(i, &sec.total_failed_logons); + assert_eq!(Option::None, sec.disp_login_failed()); + }); + } + + // 失敗回数を増やしていき、境界値でメッセージが表示されることのテスト。 + #[test] + fn test_failed_logon_hit() { + let xml_str = get_failed_logon_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + let event_another: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"Administrator", r"localuser")).unwrap(); + + let mut sec = security::Security::new(); + sec.max_failed_logons = 5; + + // メッセージが表示されるには2ユーザー以上失敗している必要がある。まず一人目 + sec.failed_logon( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + assert_eq!(1, sec.total_failed_logons); + + let ite = [1,2,3,4,5,6,7].iter(); + ite.for_each(|i|{ + sec.failed_logon( + &event_another.system.event_id.to_string(), + &event_another.parse_event_data(), + ); + let fail_cnt = i +1; + assert_eq!(fail_cnt, sec.total_failed_logons); + if fail_cnt > 5 { + let v = sec.disp_login_failed().unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"High number of total logon failures for multiple accounts".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Total accounts: 2".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &format!("Total logon failures: {}", fail_cnt), + ite.next().unwrap_or(&"".to_string()) + ); + // assert_eq!(Option::None, ite.next()); + } else { + assert_eq!(Option::None, sec.disp_login_failed()); + } + }); + + // hitするけど表示するフィールドがない場合 + let xml_nofield = r#" + + + 4625001254400x80100000000000006016SecurityDESKTOP-M5SN04R + + "#; + + // エラーにならなければOK + let event_nofield: event::Evtx = quick_xml::de::from_str(xml_nofield).unwrap(); + sec.failed_logon( + &event_nofield.system.event_id.to_string(), + &event_nofield.parse_event_data(), + ); + } + + // 失敗回数を増やしていき、境界値でメッセージが表示されることのテスト。 + #[test] + fn test_failed_logon_noteq_eventid() { + let xml_str = get_failed_logon_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"4625",r"4626")).unwrap(); + let event_another: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"4625",r"4626").replace(r"Administrator", r"localuser")).unwrap(); + + let mut sec = security::Security::new(); + sec.max_failed_logons = 5; + + // メッセージが表示されるには2ユーザー以上失敗している必要がある。まず一人目 + sec.failed_logon( + &event.system.event_id.to_string(), + &event.parse_event_data(), + ); + assert_eq!(0, sec.total_failed_logons); + + let ite = [1,2,3,4,5,6,7].iter(); + ite.for_each(|_i|{ + sec.failed_logon( + &event_another.system.event_id.to_string(), + &event_another.parse_event_data(), + ); + assert_eq!(0, sec.total_failed_logons); + assert_eq!(Option::None, sec.disp_login_failed()); + }); + } + + fn get_failed_logon_xml() -> String { + return r#" + + + + 4625 + 0 + 0 + 12544 + 0 + 0x8010000000000000 + + 6016 + + + Security + DESKTOP-M5SN04R + + + + S-1-0-0 + - + - + 0x0 + S-1-0-0 + Administrator + . + 0xc000006d + %%2313 + 0xc000006a + 3 + NtLmSsp + NTLM + fpEbpiox2Q3Qf8av + - + - + 0 + 0x0 + - + 192.168.198.149 + 33083 + + "#.to_string(); + } + + // Hitするパターンとしないパターンをまとめてテスト + #[test] + fn test_sensitive_priviledge_hit() { + let xml_str = get_sensitive_prividedge_hit(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + sec.max_total_sensitive_privuse = 6; + + let ite = [1,2,3,4,5,6,7].iter(); + ite.for_each(|i| { + let msg = sec.sensitive_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + // i == 7ときにHitしない + if i == &6 { + let v = msg.unwrap(); + let mut ite = v.iter(); + assert_eq!( + &"Sensititive Privilege Use Exceeds Threshold".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Potentially indicative of Mimikatz, multiple sensitive privilege calls have been made".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Username: Sec504".to_string(), + ite.next().unwrap_or(&"".to_string()) + ); + assert_eq!( + &"Domain Name: SEC504STUDENT", + ite.next().unwrap_or(&"".to_string()) + ); + } else { + assert_eq!(Option::None, msg); + } + }); + } + + // eventidが異なるので、Hitしないテスト + #[test] + fn test_sensitive_priviledge_noteq_eventid() { + let xml_str = get_sensitive_prividedge_hit().replace(r"4673", r"4674"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + sec.max_total_sensitive_privuse = 6; + + let ite = [1,2,3,4,5,6,7].iter(); + ite.for_each(|_i| { + let msg = sec.sensitive_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + assert_eq!(Option::None, msg); + }); + } + + fn get_sensitive_prividedge_hit() -> String { + return r#" + + + + 4673 + 0 + 0 + 13056 + 0 + 0x8010000000000000 + + 8936 + + + Security + Sec504Student + + + + S-1-5-21-2977773840-2930198165-1551093962-1000 + Sec504 + SEC504STUDENT + 0x1e3dd + Security + - + SeTcbPrivilege + 0x15a8 + C:\Tools\mimikatz\mimikatz.exe + + "#.to_string(); + } + + // Hitするテスト + #[test] + fn test_attempt_priviledge_hit() { + let xml_str = get_attempt_priviledge_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.attempt_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + + assert_ne!(Option::None, msg); + let v = msg.unwrap(); + let mut ite = v.iter(); + assert_eq!(&"Possible Hidden Service Attempt".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"User: Sec504".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"Target service: nginx".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"WRITE_DAC".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(Option::None, ite.next()); + } + + // accessmaskが異なるので、Hitしないテスト + #[test] + fn test_attempt_priviledge_noteq_accessmask() { + let xml_str = get_attempt_priviledge_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"%%1539",r"%%1538")).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.attempt_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + + assert_eq!(Option::None, msg); + } + + // Serviceが違うのでHitしないテスト + #[test] + fn test_attempt_priviledge_noteq_service() { + let xml_str = get_attempt_priviledge_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"C:\Windows\System32\services.exe",r"C:\Windows\System32\lsass.exe")).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.attempt_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + + assert_eq!(Option::None, msg); + } + + // EventIDが違うのでHitしないテスト + #[test] + fn test_attempt_priviledge_noteq_eventid() { + let xml_str = get_attempt_priviledge_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str.replace(r"4674",r"4675")).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.attempt_priviledge(&event.system.event_id.to_string(), &event.parse_event_data()); + + assert_eq!(Option::None, msg); + } + + fn get_attempt_priviledge_xml() -> String { + return r#" + + + + 4674 + 0 + 0 + 13056 + 0 + 0x8020000000000000 + + 39406 + + + Security + Sec504Student + + + + S-1-5-21-2977773840-2930198165-1551093962-1000 + Sec504 + SEC504STUDENT + 0x99e3d + SC Manager + SERVICE OBJECT + nginx + 0xffff820cb1d95928 + %%1539 + + SeSecurityPrivilege + 0x21c + C:\Windows\System32\services.exe + + "#.to_string(); + } + + #[test] + fn test_pass_spray_hit() { + let mut sec = security::Security::new(); + // 6ユーザまでは表示されず、7ユーザー以上で表示されるようになる。 + sec.max_passspray_login = 6; + sec.max_passspray_uniquser = 6; + + test_pass_spray_hit_1cycle(&mut sec,"4648".to_string(), true); + // counterがreset確認のため、2回実行 + test_pass_spray_hit_1cycle(&mut sec,"4648".to_string(), true); + } + + + // eventid異なるので、Hitしないはず + #[test] + fn test_pass_spray_noteq_eventid() { + let mut sec = security::Security::new(); + // 6ユーザまでは表示されず、7ユーザー以上で表示されるようになる。 + sec.max_passspray_login = 6; + sec.max_passspray_uniquser = 6; + + test_pass_spray_hit_1cycle(&mut sec,"4649".to_string(), false); + // counterがreset確認のため、2回実行 + test_pass_spray_hit_1cycle(&mut sec,"4649".to_string(), false); + } + + fn test_pass_spray_hit_1cycle( sec: &mut security::Security, event_id:String, is_eq:bool ) { + [1,2,3,4,5,6,7].iter().for_each(|i| { + let rep_str = format!(r#"smisenar{}"#,i); + let event_id_tag = format!("{}", event_id); + let xml_str = get_passs_pray_hit().replace(r#"smisenar"#, &rep_str).replace(r"4648", &event_id_tag); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + [1,2,3,4,5,6,7].iter().for_each(|k|{ + let ret = sec.pass_spray(&event.system.event_id.to_string(), &event.parse_event_data()); + if i == &7 && k == &7 && is_eq { + let v = ret.unwrap(); + let mut ret_ite = v.iter(); + assert_eq!(&"Distributed Account Explicit Credential Use (Password Spray Attack)".to_string(),ret_ite.next().unwrap()); + assert_eq!(&"The use of multiple user account access attempts with explicit credentials is ".to_string(),ret_ite.next().unwrap()); + assert_eq!(&"an indicator of a password spray attack".to_string(),ret_ite.next().unwrap()); + assert_eq!("Target Usernames: smisenar1 smisenar2 smisenar3 smisenar4 smisenar5 smisenar6 smisenar7",ret_ite.next().unwrap()); + assert_eq!(&"Accessing Username: jwrig".to_string(),ret_ite.next().unwrap()); + assert_eq!(&"Accessing Host Name: DESKTOP-JR78RLP".to_string(),ret_ite.next().unwrap()); + assert_eq!(Option::None,ret_ite.next()); + } else { + assert_eq!(Option::None,ret); + } + }); + }); + } + + fn get_passs_pray_hit() -> String { + return r#" + + + + 4648 + 0 + 0 + 12544 + 0 + 0x8020000000000000 + + 43097 + + + Security + DESKTOP-JR78RLP + + + + S-1-5-21-979008924-657238111-836329461-1002 + jwrig + DESKTOP-JR78RLP + 0x3069d + {00000000-0000-0000-0000-000000000000} + smisenar + DOMAIN + {00000000-0000-0000-0000-000000000000} + DESKTOP-JR78RLP + DESKTOP-JR78RLP + 0x4 + + 172.16.144.128 + 445 + + "#.to_string(); + } + + // 普通にHitするテスト + #[test] + fn test_audit_log_cleared_hit() { + let xml_str = get_audit_log_cleared_xml(); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.audit_log_cleared(&event.system.event_id.to_string(), &event.user_data); + + assert_ne!(Option::None, msg); + let v = msg.unwrap(); + let mut ite = v.iter(); + assert_eq!(&"Audit Log Clear".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"The Audit log was cleared".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(&"Security ID: jwrig".to_string(), ite.next().unwrap_or(&"".to_string())); + assert_eq!(Option::None, ite.next()); + } + + // eventid違うのでHitしないはず + #[test] + fn test_audit_log_cleared_noteq_eventid() { + let xml_str = get_audit_log_cleared_xml().replace(r"1102", r"1103"); + let event: event::Evtx = quick_xml::de::from_str(&xml_str).unwrap(); + + let mut sec = security::Security::new(); + let msg = sec.audit_log_cleared(&event.system.event_id.to_string(), &event.user_data); + assert_eq!(Option::None, msg); + } + + fn get_audit_log_cleared_xml() -> String { + return r#" + + + + 1102 + 0 + 4 + 104 + 0 + 0x4020000000000000 + + 42803 + + + Security + DESKTOP-JR78RLP + + + + + S-1-5-21-979008924-657238111-836329461-1002 + jwrig + DESKTOP-JR78RLP + 0x30550 + + + "#.to_string(); } }