From 42f8483485a80bde93813d5e78689077ef39e0e8 Mon Sep 17 00:00:00 2001 From: siamease Date: Fri, 2 Oct 2020 00:10:38 +0900 Subject: [PATCH 01/13] add sysmon --- src/detections/detection.rs | 4 ++++ src/detections/mod.rs | 1 + src/detections/sysmon.rs | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 src/detections/sysmon.rs diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 756fcf4c..417c5cd8 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -4,6 +4,7 @@ use crate::detections::application; use crate::detections::common; use crate::detections::security; use crate::detections::system; +use crate::detections::sysmon; use crate::models::event; use evtx::EvtxParser; use quick_xml::de::DeError; @@ -26,6 +27,7 @@ impl Detection { let mut security = security::Security::new(); let mut system = system::System::new(); let mut application = application::Application::new(); + let mut sysmon = sysmon::Sysmon::new(); for record in parser.records() { match record { @@ -43,6 +45,8 @@ impl Detection { &system.detection(event_id, &event.system, event_data); } else if channel == "Application" { &application.detection(event_id, &event.system, event_data); + } else if channel == "Microsoft-Windows-Sysmon/Operational" { + &sysmon.detection(event_id, &event.system, event_data); } else { //&other.detection(); } diff --git a/src/detections/mod.rs b/src/detections/mod.rs index 7238b4aa..2e67495c 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -3,3 +3,4 @@ mod common; pub mod detection; mod security; mod system; +mod sysmon; \ No newline at end of file diff --git a/src/detections/sysmon.rs b/src/detections/sysmon.rs new file mode 100644 index 00000000..34bfb6f8 --- /dev/null +++ b/src/detections/sysmon.rs @@ -0,0 +1,37 @@ +use crate::models::event; +use std::collections::HashMap; + +pub struct Sysmon {} + +impl Sysmon { + pub fn new() -> Sysmon { + Sysmon {} + } + + pub fn detection( + &mut self, + event_id: String, + system: &event::System, + event_data: HashMap, + ) { + if event_id == "1" { + &self.sysmon_event_1(event_data); + } else if event_id == "7" { + &self.sysmon_event_7(event_data); + } + } + + fn sysmon_event_1(&mut self, event_data: HashMap) { + println!("Message : Sysmon event 1"); + if let Some(_image) = event_data.get("Image") { + println!("_image : {}",_image); + } + if let Some(_command_line) = event_data.get("CommandLine") { + println!("_command_line : {}",_command_line); + } + } + + fn sysmon_event_7(&mut self, event_data: HashMap) { + println!("Message : Sysmon event 7"); + } +} From fa9f3813ae7aa381d9458e7722b3caff1c4adfbb Mon Sep 17 00:00:00 2001 From: siamease Date: Fri, 2 Oct 2020 00:14:33 +0900 Subject: [PATCH 02/13] add sysmon --- src/detections/detection.rs | 2 +- src/detections/mod.rs | 2 +- src/detections/sysmon.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 417c5cd8..4f07f017 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -3,8 +3,8 @@ extern crate quick_xml; use crate::detections::application; use crate::detections::common; use crate::detections::security; -use crate::detections::system; use crate::detections::sysmon; +use crate::detections::system; use crate::models::event; use evtx::EvtxParser; use quick_xml::de::DeError; diff --git a/src/detections/mod.rs b/src/detections/mod.rs index 2e67495c..3b50be07 100644 --- a/src/detections/mod.rs +++ b/src/detections/mod.rs @@ -2,5 +2,5 @@ mod application; mod common; pub mod detection; mod security; +mod sysmon; mod system; -mod sysmon; \ No newline at end of file diff --git a/src/detections/sysmon.rs b/src/detections/sysmon.rs index 34bfb6f8..613bf183 100644 --- a/src/detections/sysmon.rs +++ b/src/detections/sysmon.rs @@ -24,10 +24,10 @@ impl Sysmon { fn sysmon_event_1(&mut self, event_data: HashMap) { println!("Message : Sysmon event 1"); if let Some(_image) = event_data.get("Image") { - println!("_image : {}",_image); + println!("_image : {}", _image); } if let Some(_command_line) = event_data.get("CommandLine") { - println!("_command_line : {}",_command_line); + println!("_command_line : {}", _command_line); } } From ca56063f1212fed5bbeb6a121dcc092ab0517d05 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 4 Oct 2020 18:37:05 +0900 Subject: [PATCH 03/13] Security module Implemented without 4674 --- src/detections/detection.rs | 2 +- src/detections/security.rs | 400 +++++++++++++++++++++++++++--------- src/models/event.rs | 22 +- 3 files changed, 330 insertions(+), 94 deletions(-) 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 { From 1057a72efcc8f54ed51cbc2ba93ef21c61c48e9a Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 Oct 2020 08:55:03 +0900 Subject: [PATCH 04/13] remove unneccesary pub --- src/detections/detection.rs | 7 +- src/detections/security.rs | 610 ++++++++++++++++++------------------ src/models/event.rs | 266 ++++++++-------- 3 files changed, 441 insertions(+), 442 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 25a6864b..ae5899fb 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -38,7 +38,12 @@ impl Detection { &common.detection(&event.system, &event_data); //&common.detection(&event.system, &event_data); if channel == "Security" { - &security.detection(event_id, &event.system, &event.user_data, event_data); + &security.detection( + event_id, + &event.system, + &event.user_data.as_ref(), + 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 ec3d72d1..6dc4be1c 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -1,308 +1,302 @@ -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())); - } -} +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::UserData>, + 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<&event::UserData>) { + if event_id != "1102" { + return; + } + + println!("Audit Log Clear"); + println!("The Audit log was cleared."); + user_data.and_then(|u| u.log_file_cleared.as_ref()); + + let username = user_data.and_then(|u| u.log_file_cleared.and_then(|l| l.subject_user_name)); + println!("Security ID: {}", username.unwrap_or("".to_string())); + } +} diff --git a/src/models/event.rs b/src/models/event.rs index cb91779c..8234e430 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -1,133 +1,133 @@ -extern crate serde; -use serde::Deserialize; -use std::collections::HashMap; - -#[derive(Debug, Deserialize, PartialEq)] -pub struct Data { - #[serde(rename = "Name")] - pub name: Option, - #[serde(rename = "$value")] - pub text: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct TimeCreated { - #[serde(rename = "SystemTime")] - pub system_time: String, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct Execution { - #[serde(rename = "ProcessID")] - process_id: i32, - #[serde(rename = "ThreadID")] - thread_id: i32, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct Provider { - #[serde(rename = "Name")] - pub name: Option, - #[serde(rename = "Guid")] - guid: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct System { - #[serde(rename = "Provider")] - pub provider: Provider, - #[serde(rename = "EventID")] - pub event_id: String, - #[serde(rename = "Version")] - pub version: Option, - #[serde(rename = "Level")] - level: String, - #[serde(rename = "Task")] - task: String, - #[serde(rename = "Opcode")] - opcode: Option, - #[serde(rename = "Keywords")] - keywords: String, - #[serde(rename = "TimeCreated")] - pub time_created: TimeCreated, - #[serde(rename = "EventRecordID")] - pub event_record_id: String, - #[serde(rename = "Correlation")] - correlation: Option, - #[serde(rename = "Execution")] - execution: Option, - #[serde(rename = "Channel")] - pub channel: String, // Security, System, Application ...etc - #[serde(rename = "Computer")] - computer: String, - #[serde(rename = "Security")] - security: String, - #[serde(rename = "Message")] - pub message: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct EventData { - #[serde(rename = "Data")] - 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 { - // - // 文字列データを取得する - // - fn get_string(v: &Data) -> String { - let mut ret = "".to_string(); - if let Some(text) = &v.text { - ret = text.to_string(); - } - return ret; - } - - // - // EventDataをHashMapとして取得する - // - pub fn parse_event_data(&self) -> HashMap { - let mut values = HashMap::new(); - - if let Some(event_data) = &self.event_data { - if let Some(data) = &event_data.data { - for v in data.iter() { - if let Some(name) = &v.name { - values.insert(name.to_string(), Evtx::get_string(v)); - } - } - } - } - - values - } -} +extern crate serde; +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Debug, Deserialize, PartialEq)] +pub struct Data { + #[serde(rename = "Name")] + pub name: Option, + #[serde(rename = "$value")] + pub text: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct TimeCreated { + #[serde(rename = "SystemTime")] + pub system_time: String, +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Execution { + #[serde(rename = "ProcessID")] + process_id: i32, + #[serde(rename = "ThreadID")] + thread_id: i32, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct Provider { + #[serde(rename = "Name")] + pub name: Option, + #[serde(rename = "Guid")] + guid: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct System { + #[serde(rename = "Provider")] + pub provider: Provider, + #[serde(rename = "EventID")] + pub event_id: String, + #[serde(rename = "Version")] + version: Option, + #[serde(rename = "Level")] + level: String, + #[serde(rename = "Task")] + task: String, + #[serde(rename = "Opcode")] + opcode: Option, + #[serde(rename = "Keywords")] + keywords: String, + #[serde(rename = "TimeCreated")] + pub time_created: TimeCreated, + #[serde(rename = "EventRecordID")] + pub event_record_id: String, + #[serde(rename = "Correlation")] + correlation: Option, + #[serde(rename = "Execution")] + execution: Option, + #[serde(rename = "Channel")] + pub channel: String, // Security, System, Application ...etc + #[serde(rename = "Computer")] + computer: String, + #[serde(rename = "Security")] + security: String, + #[serde(rename = "Message")] + pub message: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct EventData { + #[serde(rename = "Data")] + 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 { + // + // 文字列データを取得する + // + fn get_string(v: &Data) -> String { + let mut ret = "".to_string(); + if let Some(text) = &v.text { + ret = text.to_string(); + } + return ret; + } + + // + // EventDataをHashMapとして取得する + // + pub fn parse_event_data(&self) -> HashMap { + let mut values = HashMap::new(); + + if let Some(event_data) = &self.event_data { + if let Some(data) = &event_data.data { + for v in data.iter() { + if let Some(name) = &v.name { + values.insert(name.to_string(), Evtx::get_string(v)); + } + } + } + } + + values + } +} From dc2e55cc9f3fdb8ab78553c71a2d09e6fe7178cb Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 Oct 2020 09:08:32 +0900 Subject: [PATCH 05/13] refactor --- src/detections/detection.rs | 7 +------ src/detections/security.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index ae5899fb..25a6864b 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -38,12 +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.user_data.as_ref(), - 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 6dc4be1c..9c0eb7f4 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -66,7 +66,7 @@ impl Security { &mut self, event_id: String, _system: &event::System, - user_data: &Option<&event::UserData>, + user_data: &Option, event_data: HashMap, ) { self.process_craeted(&event_id, &event_data); @@ -287,16 +287,19 @@ impl Security { self.passspray_2_user = HashMap::new(); } - fn audit_log_cleared(&mut self, event_id: &String, user_data: &Option<&event::UserData>) { + 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."); - user_data.and_then(|u| u.log_file_cleared.as_ref()); - let username = user_data.and_then(|u| u.log_file_cleared.and_then(|l| l.subject_user_name)); - 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: {}", username.unwrap_or(&"".to_string())); } } From 7bc48e80f91654609734601400fa67dd03672f06 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 Oct 2020 09:42:47 +0900 Subject: [PATCH 06/13] fix typo --- src/detections/security.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index 9c0eb7f4..e4d9a07c 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -69,7 +69,7 @@ impl Security { user_data: &Option, event_data: HashMap, ) { - self.process_craeted(&event_id, &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); @@ -80,7 +80,7 @@ impl Security { self.audit_log_cleared(&event_id, &user_data); } - fn process_craeted(&mut self, event_id: &String, _event_data: &HashMap) { + fn process_created(&mut self, event_id: &String, _event_data: &HashMap) { if event_id != "4688" { return; } From 87796f83e679bbbb6b00e5a94ed5d2b3f41011a7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 5 Oct 2020 20:56:47 +0900 Subject: [PATCH 07/13] fix line feed code and refactoring --- src/detections/security.rs | 2 +- src/models/event.rs | 266 ++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 134 deletions(-) diff --git a/src/detections/security.rs b/src/detections/security.rs index e4d9a07c..6c40b07f 100644 --- a/src/detections/security.rs +++ b/src/detections/security.rs @@ -201,7 +201,7 @@ impl Security { // 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; + let failed_cnt = self.account_2_failedcnt.get(username).unwrap_or(&0) + 1; self.account_2_failedcnt .insert(username.to_string(), failed_cnt); } diff --git a/src/models/event.rs b/src/models/event.rs index 8234e430..aaea9312 100644 --- a/src/models/event.rs +++ b/src/models/event.rs @@ -1,133 +1,133 @@ -extern crate serde; -use serde::Deserialize; -use std::collections::HashMap; - -#[derive(Debug, Deserialize, PartialEq)] -pub struct Data { - #[serde(rename = "Name")] - pub name: Option, - #[serde(rename = "$value")] - pub text: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct TimeCreated { - #[serde(rename = "SystemTime")] - pub system_time: String, -} - -#[derive(Debug, Deserialize, PartialEq)] -struct Execution { - #[serde(rename = "ProcessID")] - process_id: i32, - #[serde(rename = "ThreadID")] - thread_id: i32, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct Provider { - #[serde(rename = "Name")] - pub name: Option, - #[serde(rename = "Guid")] - guid: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct System { - #[serde(rename = "Provider")] - pub provider: Provider, - #[serde(rename = "EventID")] - pub event_id: String, - #[serde(rename = "Version")] - version: Option, - #[serde(rename = "Level")] - level: String, - #[serde(rename = "Task")] - task: String, - #[serde(rename = "Opcode")] - opcode: Option, - #[serde(rename = "Keywords")] - keywords: String, - #[serde(rename = "TimeCreated")] - pub time_created: TimeCreated, - #[serde(rename = "EventRecordID")] - pub event_record_id: String, - #[serde(rename = "Correlation")] - correlation: Option, - #[serde(rename = "Execution")] - execution: Option, - #[serde(rename = "Channel")] - pub channel: String, // Security, System, Application ...etc - #[serde(rename = "Computer")] - computer: String, - #[serde(rename = "Security")] - security: String, - #[serde(rename = "Message")] - pub message: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct EventData { - #[serde(rename = "Data")] - 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 { - // - // 文字列データを取得する - // - fn get_string(v: &Data) -> String { - let mut ret = "".to_string(); - if let Some(text) = &v.text { - ret = text.to_string(); - } - return ret; - } - - // - // EventDataをHashMapとして取得する - // - pub fn parse_event_data(&self) -> HashMap { - let mut values = HashMap::new(); - - if let Some(event_data) = &self.event_data { - if let Some(data) = &event_data.data { - for v in data.iter() { - if let Some(name) = &v.name { - values.insert(name.to_string(), Evtx::get_string(v)); - } - } - } - } - - values - } -} +extern crate serde; +use serde::Deserialize; +use std::collections::HashMap; + +#[derive(Debug, Deserialize, PartialEq)] +pub struct Data { + #[serde(rename = "Name")] + pub name: Option, + #[serde(rename = "$value")] + pub text: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct TimeCreated { + #[serde(rename = "SystemTime")] + pub system_time: String, +} + +#[derive(Debug, Deserialize, PartialEq)] +struct Execution { + #[serde(rename = "ProcessID")] + process_id: i32, + #[serde(rename = "ThreadID")] + thread_id: i32, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct Provider { + #[serde(rename = "Name")] + pub name: Option, + #[serde(rename = "Guid")] + guid: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct System { + #[serde(rename = "Provider")] + pub provider: Provider, + #[serde(rename = "EventID")] + pub event_id: String, + #[serde(rename = "Version")] + version: Option, + #[serde(rename = "Level")] + level: String, + #[serde(rename = "Task")] + task: String, + #[serde(rename = "Opcode")] + opcode: Option, + #[serde(rename = "Keywords")] + keywords: String, + #[serde(rename = "TimeCreated")] + pub time_created: TimeCreated, + #[serde(rename = "EventRecordID")] + pub event_record_id: String, + #[serde(rename = "Correlation")] + correlation: Option, + #[serde(rename = "Execution")] + execution: Option, + #[serde(rename = "Channel")] + pub channel: String, // Security, System, Application ...etc + #[serde(rename = "Computer")] + computer: String, + #[serde(rename = "Security")] + security: String, + #[serde(rename = "Message")] + pub message: Option, +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct EventData { + #[serde(rename = "Data")] + 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 { + // + // 文字列データを取得する + // + fn get_string(v: &Data) -> String { + let mut ret = "".to_string(); + if let Some(text) = &v.text { + ret = text.to_string(); + } + return ret; + } + + // + // EventDataをHashMapとして取得する + // + pub fn parse_event_data(&self) -> HashMap { + let mut values = HashMap::new(); + + if let Some(event_data) = &self.event_data { + if let Some(data) = &event_data.data { + for v in data.iter() { + if let Some(name) = &v.name { + values.insert(name.to_string(), Evtx::get_string(v)); + } + } + } + } + + values + } +} From 32c6e13ccfe8f6d523f68b3376a44aa542fc074c Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Tue, 6 Oct 2020 22:13:00 +0900 Subject: [PATCH 08/13] 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 09/13] 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 c62c8dc32688c92ad4c37f49522e3a3057389ed1 Mon Sep 17 00:00:00 2001 From: siamease Date: Wed, 7 Oct 2020 00:16:47 +0900 Subject: [PATCH 10/13] fix --- src/detections/sysmon.rs | 49 +++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/detections/sysmon.rs b/src/detections/sysmon.rs index 613bf183..7cbab355 100644 --- a/src/detections/sysmon.rs +++ b/src/detections/sysmon.rs @@ -1,11 +1,16 @@ use crate::models::event; use std::collections::HashMap; -pub struct Sysmon {} +pub struct Sysmon { + checkunsigned: u64, +} impl Sysmon { pub fn new() -> Sysmon { - Sysmon {} + Sysmon { + //checkunsigned: 0, + checkunsigned: 1, + } } pub fn detection( @@ -15,23 +20,45 @@ impl Sysmon { event_data: HashMap, ) { if event_id == "1" { - &self.sysmon_event_1(event_data); + &self.check_command_lines(event_data); } else if event_id == "7" { - &self.sysmon_event_7(event_data); + &self.check_for_unsigned_files(event_data); } } - fn sysmon_event_1(&mut self, event_data: HashMap) { - println!("Message : Sysmon event 1"); - if let Some(_image) = event_data.get("Image") { - println!("_image : {}", _image); + fn check_command_lines(&mut self, event_data: HashMap) { + // Check command lines + if let Some(_date) = event_data.get("UtcTime") { + println!("Date : {} (UTC)", _date); } + println!("Log : Sysmon"); + println!("EventID : 1"); + //if let Some(_creater) = event_data.get("ParentImage") { + // println!("_creater : {}", _image); + //} if let Some(_command_line) = event_data.get("CommandLine") { - println!("_command_line : {}", _command_line); + self.check_command("1", event_data); + println!("Command : {}", _command_line); + } + println!(""); + } + + fn check_for_unsigned_files(&mut self, event_data: HashMap) { + // Check for unsigned EXEs/DLLs: + // This can be very chatty, so it's disabled. + // Set $checkunsigned to 1 (global variable section) to enable: + if self.checkunsigned == 1 { + if let Some(_date) = event_data.get("UtcTime") { + println!("Date : {} (UTC)", _date); + } + println!("Log : Sysmon"); + println!("EventID : 7"); + //# TBD + println!(""); } } - fn sysmon_event_7(&mut self, event_data: HashMap) { - println!("Message : Sysmon event 7"); + fn check_command(&mut self, event_id: String, event_data: HashMap) { + //# TBD } } From c3feb1eca202276ab1cac2c596bb4f4c327bee74 Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Wed, 7 Oct 2020 00:53:19 +0900 Subject: [PATCH 11/13] 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 1c2ec6e6dd5e87dcf04c7cd96fafb7249f17e844 Mon Sep 17 00:00:00 2001 From: siamease Date: Wed, 7 Oct 2020 00:56:03 +0900 Subject: [PATCH 12/13] Implementation --- src/detections/sysmon.rs | 60 ++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/detections/sysmon.rs b/src/detections/sysmon.rs index 7cbab355..165e39db 100644 --- a/src/detections/sysmon.rs +++ b/src/detections/sysmon.rs @@ -8,8 +8,8 @@ pub struct Sysmon { impl Sysmon { pub fn new() -> Sysmon { Sysmon { - //checkunsigned: 0, - checkunsigned: 1, + //checkunsigned: 0, // DeepBlueでは0固定 + checkunsigned: 1, // 開発用に1 } } @@ -28,19 +28,17 @@ impl Sysmon { fn check_command_lines(&mut self, event_data: HashMap) { // Check command lines - if let Some(_date) = event_data.get("UtcTime") { - println!("Date : {} (UTC)", _date); - } - println!("Log : Sysmon"); - println!("EventID : 1"); - //if let Some(_creater) = event_data.get("ParentImage") { - // println!("_creater : {}", _image); - //} if let Some(_command_line) = event_data.get("CommandLine") { - self.check_command("1", event_data); - println!("Command : {}", _command_line); + if let Some(_date) = event_data.get("UtcTime") { + println!("Date : {} (UTC)", _date); + } + println!("Log : Sysmon"); + //if let Some(_creater) = event_data.get("ParentImage") { + // println!("_creater : {}", _image); + //} + self.check_command("1".to_string(), _command_line.to_string()); + println!(""); } - println!(""); } fn check_for_unsigned_files(&mut self, event_data: HashMap) { @@ -48,17 +46,37 @@ impl Sysmon { // This can be very chatty, so it's disabled. // Set $checkunsigned to 1 (global variable section) to enable: if self.checkunsigned == 1 { - if let Some(_date) = event_data.get("UtcTime") { - println!("Date : {} (UTC)", _date); + if let Some(_signed) = event_data.get("Signed") { + if _signed == "false" { + if let Some(_date) = event_data.get("UtcTime") { + println!("Date : {} (UTC)", _date); + } + println!("Log : Sysmon"); + println!("EventID : 7"); + println!("Message : Unsigned Image (DLL)"); + if let Some(_image) = event_data.get("Image") { + println!("Result : Loaded by: {}", _image); + } + if let Some(_command_line) = event_data.get("ImageLoaded") { + println!("Command : {}", _command_line); + } + println!(""); + } } - println!("Log : Sysmon"); - println!("EventID : 7"); - //# TBD - println!(""); } } - fn check_command(&mut self, event_id: String, event_data: HashMap) { - //# TBD + fn check_command(&mut self, _event_id: String, _command_line: String) { + let _result = "(TBD)"; + let _decoded = "(TBD)"; + + // TBD + + // Write-Output $obj + println!("EventID : {}", _event_id); + println!("Message : Suspicious Command Line"); + println!("Result : {}", _result); + println!("Command : {}", _command_line); + println!("Decoded : {}", _decoded); } } From 6ad9a773614604379212a2a58a822df1f3b534b1 Mon Sep 17 00:00:00 2001 From: ichiichi11 Date: Thu, 8 Oct 2020 08:30:56 +0900 Subject: [PATCH 13/13] 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(); + } +}