use crate::detections::utils; use crate::models::event; use std::collections::HashMap; #[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) { self.disp_admin_logons().and_then(Security::print_console); self.disp_login_failed().and_then(Security::print_console); } fn disp_admin_logons(&self) -> Option> { if self.total_admin_logons < 1 { return Option::None; } let mut msges: Vec = Vec::new(); msges.push(format!("total_admin_logons:{}", self.total_admin_logons)); msges.push(format!("admin_logons:{:?}", self.admin_logons)); msges.push(format!( "multiple_admin_logons:{:?}", self.multiple_admin_logons )); return Option::Some(msges); } fn disp_login_failed(&self) -> Option> { let exceed_failed_logons = self.total_failed_logons <= self.max_failed_logons; let exist_failed_account = self.account_2_failedcnt.keys().count() as i32 <= 1; if exceed_failed_logons || exist_failed_account { return Option::None; } let mut msges: Vec = Vec::new(); msges.push(format!( "High number of total logon failures for multiple accounts" )); msges.push(format!( "Total accounts: {}", self.account_2_failedcnt.keys().count() )); msges.push(format!( "Total logon failures: {}", self.total_failed_logons )); return Option::Some(msges); } pub fn detection( &mut self, event_id: String, _system: &event::System, user_data: &Option, event_data: HashMap, ) { self.process_created(&event_id, &event_data); self.se_debug_privilege(&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) .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) { if event_id != "4688" { return; } let commandline = event_data.get("CommandLine").unwrap_or(&self.empty_str); let creator = event_data .get("ParentProcessName") .unwrap_or(&self.empty_str); utils::check_command(4688, &commandline, 1000, 0, &self.empty_str, &creator); } // // 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, ) -> Option> { if event_id != "4720" { return Option::None; } let mut msges: Vec = Vec::new(); msges.push("New User Created".to_string()); let username = event_data.get("TargetUserName").unwrap_or(&self.empty_str); msges.push(format!("Username: {}", username)); let sid = event_data.get("TargetSid").unwrap_or(&self.empty_str); msges.push(format!("User SID: {}", sid)); return Option::Some(msges); } // add member to security group fn add_member_security_group( &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 Option::None; } // A member was added to a security-enabled (global|local|universal) group. let mut msges: Vec = Vec::new(); if event_id == "4728" { msges.push("User added to global Administrators group".to_string()); } else if event_id == "4732" { msges.push("User added to local Administrators group".to_string()); } else if event_id == "4756" { msges.push("User added to universal Administrators group".to_string()); } else { return Option::None; } let username = event_data.get("MemberName").unwrap_or(&self.empty_str); msges.push(format!("Username: {}", username)); let sid = event_data.get("MemberSid").unwrap_or(&self.empty_str); msges.push(format!("User SID: {}", sid)); return Option::Some(msges); } // An account failed to log on.:OK // 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, ) -> Option> { if event_id != "4673" { 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 { return Option::None; } msges.push("Sensititive Privilege Use Exceeds Threshold".to_string()); msges.push( "Potentially indicative of Mimikatz, multiple sensitive privilege calls have been made" .to_string(), ); let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); msges.push(format!("Username: {}", username)); let domainname = event_data .get("SubjectDomainName") .unwrap_or(&self.empty_str); msges.push(format!("Domain Name: {}", domainname)); return Option::Some(msges); } fn attempt_priviledge( &mut self, event_id: &String, event_data: &HashMap, ) -> Option> { if event_id != "4674" { return Option::None; } // "%%1539" means WRITE_DAC(see detail: https://docs.microsoft.com/ja-jp/windows/security/threat-protection/auditing/event-4663) let servicename = event_data .get("ProcessName") .unwrap_or(&self.empty_str) .to_uppercase(); let accessname = event_data.get("AccessMask").unwrap_or(&self.empty_str); if servicename != r"C:\WINDOWS\SYSTEM32\SERVICES.EXE" || accessname != "%%1539" { return Option::None; } let mut msges: Vec = Vec::new(); msges.push("Possible Hidden Service Attempt".to_string()); msges.push("User requested to modify the Dynamic Access Control (DAC) permissions of a sevice, possibly to hide it from view".to_string()); let username = event_data.get("SubjectUserName").unwrap_or(&self.empty_str); msges.push(format!("User: {}", username)); let servicename = event_data.get("ObjectName").unwrap_or(&self.empty_str); msges.push(format!("Target service: {}", servicename)); msges.push("WRITE_DAC".to_string()); return Option::Some(msges); } // A logon was attempted using explicit credentials. fn pass_spray( &mut self, event_id: &String, event_data: &HashMap, ) -> Option> { if event_id != "4648" { return Option::None; } 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 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 Option::None; } // 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); acc.push_str(" "); return acc; }, ); let mut msges: Vec = Vec::new(); msges.push( "Distributed Account Explicit Credential Use (Password Spray Attack)".to_string(), ); 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, ) -> Option> { if event_id != "1102" { return Option::None; } let mut msges: Vec = Vec::new(); msges.push("Audit Log Clear".to_string()); msges.push("The Audit log was cleared".to_string()); let username = user_data .as_ref() .and_then(|u| u.log_file_cleared.as_ref()) .and_then(|l| l.subject_user_name.as_ref()); msges.push(format!( "Security ID: {}", username.unwrap_or(&self.empty_str) )); 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(); } }