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/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/config/exclude-rules.txt b/config/exclude-rules.txt index 69422602..201932cc 100644 --- a/config/exclude-rules.txt +++ b/config/exclude-rules.txt @@ -1,2 +1,5 @@ -4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6 -c92f1896-d1d2-43c3-92d5-7a5b35c217bb \ No newline at end of file +4fe151c2-ecf9-4fae-95ae-b88ec9c2fca6 +c92f1896-d1d2-43c3-92d5-7a5b35c217bb +7b449a5e-1db5-4dd0-a2dc-4e3a67282538 +c265cf08-3f99-46c1-8d59-328247057d57 +66b6be3d-55d0-4f47-9855-d69df21740ea \ No newline at end of file diff --git a/config/noisy-rules.txt b/config/noisy-rules.txt index e69de29b..6e03bcf7 100644 --- a/config/noisy-rules.txt +++ 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. 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 d615c4ea..52dab334 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -157,9 +157,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() { @@ -212,7 +212,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 95563ecc..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()); + } + } } } @@ -188,6 +192,11 @@ impl Message { return Option::Some(datetime.unwrap()); } } + + /// message内のマップをクリアする。テストする際の冪等性の担保のため作成。 + pub fn clear(&mut self) { + self.map.clear(); + } } impl AlertMessage { @@ -327,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()); + } } diff --git a/src/main.rs b/src/main.rs index 86050cbd..f4e86cf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,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::collections::HashSet; use std::{ @@ -152,12 +153,14 @@ fn analysis_files(evtx_files: Vec) { configs::CONFIG.read().unwrap().args.value_of("rules"), &fill_ids, ); + 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();