From 32c6e13ccfe8f6d523f68b3376a44aa542fc074c Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Tue, 6 Oct 2020 22:13:00 +0900 Subject: [PATCH 1/4] refactor --- src/detections/security.rs | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index 6c40b07f..d1639c5d 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, @@ -47,7 +43,7 @@ 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:{:?}\n", self.multiple_admin_logons); + println!("multiple_admin_logons:{:?}\n\n", self.multiple_admin_logons); } let exceed_failed_logons = self.total_failed_logons > self.max_failed_logons; @@ -58,7 +54,7 @@ impl Security { "Total accounts: {}", self.account_2_failedcnt.keys().count() ); - println!("Total logon failures: {}\n", self.total_failed_logons); + println!("Total logon failures: {}\n\n", self.total_failed_logons); } } @@ -150,11 +146,11 @@ impl Security { println!("New User Created"); println!( "Username: {}", - event_data.get("TargetUserName").unwrap_or(&"".to_string()) + event_data.get("TargetUserName").unwrap_or(&self.empty_str) ); println!( - "User SID:: {}\n", - event_data.get("TargetSid").unwrap_or(&"".to_string()) + "User SID:: {}\n\n", + event_data.get("TargetSid").unwrap_or(&self.empty_str) ); } @@ -182,11 +178,11 @@ impl Security { println!( "Username: {}", - event_data.get("TargetUserName").unwrap_or(&"".to_string()) + event_data.get("TargetUserName").unwrap_or(&self.empty_str) ); println!( - "User SID:: {}\n", - event_data.get("TargetSid").unwrap_or(&"".to_string()) + "User SID:: {}\n\n", + event_data.get("TargetSid").unwrap_or(&self.empty_str) ); } @@ -221,7 +217,7 @@ impl Security { event_data.get("SubjectUserName").unwrap_or(&self.empty_str) ); println!( - "Domain Name: {}", + "Domain Name: {}\n\n", event_data .get("SubjectDomainName") .unwrap_or(&self.empty_str) @@ -295,11 +291,10 @@ impl Security { 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())); + 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: {}\n\n", username.unwrap_or(&self.empty_str)); } } From 3f257a52be7ec17fc5b2de0657b39824ba943236 Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Tue, 6 Oct 2020 22:37:19 +0900 Subject: [PATCH 2/4] eventid=4674 --- src/detections/security.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index d1639c5d..9b4c968b 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -225,8 +225,29 @@ impl Security { } } - 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) { + if event_id != "4674" { + return; + } + + // "%%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; + } + + println!("Possible Hidden Service Attempt"); + println!("User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view."); + + let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); + println!("User: {}", username); + let servicename = event_data.get("ObjectName").unwrap_or(&self.empty_str); + println!("Target service: {}", servicename); + println!("WRITE_DAC\n\n"); } // A logon was attempted using explicit credentials. From c3feb1eca202276ab1cac2c596bb4f4c327bee74 Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Wed, 7 Oct 2020 00:53:19 +0900 Subject: [PATCH 3/4] refactor for test. --- src/detections/security.rs | 250 ++++++++++++++++++++++++------------- 1 file changed, 162 insertions(+), 88 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index 9b4c968b..37bcc74b 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -40,22 +40,47 @@ 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\n", self.multiple_admin_logons); + self.fmt_admin_logons().and_then(Security::print_console); + self.fmt_passspray().and_then(Security::print_console); + } + + fn fmt_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\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:{:?}\n\n", + self.multiple_admin_logons + )); + + return Option::Some(msges); + } + + fn fmt_passspray(&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: {}\n\n", + self.total_failed_logons + )); + + return Option::Some(msges); } pub fn detection( @@ -67,13 +92,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) { @@ -138,20 +175,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(&self.empty_str) - ); - println!( - "User SID:: {}\n\n", - event_data.get("TargetSid").unwrap_or(&self.empty_str) - ); + 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!("TargetSid: {}", sid)); + + return Option::Some(msges); } // add member to security group @@ -159,31 +200,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(&self.empty_str) - ); - println!( - "User SID:: {}\n\n", - event_data.get("TargetSid").unwrap_or(&self.empty_str) - ); + 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!("TargetSid: {}", sid)); + + return Option::Some(msges); } // An account failed to log on.:OK @@ -203,31 +243,42 @@ 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: {}\n\n", - 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()); + + 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) { + fn attempt_priviledge( + &mut self, + event_id: &String, + event_data: &HashMap, + ) -> Option> { if event_id != "4674" { - return; + return Option::None; } // "%%1539" means WRITE_DAC(see detail: https://docs.microsoft.com/ja-jp/windows/security/threat-protection/auditing/event-4663) @@ -237,23 +288,32 @@ impl Security { .to_uppercase(); let accessname = event_data.get("AccessMask").unwrap_or(&self.empty_str); if servicename != r"C:\WINDOWS\SYSTEM32\SERVICES.EXE" || accessname != "%%1539" { - return; + return Option::None; } - println!("Possible Hidden Service Attempt"); - println!("User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view."); + 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); - println!("User: {}", username); + msges.push(format!("User: {}", username)); + let servicename = event_data.get("ObjectName").unwrap_or(&self.empty_str); - println!("Target service: {}", servicename); - println!("WRITE_DAC\n\n"); + 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); @@ -263,7 +323,7 @@ impl Security { // check targetuser's attempt count. if self.passspray_2_user.get(targetusername).unwrap_or(&0) <= &self.max_passspray_login { - return; + return Option::None; } // check exceeded targetuser count. @@ -273,7 +333,7 @@ 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( @@ -285,37 +345,51 @@ 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()); - println!("Security ID: {}\n\n", username.unwrap_or(&self.empty_str)); + msges.push(format!( + "Security ID: {}", + username.unwrap_or(&self.empty_str) + )); + + return Option::Some(msges); } } From 6ad9a773614604379212a2a58a822df1f3b534b1 Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Thu, 8 Oct 2020 08:30:56 +0900 Subject: [PATCH 4/4] testcase implemented --- src/detections/security.rs | 916 ++++++++++++++++++++++++++++++++++++- 1 file changed, 897 insertions(+), 19 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index 37bcc74b..97a30ce5 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -40,11 +40,11 @@ impl Security { } pub fn disp(&self) { - self.fmt_admin_logons().and_then(Security::print_console); - self.fmt_passspray().and_then(Security::print_console); + self.disp_admin_logons().and_then(Security::print_console); + self.disp_login_failed().and_then(Security::print_console); } - fn fmt_admin_logons(&self) -> Option> { + fn disp_admin_logons(&self) -> Option> { if self.total_admin_logons < 1 { return Option::None; } @@ -53,15 +53,16 @@ impl Security { msges.push(format!("total_admin_logons:{}", self.total_admin_logons)); msges.push(format!("admin_logons:{:?}", self.admin_logons)); msges.push(format!( - "multiple_admin_logons:{:?}\n\n", + "multiple_admin_logons:{:?}", self.multiple_admin_logons )); return Option::Some(msges); } - fn fmt_passspray(&self) -> Option> { + 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; @@ -76,7 +77,7 @@ impl Security { self.account_2_failedcnt.keys().count() )); msges.push(format!( - "Total logon failures: {}\n\n", + "Total logon failures: {}", self.total_failed_logons )); @@ -190,7 +191,7 @@ impl Security { 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!("TargetSid: {}", sid)); + msges.push(format!("User SID: {}", sid)); return Option::Some(msges); } @@ -218,10 +219,10 @@ impl Security { return Option::None; } - let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); + let username = event_data.get("MemberName").unwrap_or(&self.empty_str); msges.push(format!("Username: {}", username)); - let sid = event_data.get("TargetSid").unwrap_or(&self.empty_str); - msges.push(format!("TargetSid: {}", sid)); + let sid = event_data.get("MemberSid").unwrap_or(&self.empty_str); + msges.push(format!("User SID: {}", sid)); return Option::Some(msges); } @@ -260,6 +261,7 @@ impl Security { } 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)); @@ -293,7 +295,7 @@ impl Security { 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()); + 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)); @@ -321,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 Option::None; - } - // check exceeded targetuser count. let spray_uniq_user = self .passspray_2_user @@ -336,7 +333,11 @@ impl Security { 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); @@ -353,11 +354,13 @@ impl Security { "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("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); @@ -380,7 +383,7 @@ impl Security { let mut msges: Vec = Vec::new(); msges.push("Audit Log Clear".to_string()); - msges.push("The Audit log was cleared.".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()) @@ -393,3 +396,878 @@ impl Security { 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(); + } +}