From 8b9dac961a08416ab1e3a7067df23fde1ff3928f Mon Sep 17 00:00:00 2001 From: DustInDark Date: Fri, 3 Dec 2021 10:12:31 +0900 Subject: [PATCH 1/4] added progress bar #199 (#247) --- Cargo.lock | 53 +++++++++++++++++++++++++++++++++++++++++++---------- Cargo.toml | 1 + src/main.rs | 3 +++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 334091d3..b1299b9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -312,10 +312,20 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "maybe-uninit", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.5", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -323,7 +333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "maybe-uninit", ] @@ -335,7 +345,7 @@ checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg 1.0.1", "cfg-if 0.1.10", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "lazy_static", "maybe-uninit", "memoffset", @@ -349,7 +359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ "cfg-if 0.1.10", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "maybe-uninit", ] @@ -364,6 +374,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "csv" version = "1.1.3" @@ -730,6 +750,7 @@ dependencies = [ "linked-hash-map", "mopa", "num_cpus", + "pbr", "quick-xml 0.17.2", "regex", "serde", @@ -1288,6 +1309,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "pbr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2" +dependencies = [ + "crossbeam-channel 0.5.1", + "libc", + "time 0.1.44", + "winapi 0.3.9", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -1564,9 +1597,9 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" dependencies = [ - "crossbeam-channel", + "crossbeam-channel 0.4.4", "crossbeam-deque", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "lazy_static", "num_cpus", ] @@ -2209,7 +2242,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "futures", ] @@ -2241,7 +2274,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "futures", "lazy_static", "log", @@ -2286,7 +2319,7 @@ checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" dependencies = [ "crossbeam-deque", "crossbeam-queue", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "futures", "lazy_static", "log", @@ -2301,7 +2334,7 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "futures", "slab", "tokio-executor", diff --git a/Cargo.toml b/Cargo.toml index 124b102e..7ced827a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ mopa = "0.2.2" slack-hook = "0.8" dotenv = "0.15.0" hhmmss = "*" +pbr = "*" [target.x86_64-pc-windows-gnu] linker = "x86_64-w64-mingw32-gcc" diff --git a/src/main.rs b/src/main.rs index 7898036f..dd4d59df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use hayabusa::omikuji::Omikuji; use hayabusa::{afterfact::after_fact, detections::utils}; use hayabusa::{detections::configs, timeline::timeline::Timeline}; use hhmmss::Hhmmss; +use pbr::ProgressBar; use serde_json::Value; use std::{ fs::{self, File}, @@ -124,12 +125,14 @@ fn analysis_files(evtx_files: Vec) { level, configs::CONFIG.read().unwrap().args.value_of("rules"), ); + let mut pb = ProgressBar::new(evtx_files.len() as u64); let mut detection = detection::Detection::new(rule_files); for evtx_file in evtx_files { if configs::CONFIG.read().unwrap().args.is_present("verbose") { println!("Checking target evtx FilePath: {:?}", &evtx_file); } detection = analysis_file(evtx_file, detection); + pb.inc(); } after_fact(); detection.print_unique_results(); From e0936ab2d13ea9ba228c021e130bbad803a1fd1b Mon Sep 17 00:00:00 2001 From: Yamato Security <71482215+YamatoSecurity@users.noreply.github.com> Date: Fri, 3 Dec 2021 15:52:43 +0900 Subject: [PATCH 2/4] rule update (#249) --- config/exclude-rules.txt | 5 +++++ config/noisy-rules.txt | 5 +++++ .../4625_LateralMovement_LogonFailure-WrongPassword.yml | 2 +- .../4625_LateralMovement_LogonFailure-WrongUsername.yml | 2 +- ...732-AccountManipulation_UserAddedToLocalSecurityGroup.yml | 2 +- ...rRemovalOnHost-ClearWindowsEventLogs_SystemLogCleared.yml | 2 +- .../BitsClientOperational/59_BITS-Jobs_BitsJobCreation.yml | 2 +- .../Security/Logons/4624_LogonType-11-CachedInteractive.yml | 2 +- .../Logons/4624_LogonType-12-CachedRemoteInteractive.yml | 2 +- .../events/Security/Logons/4624_LogonType-2-Interactive.yml | 2 +- .../events/Security/Logons/4624_LogonType-4-Batch.yml | 2 +- .../events/Security/Logons/4647_LogoffUserInitiated.yml | 2 +- rules/hayabusa/events/Security/Logons/4672_AdminLogon.yml | 2 +- .../events/Security/Logons/4768_KerberosTGT-Request.yml | 2 +- .../events/Security/Logons/4776_NTLM-LogonToLocalAccount.yml | 4 ++-- 15 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 config/exclude-rules.txt create mode 100644 config/noisy-rules.txt diff --git a/config/exclude-rules.txt b/config/exclude-rules.txt new file mode 100644 index 00000000..47044f72 --- /dev/null +++ b/config/exclude-rules.txt @@ -0,0 +1,5 @@ +c92f1896-d1d2-43c3-92d5-7a5b35c217bb # rules/sigma/other/win_exchange_cve_2021_42321.yml (rule parse error) +83809e84-4475-4b69-bc3e-4aad8568612f # rules/sigma/builtin/win_exchange_transportagent.yml (rule parse error) +7b449a5e-1db5-4dd0-a2dc-4e3a67282538 # replaced by hayabusa rule +c265cf08-3f99-46c1-8d59-328247057d57 # replaced by hayabusa rule +66b6be3d-55d0-4f47-9855-d69df21740ea # replaced by hayabusa rule \ No newline at end of file diff --git a/config/noisy-rules.txt b/config/noisy-rules.txt new file mode 100644 index 00000000..6e03bcf7 --- /dev/null +++ b/config/noisy-rules.txt @@ -0,0 +1,5 @@ +0f06a3a5-6a09-413f-8743-e6cf35561297 # sysmon_wmi_event_subscription.yml +b0d77106-7bb0-41fe-bd94-d1752164d066 # win_rare_schtasks_creations.yml +66bfef30-22a5-4fcd-ad44-8d81e60922ae # win_rare_service_installs.yml +e98374a6-e2d9-4076-9b5c-11bdb2569995 # win_susp_failed_logons_single_source.yml +6309ffc4-8fa2-47cf-96b8-a2f72e58e538 # win_susp_failed_logons_single_source2.yml \ No newline at end of file diff --git a/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongPassword.yml b/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongPassword.yml index e9f587ab..a48aedf8 100644 --- a/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongPassword.yml +++ b/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongPassword.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : タイプ: %LogonType% : 端末: % description: Prints logon information. description_jp: Prints logon information. -id: a85096da-be85-48d7-8ad5-2f957cd74daa +id: e87bd730-df45-4ae9-85de-6c75369c5d29 level: low status: stable detection: diff --git a/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongUsername.yml b/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongUsername.yml index 35c7936b..d31e06c0 100644 --- a/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongUsername.yml +++ b/rules/hayabusa/alerts/Security/4625_LateralMovement_LogonFailure-WrongUsername.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : タイプ: %LogonType% : 端末: % description: Prints logon information. description_jp: Prints logon information. -id: a85096da-be85-48d7-8ad5-2f957cd74daa +id: 8afa97ce-a217-4f7c-aced-3e320a57756d level: informational status: stable detection: diff --git a/rules/hayabusa/alerts/Security/4732-AccountManipulation_UserAddedToLocalSecurityGroup.yml b/rules/hayabusa/alerts/Security/4732-AccountManipulation_UserAddedToLocalSecurityGroup.yml index 859d37db..b925c4b8 100644 --- a/rules/hayabusa/alerts/Security/4732-AccountManipulation_UserAddedToLocalSecurityGroup.yml +++ b/rules/hayabusa/alerts/Security/4732-AccountManipulation_UserAddedToLocalSecurityGroup.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %MemberName% : SID: %MemberSid% : グループ名: %T description: A user was added to a security-enabled local group. description_jp: ユーザがローカルセキュリティグループに追加された。 -id: 611e2e76-a28f-4255-812c-eb8836b2f5bb +id: 2f04e44e-1c79-4343-b4ab-ba670ee10aa0 level: low status: stable detection: diff --git a/rules/hayabusa/alerts/System/104_IndicatorRemovalOnHost-ClearWindowsEventLogs_SystemLogCleared.yml b/rules/hayabusa/alerts/System/104_IndicatorRemovalOnHost-ClearWindowsEventLogs_SystemLogCleared.yml index 91fa9280..d76fd058 100644 --- a/rules/hayabusa/alerts/System/104_IndicatorRemovalOnHost-ClearWindowsEventLogs_SystemLogCleared.yml +++ b/rules/hayabusa/alerts/System/104_IndicatorRemovalOnHost-ClearWindowsEventLogs_SystemLogCleared.yml @@ -9,7 +9,7 @@ output_jp: "ユーザ名: %LogFileClearedSubjectUserName%" description: Somebody has cleared the System event log. description_jp: 誰かがシステムログをクリアした。 -id: c2f690ac-53f8-4745-8cfe-7127dda28c74 +id: f481a1f3-969e-4187-b3a5-b47c272bfebd level: high status: stable detection: diff --git a/rules/hayabusa/events/BitsClientOperational/59_BITS-Jobs_BitsJobCreation.yml b/rules/hayabusa/events/BitsClientOperational/59_BITS-Jobs_BitsJobCreation.yml index 50ed1553..247f8113 100644 --- a/rules/hayabusa/events/BitsClientOperational/59_BITS-Jobs_BitsJobCreation.yml +++ b/rules/hayabusa/events/BitsClientOperational/59_BITS-Jobs_BitsJobCreation.yml @@ -9,7 +9,7 @@ output_jp: 'Job名: %JobTitle% : URL: %Url%' description: Adversaries may abuse BITS jobs to persistently execute or clean up after malicious payloads. description_jp: Adversaries may abuse BITS jobs to persistently execute or clean up after malicious payloads. -id: d3fb8f7b-88b0-4ff4-bf9b-ca286ce19031 +id: 18e6fa4a-353d-42b6-975c-bb05dbf4a004 level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4624_LogonType-11-CachedInteractive.yml b/rules/hayabusa/events/Security/Logons/4624_LogonType-11-CachedInteractive.yml index 0d70a81f..23b6a503 100644 --- a/rules/hayabusa/events/Security/Logons/4624_LogonType-11-CachedInteractive.yml +++ b/rules/hayabusa/events/Security/Logons/4624_LogonType-11-CachedInteractive.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : 端末: %WorkstationName% : IPア description: Prints logon information. description_jp: Prints logon information. -id: e50e3952-06d9-44a8-ab07-7a41c9801d78 +id: fbbe9d3f-ed1f-49a9-9446-726e349f5fba level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4624_LogonType-12-CachedRemoteInteractive.yml b/rules/hayabusa/events/Security/Logons/4624_LogonType-12-CachedRemoteInteractive.yml index 31d17a0d..0b4aa4a8 100644 --- a/rules/hayabusa/events/Security/Logons/4624_LogonType-12-CachedRemoteInteractive.yml +++ b/rules/hayabusa/events/Security/Logons/4624_LogonType-12-CachedRemoteInteractive.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : 端末: %WorkstationName% : IPア description: Prints logon information. description_jp: Prints logon information. -id: e50e3952-06d9-44a8-ab07-7a41c9801d78 +id: f4b46dd3-63d6-4c75-a54c-9f6bd095cd6f level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4624_LogonType-2-Interactive.yml b/rules/hayabusa/events/Security/Logons/4624_LogonType-2-Interactive.yml index 62c8f35e..f555d9e9 100644 --- a/rules/hayabusa/events/Security/Logons/4624_LogonType-2-Interactive.yml +++ b/rules/hayabusa/events/Security/Logons/4624_LogonType-2-Interactive.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : 端末: %WorkstationName% : IPア description: Prints logon information description_jp: Prints logon information -id: c7b22878-e5d8-4c30-b245-e51fd354359e +id: 7beb4832-f357-47a4-afd8-803d69a5c85c level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4624_LogonType-4-Batch.yml b/rules/hayabusa/events/Security/Logons/4624_LogonType-4-Batch.yml index 41bddeb3..e5cc2622 100644 --- a/rules/hayabusa/events/Security/Logons/4624_LogonType-4-Batch.yml +++ b/rules/hayabusa/events/Security/Logons/4624_LogonType-4-Batch.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : 端末: %WorkstationName% : IPア description: Prints logon information description_jp: Prints logon information -id: 408e1304-51d7-4d3e-ab31-afd07192400b +id: 8ad8b25f-6052-4cfd-9a50-717cb514af13 level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4647_LogoffUserInitiated.yml b/rules/hayabusa/events/Security/Logons/4647_LogoffUserInitiated.yml index 202bc2d8..34b7a268 100644 --- a/rules/hayabusa/events/Security/Logons/4647_LogoffUserInitiated.yml +++ b/rules/hayabusa/events/Security/Logons/4647_LogoffUserInitiated.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : ログオンID: %TargetLogonId%' description: Prints logon information. description_jp: Prints logon information. -id: 7309e070-56b9-408b-a2f4-f1840f8f1ebf +id: 6bad16f1-02c4-4075-b414-3cd16944bc65 level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4672_AdminLogon.yml b/rules/hayabusa/events/Security/Logons/4672_AdminLogon.yml index cd2ad1d3..c3c9346f 100644 --- a/rules/hayabusa/events/Security/Logons/4672_AdminLogon.yml +++ b/rules/hayabusa/events/Security/Logons/4672_AdminLogon.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %SubjectUserName% : ログオンID: %SubjectLogonId%' description: Prints logon information. description_jp: Prints logon information. -id: 7309e070-56b9-408b-a2f4-f1840f8f1ebf +id: fdd0b325-8b89-469c-8b0c-e5ddfe39b62e level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4768_KerberosTGT-Request.yml b/rules/hayabusa/events/Security/Logons/4768_KerberosTGT-Request.yml index 2e199fd0..20a61d27 100644 --- a/rules/hayabusa/events/Security/Logons/4768_KerberosTGT-Request.yml +++ b/rules/hayabusa/events/Security/Logons/4768_KerberosTGT-Request.yml @@ -9,7 +9,7 @@ output_jp: 'ユーザ: %TargetUserName% : サービス: %ServiceName% : IP description: Prints logon information. description_jp: Prints logon information. -id: da6257f3-cf49-464a-96fc-c84a7ce20636 +id: d9f336ea-bb16-4a35-8a9c-183216b8d59c level: informational status: stable detection: diff --git a/rules/hayabusa/events/Security/Logons/4776_NTLM-LogonToLocalAccount.yml b/rules/hayabusa/events/Security/Logons/4776_NTLM-LogonToLocalAccount.yml index e459d48a..2ca207a7 100644 --- a/rules/hayabusa/events/Security/Logons/4776_NTLM-LogonToLocalAccount.yml +++ b/rules/hayabusa/events/Security/Logons/4776_NTLM-LogonToLocalAccount.yml @@ -4,8 +4,8 @@ modified: 2021/11/26 title: NTLM Logon to Local Account title_jp: ローカルアカウントへのNTLMログオン -output: 'User: %TargetUserName% : Workstation %WorkstationName% : Status: %Status%' -output_jp: 'ユーザ: %TargetUserName% : 端末: %WorkstationName% : ステータス: %Status%' +output: 'User: %TargetUserName% : Workstation %Workstation% : Status: %Status%' +output_jp: 'ユーザ: %TargetUserName% : 端末: %Workstation% : ステータス: %Status%' description: Prints logon information. description_jp: Prints logon information. From d1121297719ce9e9b7cb977c37ddfe7b1d55deb4 Mon Sep 17 00:00:00 2001 From: DustInDark Date: Sat, 4 Dec 2021 11:20:11 +0900 Subject: [PATCH 3/4] changed stdout result delimiter #244 (#245) * changed stdout result delimiter #244 * removed unnecessary space #244 * added display output test #244 - added static map clear function (only test use) - added outputformat test case of stdout (change sequencial process in emit_csv test To prevent the contents of static variables from changing depending on the order of execution) * fixed typo --- src/afterfact.rs | 247 ++++++++++++++++++++++++------------ src/detections/detection.rs | 4 +- src/detections/print.rs | 5 + 3 files changed, 176 insertions(+), 80 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index dd1ae5fb..2be77936 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -72,19 +72,26 @@ pub fn after_fact() { } fn emit_csv(writer: &mut W, displayflag: bool) -> io::Result<()> { - let mut wtr = csv::WriterBuilder::new().from_writer(writer); + let mut wtr; + if displayflag { + wtr = csv::WriterBuilder::new() + .delimiter(b'|') + .from_writer(writer); + } else { + wtr = csv::WriterBuilder::new().from_writer(writer); + } let messages = print::MESSAGES.lock().unwrap(); let mut detect_count = 0; for (time, detect_infos) in messages.iter() { for detect_info in detect_infos { if displayflag { wtr.serialize(DisplayFormat { - time: &format_time(time), - level: &detect_info.level, - computername: &detect_info.computername, - eventid: &detect_info.eventid, - alert: &detect_info.alert, - details: &detect_info.detail, + time: &format!("{} ", &format_time(time)), + level: &format!(" {} ", &detect_info.level), + computername: &format!(" {} ", &detect_info.computername), + eventid: &format!(" {} ", &detect_info.eventid), + alert: &format!(" {} ", &detect_info.alert), + details: &format!(" {}", &detect_info.detail), })?; } else { // csv出力時フォーマット @@ -131,81 +138,165 @@ where } } -#[test] -fn test_emit_csv() { +#[cfg(test)] +mod tests { + use crate::afterfact::emit_csv; + use crate::detections::print; + use chrono::{Local, TimeZone, Utc}; use serde_json::Value; + use std::fs::File; use std::fs::{read_to_string, remove_file}; - let testfilepath: &str = "test.evtx"; - let testrulepath: &str = "test-rule.yml"; - let test_title = "test_title"; - let test_level = "high"; - let test_computername = "testcomputer"; - let test_eventid = "1111"; - let output = "pokepoke"; - { - let mut messages = print::MESSAGES.lock().unwrap(); + use std::io; - let val = r##" - { - "Event": { - "EventData": { - "CommandRLine": "hoge" - }, - "System": { - "TimeCreated_attributes": { - "SystemTime": "1996-02-27T01:05:01Z" - } - } - } - } - "##; - let event: Value = serde_json::from_str(val).unwrap(); - messages.insert( - testfilepath.to_string(), - testrulepath.to_string(), - &event, - test_level.to_string(), - test_computername.to_string(), - test_eventid.to_string(), - test_title.to_string(), - output.to_string(), - ); + #[test] + fn test_emit_csv() { + //テストの並列処理によって読み込みの順序が担保できずstatic変数の内容が担保が取れない為、このテストはシーケンシャルで行う + test_emit_csv_output(); + test_emit_csv_output(); } - let expect_time = Utc - .datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ") - .unwrap(); - let expect_tz = expect_time.with_timezone(&Local); - let expect = "Time,Computername,Eventid,Level,Alert,Details,Rulepath,Filepath\n".to_string() - + &expect_tz - .clone() - .format("%Y-%m-%d %H:%M:%S%.3f %:z") - .to_string() - + "," - + test_computername - + "," - + test_eventid - + "," - + test_level - + "," - + test_title - + "," - + output - + "," - + testrulepath - + "," - + &testfilepath.to_string() - + "\n"; - - let mut file: Box = - Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap()); - assert!(emit_csv(&mut file, false).is_ok()); - - match read_to_string("./test_emit_csv.csv") { - Err(_) => panic!("Failed to open file."), - Ok(s) => { - assert_eq!(s, expect); + fn test_emit_csv_output() { + let testfilepath: &str = "test.evtx"; + let testrulepath: &str = "test-rule.yml"; + let test_title = "test_title"; + let test_level = "high"; + let test_computername = "testcomputer"; + let test_eventid = "1111"; + let output = "pokepoke"; + { + let mut messages = print::MESSAGES.lock().unwrap(); + messages.clear(); + let val = r##" + { + "Event": { + "EventData": { + "CommandRLine": "hoge" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event: Value = serde_json::from_str(val).unwrap(); + messages.insert( + testfilepath.to_string(), + testrulepath.to_string(), + &event, + test_level.to_string(), + test_computername.to_string(), + test_eventid.to_string(), + test_title.to_string(), + output.to_string(), + ); } - }; - assert!(remove_file("./test_emit_csv.csv").is_ok()); + let expect_time = Utc + .datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ") + .unwrap(); + let expect_tz = expect_time.with_timezone(&Local); + let expect = "Time,Computername,Eventid,Level,Alert,Details,Rulepath,Filepath\n" + .to_string() + + &expect_tz + .clone() + .format("%Y-%m-%d %H:%M:%S%.3f %:z") + .to_string() + + "," + + test_computername + + "," + + test_eventid + + "," + + test_level + + "," + + test_title + + "," + + output + + "," + + testrulepath + + "," + + &testfilepath.to_string() + + "\n"; + let mut file: Box = + Box::new(File::create("./test_emit_csv.csv".to_string()).unwrap()); + assert!(emit_csv(&mut file, false).is_ok()); + match read_to_string("./test_emit_csv.csv") { + Err(_) => panic!("Failed to open file."), + Ok(s) => { + assert_eq!(s, expect); + } + }; + assert!(remove_file("./test_emit_csv.csv").is_ok()); + check_emit_csv_display(); + } + + fn check_emit_csv_display() { + let testfilepath: &str = "test2.evtx"; + let testrulepath: &str = "test-rule2.yml"; + let test_title = "test_title2"; + let test_level = "medium"; + let test_computername = "testcomputer2"; + let test_eventid = "2222"; + let output = "displaytest"; + { + let mut messages = print::MESSAGES.lock().unwrap(); + messages.clear(); + let val = r##" + { + "Event": { + "EventData": { + "CommandRLine": "hoge" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event: Value = serde_json::from_str(val).unwrap(); + messages.insert( + testfilepath.to_string(), + testrulepath.to_string(), + &event, + test_level.to_string(), + test_computername.to_string(), + test_eventid.to_string(), + test_title.to_string(), + output.to_string(), + ); + messages.debug(); + } + let expect_time = Utc + .datetime_from_str("1996-02-27T01:05:01Z", "%Y-%m-%dT%H:%M:%SZ") + .unwrap(); + let expect_tz = expect_time.with_timezone(&Local); + let expect = "Time|Computername|Eventid|Level|Alert|Details\n".to_string() + + &expect_tz + .clone() + .format("%Y-%m-%d %H:%M:%S%.3f %:z") + .to_string() + + " | " + + test_computername + + " | " + + test_eventid + + " | " + + test_level + + " | " + + test_title + + " | " + + output + + "\n"; + let mut file: Box = + Box::new(File::create("./test_emit_csv_display.txt".to_string()).unwrap()); + assert!(emit_csv(&mut file, true).is_ok()); + match read_to_string("./test_emit_csv_display.txt") { + Err(_) => panic!("Failed to open file."), + Ok(s) => { + assert_eq!(s, expect); + } + }; + assert!(remove_file("./test_emit_csv_display.txt").is_ok()); + } } diff --git a/src/detections/detection.rs b/src/detections/detection.rs index b103a742..8012f1b9 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -151,9 +151,9 @@ impl Detection { "Medium", "Low", "Informational", - "Undeifned", + "Undefined", ]); - // levclcounts is [(Undeifned), (Informational), (Low),(Medium),(High),(Critical)] + // levclcounts is [(Undefined), (Informational), (Low),(Medium),(High),(Critical)] let mut levelcounts = Vec::from([0, 0, 0, 0, 0, 0]); for rule in rules.into_iter() { if rule.check_exist_countdata() { diff --git a/src/detections/print.rs b/src/detections/print.rs index 95563ecc..e489587b 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -188,6 +188,11 @@ impl Message { return Option::Some(datetime.unwrap()); } } + + /// message内のマップをクリアする。テストする際の冪等性の担保のため作成。 + pub fn clear(&mut self) { + self.map.clear(); + } } impl AlertMessage { From ac5c5c2917e4a6716bb8e3449ed498682f0ddde5 Mon Sep 17 00:00:00 2001 From: DustInDark Date: Sat, 4 Dec 2021 11:49:38 +0900 Subject: [PATCH 4/4] Bugfix/yml alias not found all data output#227 (#241) * removed no use alias #227 * changed case of object type return none #227 - serde json value is object type when alias key dont exist in detected record. * adjust serde_number_to_string function return value change #227 * adjust yml rule to change of aliaskey_alias.txt #227 * merged same regex as static * create new struct to reduce same output in rule and keyword warn message #227 * changed output position * removed regression warnings #227 * removed output wanring * Fixed a possible panic when None. #227 * added parse_message test #227 * added get_serde_number_to_string tests #227 * removed unnecessary test data part in get_serde_numuber_to_string test #227 --- config/eventkey_alias.txt | 2 +- src/detections/detection.rs | 4 +- src/detections/print.rs | 99 ++++++++++++++++++++++++++++++++++--- src/detections/utils.rs | 68 +++++++++++++++++++++++-- 4 files changed, 162 insertions(+), 11 deletions(-) diff --git a/config/eventkey_alias.txt b/config/eventkey_alias.txt index d7ea313a..3188c4b9 100644 --- a/config/eventkey_alias.txt +++ b/config/eventkey_alias.txt @@ -126,7 +126,7 @@ SubjectDomainName,Event.EventData.SubjectDomainName SubjectLogonId,Event.EventData.SubjectLogonId SubjectUserName,Event.EventData.SubjectUserName SubjectUserSid,Event.EventData.SubjectUserSid -TargetDomainName,Event.EventData.TargetDomainName +TargetDomainName,Event.EventData.TargetDomainName TargetFilename,Event.EventData.TargetFilename TargetImage,Event.EventData.TargetImage TargetLogonId,Event.EventData.TargetLogonId diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 8012f1b9..b7173ffd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -206,7 +206,9 @@ impl Detection { record_info.record["Event"]["System"]["Computer"] .to_string() .replace("\"", ""), - get_serde_number_to_string(&record_info.record["Event"]["System"]["EventID"]), + get_serde_number_to_string(&record_info.record["Event"]["System"]["EventID"]) + .unwrap_or("-".to_owned()) + .to_string(), rule.yaml["title"].as_str().unwrap_or("").to_string(), rule.yaml["output"].as_str().unwrap_or("").to_string(), ); diff --git a/src/detections/print.rs b/src/detections/print.rs index e489587b..51e65acb 100644 --- a/src/detections/print.rs +++ b/src/detections/print.rs @@ -30,6 +30,7 @@ pub struct AlertMessage {} lazy_static! { pub static ref MESSAGES: Mutex = Mutex::new(Message::new()); + pub static ref ALIASREGEX: Regex = Regex::new(r"%[a-zA-Z0-9-_]+%").unwrap(); } impl Message { @@ -101,8 +102,7 @@ impl Message { fn parse_message(&mut self, event_record: &Value, output: String) -> String { let mut return_message: String = output; let mut hash_map: HashMap = HashMap::new(); - let re = Regex::new(r"%[a-zA-Z0-9-_]+%").unwrap(); - for caps in re.captures_iter(&return_message) { + for caps in ALIASREGEX.captures_iter(&return_message) { let full_target_str = &caps[0]; let target_length = full_target_str.chars().count() - 2; // The meaning of 2 is two percent let target_str = full_target_str @@ -118,16 +118,20 @@ impl Message { .get_event_key(target_str.to_string()) { let split: Vec<&str> = array_str.split(".").collect(); + let mut is_exist_event_key = false; let mut tmp_event_record: &Value = event_record.into(); for s in split { if let Some(record) = tmp_event_record.get(s) { + is_exist_event_key = true; tmp_event_record = record; } } - hash_map.insert( - full_target_str.to_string(), - get_serde_number_to_string(tmp_event_record), - ); + if is_exist_event_key { + let hash_value = get_serde_number_to_string(tmp_event_record); + if hash_value.is_some() { + hash_map.insert(full_target_str.to_string(), hash_value.unwrap()); + } + } } } @@ -332,4 +336,87 @@ mod tests { let mut stdout = stdout.lock(); AlertMessage::alert(&mut stdout, input.to_string()).expect("[WARN] TESTWarn!"); } + + #[test] + /// outputで指定されているキー(eventkey_alias.txt内で設定済み)から対象のレコード内の情報でメッセージをパースしているか確認する関数 + fn test_parse_message() { + let mut message = Message::new(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest1" + }, + "System": { + "Computer": "testcomputer1", + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest1 computername:testcomputer1"; + assert_eq!( + message.parse_message( + &event_record, + "commandline:%CommandLine% computername:%ComputerName%".to_owned() + ), + expected, + ); + } + #[test] + /// outputで指定されているキーが、eventkey_alias.txt内で設定されていない場合の出力テスト + fn test_parse_message_not_exist_key_in_output() { + let mut message = Message::new(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest2" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "NoExistKey:%TESTNoExistKey%"; + assert_eq!( + message.parse_message(&event_record, "NoExistKey:%TESTNoExistKey%".to_owned()), + expected, + ); + } + #[test] + /// outputで指定されているキー(eventkey_alias.txt内で設定済み)が対象のレコード内に該当する情報がない場合の出力テスト + fn test_parse_message_not_exist_value_in_record() { + let mut message = Message::new(); + let json_str = r##" + { + "Event": { + "EventData": { + "CommandLine": "parsetest3" + }, + "System": { + "TimeCreated_attributes": { + "SystemTime": "1996-02-27T01:05:01Z" + } + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + let expected = "commandline:parsetest3 computername:%ComputerName%"; + assert_eq!( + message.parse_message( + &event_record, + "commandline:%CommandLine% computername:%ComputerName%".to_owned() + ), + expected, + ); + } } diff --git a/src/detections/utils.rs b/src/detections/utils.rs index f354c3b5..9df91ff7 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -94,11 +94,14 @@ pub fn get_event_id_key() -> String { } /// serde:Valueの型を確認し、文字列を返します。 -pub fn get_serde_number_to_string(value: &serde_json::Value) -> String { +pub fn get_serde_number_to_string(value: &serde_json::Value) -> Option { if value.is_string() { - return value.as_str().unwrap_or("").to_string(); + return Option::Some(value.as_str().unwrap_or("").to_string()); + } else if value.is_object() { + // Object type is not specified record value. + return Option::None; } else { - return value.to_string(); + return Option::Some(value.to_string()); } } @@ -163,6 +166,7 @@ pub fn create_tokio_runtime() -> Runtime { mod tests { use crate::detections::utils; use regex::Regex; + use serde_json::Value; #[test] fn test_check_regex() { @@ -191,4 +195,62 @@ mod tests { let commandline = "\"C:\\Program Files\\Google\\Update\\GoogleUpdate2.exe\""; assert!(false == utils::check_allowlist(commandline, &allowlist)); } + + #[test] + /// Serde::Valueの数値型の値を文字列として返却することを確かめるテスト + fn test_get_serde_number_to_string() { + let json_str = r##" + { + "Event": { + "System": { + "EventID": 11111 + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + + assert_eq!( + utils::get_serde_number_to_string(&event_record["Event"]["System"]["EventID"]).unwrap(), + "11111".to_owned() + ); + } + + #[test] + /// Serde::Valueの文字列型の値を文字列として返却することを確かめるテスト + fn test_get_serde_number_serde_string_to_string() { + let json_str = r##" + { + "Event": { + "EventData": { + "ComputerName": "HayabusaComputer1" + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + + assert_eq!( + utils::get_serde_number_to_string(&event_record["Event"]["EventData"]["ComputerName"]) + .unwrap(), + "HayabusaComputer1".to_owned() + ); + } + + #[test] + /// Serde::Valueのオブジェクト型の内容を誤って渡した際にNoneを返却することを確かめるテスト + fn test_get_serde_number_serde_object_ret_none() { + let json_str = r##" + { + "Event": { + "EventData": { + "ComputerName": "HayabusaComputer1" + } + } + } + "##; + let event_record: Value = serde_json::from_str(json_str).unwrap(); + + assert!(utils::get_serde_number_to_string(&event_record["Event"]["EventData"]).is_none()); + } }