From 72bc5689122eb3d4aa03e3718424fa07c63cd790 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:04:05 +0900 Subject: [PATCH 01/31] fix(afterfact): fixed wrong field name separate processing #1145 --- src/afterfact.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 8d52fff0e..d695ba0b7 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1462,7 +1462,11 @@ pub fn output_json_str( let mut tmp_stock = vec![]; let mut space_split_contents = detail_contents.split(' '); while let Some(sp) = space_split_contents.next() { - if !sp.contains('\\') && sp.ends_with(':') && sp.len() > 2 { + if !sp.contains('\\') + && !sp.starts_with('-') + && sp.ends_with(':') + && sp.len() > 2 + { key_index_stock.push(sp.replace(':', "")); if sp == "Payload:" { stocked_value.push(vec![]); From 77ee339a6a49c26e84a61aecd0f0b5996e482d3f Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:04:56 +0900 Subject: [PATCH 02/31] fix(afterfact): fixed comma separate field data processing in json-timeline #1145 --- src/afterfact.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index d695ba0b7..6fb6604f1 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1517,7 +1517,13 @@ pub fn output_json_str( key_index_stock[key_idx].as_str() }; if !output_value_stock.is_empty() { - output_value_stock.push_str(" | "); + let separate_chr = + if key_index_stock[key_idx].starts_with("ScriptBlock") { + " | " + } else { + ": " + }; + output_value_stock.push_str(separate_chr); } output_value_stock.push_str(&value.join(" ")); //1つまえのキーの段階で以降にvalueの配列で区切りとなる空の配列が存在しているかを確認する From 0df9a85a660ef3f235579a4c57caf12e9a84f274 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:45:02 +0900 Subject: [PATCH 03/31] fix(afterfact): fixed field wrong separate processing in "Final result:" #1145 --- src/afterfact.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/afterfact.rs b/src/afterfact.rs index 6fb6604f1..c5e845b17 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1478,6 +1478,12 @@ pub fn output_json_str( stocked_value.push(tmp_stock); tmp_stock = vec![]; } + } else if sp.ends_with(';') && sp.len() < 5 { + let last_key = key_index_stock.pop().unwrap_or_default(); + let mut last_stocked_value = + stocked_value.pop().unwrap_or_default(); + last_stocked_value.push(format!("{last_key}: {sp}")); + stocked_value.push(last_stocked_value); } else { tmp_stock.push(sp.to_owned()); } From fba4422872646eaffff6254bb0085cbec18c8471 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 29 Jul 2023 13:49:59 +0900 Subject: [PATCH 04/31] docs(CHANGELOG): added #1145 --- CHANGELOG-Japanese.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 8af539670..b4c9ac468 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -29,6 +29,7 @@ - `metrics`と`logon-summary`コマンドのレコード数の表示が`csv-timeline`のコマンドでのレコード数の表示と異なっている状態を修正した。 (#1105) (@hitenkoku) - パスの代わりにルールIDでルール数を数えるように変更した。 (#1113) (@hitenkoku) +- JSON出力で`CommandLine`フィールド内で誤ったフィールドの分割が行われてしまう問題を修正した。 (#1145) (@hitenkoku) **その他:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 244902207..58be1f050 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - The total number of records being displayed in the `metrics` and `logon-summary` commands differed from the `csv-timeline` command. (#1105) (@hitenkoku) - Changed rule count by rule ID instead of path. (#1113) (@hitenkoku) +- Fixed a problem with incorrect field splitting in the `CommandLine` field in JSON output. (#1145) (@hitenkoku) **Other:** From 9035e75133f8e91f67f10bfdaffa3a7c2d9eccc0 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 30 Jul 2023 22:42:46 +0900 Subject: [PATCH 05/31] feat(afterfact): fixed jq error to output file #1145 --- src/afterfact.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index c5e845b17..45eeed5e2 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1478,7 +1478,8 @@ pub fn output_json_str( stocked_value.push(tmp_stock); tmp_stock = vec![]; } - } else if sp.ends_with(';') && sp.len() < 5 { + } else if sp.ends_with(';') && sp.len() < 5 && key_index_stock.len() > 1 + { let last_key = key_index_stock.pop().unwrap_or_default(); let mut last_stocked_value = stocked_value.pop().unwrap_or_default(); @@ -1541,7 +1542,8 @@ pub fn output_json_str( .iter() .any(|remain_value| remain_value.is_empty()); if (value_idx < stocked_value.len() - 1 - && stocked_value[value_idx + 1].is_empty()) + && stocked_value[value_idx + 1].is_empty() + && key_idx != key_index_stock.len() - 1) || is_remain_split_stock { // 次の要素を確認して、存在しないもしくは、キーが入っているとなった場合現在ストックしている内容が出力していいことが確定するので出力処理を行う From a3bd81297bc93621acbd91b166ac2542fa333c97 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 1 Aug 2023 22:13:13 +0900 Subject: [PATCH 06/31] fix(afterfact): fixed ScriptBlockText gets put inside ScriptBlockId field value #1145 --- src/afterfact.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 45eeed5e2..e44d5d8c1 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1478,7 +1478,12 @@ pub fn output_json_str( stocked_value.push(tmp_stock); tmp_stock = vec![]; } - } else if sp.ends_with(';') && sp.len() < 5 && key_index_stock.len() > 1 + } else if char::from_str(&sp.chars().next().unwrap_or('a').to_string()) + .unwrap_or_default() + .is_lowercase() + && sp.ends_with(';') + && sp.len() < 5 + && key_index_stock.len() > 1 { let last_key = key_index_stock.pop().unwrap_or_default(); let mut last_stocked_value = From 3b4146fa4798987c8513b4064e191c30806edb43 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 2 Aug 2023 10:15:23 +0900 Subject: [PATCH 07/31] fix(afterfact): fixed wrong separate in ScriptBlockText field #1145 --- src/afterfact.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index e44d5d8c1..c1c52e701 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1478,9 +1478,14 @@ pub fn output_json_str( stocked_value.push(tmp_stock); tmp_stock = vec![]; } - } else if char::from_str(&sp.chars().next().unwrap_or('a').to_string()) - .unwrap_or_default() - .is_lowercase() + } else if (char::from_str( + &sp.chars().next().unwrap_or('a').to_string(), + ) + .unwrap_or_default() + .is_lowercase() + || char::from_str(&sp.chars().next().unwrap_or('a').to_string()) + .unwrap_or_default() + .is_numeric()) && sp.ends_with(';') && sp.len() < 5 && key_index_stock.len() > 1 From e1558c16cf4f50511d05284b51eb0f04725fc1cf Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 2 Aug 2023 19:39:52 +0900 Subject: [PATCH 08/31] fix(afterfact): fixed wrong field separate to cmd option in json-timeline #1145 --- src/afterfact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index c1c52e701..a7233a764 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1463,7 +1463,7 @@ pub fn output_json_str( let mut space_split_contents = detail_contents.split(' '); while let Some(sp) = space_split_contents.next() { if !sp.contains('\\') - && !sp.starts_with('-') + && !sp.starts_with(['-', '/']) && sp.ends_with(':') && sp.len() > 2 { From b02cfac346271a7b451a64006f5e1e22da7de302 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 2 Aug 2023 22:03:32 +0900 Subject: [PATCH 09/31] fix(afterfact): fixed wrong field separate #1145 --- src/afterfact.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index a7233a764..30703faab 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1462,10 +1462,14 @@ pub fn output_json_str( let mut tmp_stock = vec![]; let mut space_split_contents = detail_contents.split(' '); while let Some(sp) = space_split_contents.next() { + let first_character = + char::from_str(&sp.chars().next().unwrap_or('-').to_string()) + .unwrap_or_default(); if !sp.contains('\\') + && first_character.is_uppercase() && !sp.starts_with(['-', '/']) && sp.ends_with(':') - && sp.len() > 2 + && sp.len() > 6 { key_index_stock.push(sp.replace(':', "")); if sp == "Payload:" { @@ -1478,14 +1482,8 @@ pub fn output_json_str( stocked_value.push(tmp_stock); tmp_stock = vec![]; } - } else if (char::from_str( - &sp.chars().next().unwrap_or('a').to_string(), - ) - .unwrap_or_default() - .is_lowercase() - || char::from_str(&sp.chars().next().unwrap_or('a').to_string()) - .unwrap_or_default() - .is_numeric()) + } else if (first_character.is_lowercase() + || first_character.is_numeric()) && sp.ends_with(';') && sp.len() < 5 && key_index_stock.len() > 1 From 593af2d87973f4127f873d91223f3d3b78250b90 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:14:31 +0900 Subject: [PATCH 10/31] fix(afterfact): reverted json field name extract length condition and added exclude key condition #1145 --- src/afterfact.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 30703faab..9a0e5ccb4 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1465,11 +1465,11 @@ pub fn output_json_str( let first_character = char::from_str(&sp.chars().next().unwrap_or('-').to_string()) .unwrap_or_default(); - if !sp.contains('\\') + if !sp.contains(['\\', '"']) && first_character.is_uppercase() && !sp.starts_with(['-', '/']) && sp.ends_with(':') - && sp.len() > 6 + && sp.len() > 2 { key_index_stock.push(sp.replace(':', "")); if sp == "Payload:" { From 73579cd5caa897bd5c183ff671e369d89c81c696 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:07:00 +0900 Subject: [PATCH 11/31] fix(afterfact): modified extract json key condition #1145 --- src/afterfact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 9a0e5ccb4..c41bd2be7 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1465,7 +1465,7 @@ pub fn output_json_str( let first_character = char::from_str(&sp.chars().next().unwrap_or('-').to_string()) .unwrap_or_default(); - if !sp.contains(['\\', '"']) + if !sp.contains(['\\', '"', '\r', '\n']) && first_character.is_uppercase() && !sp.starts_with(['-', '/']) && sp.ends_with(':') From 8f7d9131676078ccad731f170a4bd0c888e89268 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Thu, 3 Aug 2023 11:48:25 +0900 Subject: [PATCH 12/31] fix(afterfact): fixed key extract condition to exclude string included newline character from key #1145 --- src/afterfact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index c41bd2be7..bd6a97846 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1465,7 +1465,7 @@ pub fn output_json_str( let first_character = char::from_str(&sp.chars().next().unwrap_or('-').to_string()) .unwrap_or_default(); - if !sp.contains(['\\', '"', '\r', '\n']) + if !sp.contains(['\\', '"', '🛂']) && first_character.is_uppercase() && !sp.starts_with(['-', '/']) && sp.ends_with(':') From b6f0de629d3155f42734ae3c2274ee59b71ddb34 Mon Sep 17 00:00:00 2001 From: DustInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 16 Aug 2023 13:39:33 +0900 Subject: [PATCH 13/31] fix(afterfact): fixed cmdline moved over #1145 --- src/afterfact.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/afterfact.rs b/src/afterfact.rs index 4dfe2b468..b4faa311f 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1527,6 +1527,7 @@ pub fn output_json_str( && sp.ends_with(';') && sp.len() < 5 && key_index_stock.len() > 1 + && key_index_stock.last().unwrap_or(&String::default()) != "Cmdline" { let last_key = key_index_stock.pop().unwrap_or_default(); let mut last_stocked_value = From 6f3690dda489d94582211db3de3731161326a12c Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:12:48 +0900 Subject: [PATCH 14/31] fix(afterfact): fixed json parse error #1145 --- src/afterfact.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/afterfact.rs b/src/afterfact.rs index b4faa311f..0e767f4e5 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1510,6 +1510,7 @@ pub fn output_json_str( && !sp.starts_with(['-', '/']) && sp.ends_with(':') && sp.len() > 2 + && sp != "Number:" { key_index_stock.push(sp.replace(':', "")); if sp == "Payload:" { From cb60c1d9e3ef06d4f756dbc0f7121b0f4b914e24 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:37:21 +0900 Subject: [PATCH 15/31] feat: refactoring JSON output processing #1145 --- src/afterfact.rs | 183 ++++++++++--------------------- src/detections/detection.rs | 14 ++- src/detections/field_data_map.rs | 2 +- src/detections/message.rs | 165 ++++++++++++++++++---------- src/detections/utils.rs | 8 +- src/timeline/search.rs | 3 +- 6 files changed, 177 insertions(+), 198 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index ae1250012..d810b53eb 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -328,6 +328,7 @@ fn emit_csv( // remove duplicate dataのための前レコード分の情報を保持する変数 let mut prev_message: HashMap = HashMap::new(); + let mut prev_details_convert_map: HashMap> = HashMap::new(); for (message_idx, time) in MESSAGEKEYS .lock() .unwrap() @@ -410,8 +411,10 @@ fn emit_csv( jsonl_output_flag, GEOIP_DB_PARSER.read().unwrap().is_some(), remove_duplicate_data_flag, + &[&detect_info.details_convert_map, &prev_details_convert_map], ); prev_message = result.1; + prev_details_convert_map = detect_info.details_convert_map.clone(); wtr.write_field(format!("{{ {} }}", &result.0))?; } else if json_output_flag { // JSON output @@ -422,8 +425,10 @@ fn emit_csv( jsonl_output_flag, GEOIP_DB_PARSER.read().unwrap().is_some(), remove_duplicate_data_flag, + &[&detect_info.details_convert_map, &prev_details_convert_map], ); prev_message = result.1; + prev_details_convert_map = detect_info.details_convert_map.clone(); wtr.write_field(&result.0)?; wtr.write_field("}")?; } else { @@ -1426,6 +1431,7 @@ pub fn output_json_str( jsonl_output_flag: bool, is_included_geo_ip: bool, remove_duplicate_flag: bool, + details_infos: &[&HashMap>], ) -> (String, HashMap) { let mut target: Vec = vec![]; let mut target_ext_field = Vec::new(); @@ -1435,15 +1441,32 @@ pub fn output_json_str( for (field_name, profile) in ext_field.iter() { match profile { Profile::Details(_) | Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) => { - if prev_message - .get(field_name) - .unwrap_or(&Profile::Literal("-".into())) - .to_value() - == profile.to_value() - { + let details_key = match profile { + Profile::Details(_) => "Details", + Profile::AllFieldInfo(_) => "AllFieldInfo", + Profile::ExtraFieldInfo(_) => "ExtraFieldInfo", + _ => "", + }; + + let empty = vec![]; + let now = details_infos[0] + .get(format!("#{details_key}").as_str()) + .unwrap_or(&empty); + let prev = details_infos[1] + .get(format!("#{details_key}").as_str()) + .unwrap_or(&empty); + let dup_flag = (!profile.to_value().is_empty() + && prev_message + .get(field_name) + .unwrap_or(&Profile::Literal("".into())) + .to_value() + == profile.to_value()) + || (!&now.is_empty() && !&prev.is_empty() && now == prev); + if dup_flag { // 合致する場合は前回レコード分のメッセージを更新する合致している場合は出力用のフィールドマップの内容を変更する。 // 合致しているので前回分のメッセージは更新しない - target_ext_field.push((field_name.clone(), profile.convert(&"DUP".into()))); + //DUPという通常の文字列を出すためにProfile::Literalを使用する + target_ext_field.push((field_name.clone(), Profile::Literal("DUP".into()))); } else { // 合致しない場合は前回レコード分のメッセージを更新する next_prev_message.insert(field_name.clone(), profile.clone()); @@ -1466,6 +1489,7 @@ pub fn output_json_str( "TgtCountry", "TgtCity", ]; + let valid_key_add_to_details: Vec<&str> = key_add_to_details .iter() .filter(|target_key| { @@ -1477,7 +1501,13 @@ pub fn output_json_str( for (key, profile) in target_ext_field.iter() { let val = profile.to_value(); let vec_data = _get_json_vec(profile, &val.to_string()); - if !key_add_to_details.contains(&key.as_str()) && vec_data.is_empty() { + if (!key_add_to_details.contains(&key.as_str()) + && !matches!( + profile, + Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) + )) + && vec_data.is_empty() + { let tmp_val: Vec<&str> = val.split(": ").collect(); let output_val = _convert_valid_json_str(&tmp_val, matches!(profile, Profile::AllFieldInfo(_))); @@ -1509,133 +1539,33 @@ pub fn output_json_str( Profile::Details(_) | Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) => { let mut output_stock: Vec = vec![]; output_stock.push(format!(" \"{key}\": {{")); - let mut stocked_value: Vec> = vec![]; - let mut key_index_stock = vec![]; - for detail_contents in vec_data.iter() { - // 分解してキーとなりえる箇所を抽出する - let mut tmp_stock = vec![]; - let mut space_split_contents = detail_contents.split(' '); - while let Some(sp) = space_split_contents.next() { - let first_character = - char::from_str(&sp.chars().next().unwrap_or('-').to_string()) - .unwrap_or_default(); - if !sp.contains(['\\', '"', '🛂']) - && first_character.is_uppercase() - && !sp.starts_with(['-', '/']) - && sp.ends_with(':') - && sp.len() > 2 - && sp != "Number:" - { - key_index_stock.push(sp.replace(':', "")); - if sp == "Payload:" { - stocked_value.push(vec![]); - stocked_value.push( - space_split_contents.map(|s| s.to_string()).collect(), - ); - break; - } else { - stocked_value.push(tmp_stock); - tmp_stock = vec![]; - } - } else if (first_character.is_lowercase() - || first_character.is_numeric()) - && sp.ends_with(';') - && sp.len() < 5 - && key_index_stock.len() > 1 - && key_index_stock.last().unwrap_or(&String::default()) != "Cmdline" - { - let last_key = key_index_stock.pop().unwrap_or_default(); - let mut last_stocked_value = - stocked_value.pop().unwrap_or_default(); - last_stocked_value.push(format!("{last_key}: {sp}")); - stocked_value.push(last_stocked_value); - } else { - tmp_stock.push(sp.to_owned()); - } - } - if !tmp_stock.is_empty() { - stocked_value.push(tmp_stock); - } - } - if stocked_value - .iter() - .counts_by(|x| x.len()) - .get(&0) - .unwrap_or(&0) - != &key_index_stock.len() - { - if let Some((target_idx, _)) = key_index_stock - .iter() - .enumerate() - .rfind(|(_, y)| "CmdLine" == *y) - { - let cmd_line_vec_idx_len = - stocked_value[2 * (target_idx + 1) - 1].len(); - stocked_value[2 * (target_idx + 1) - 1][cmd_line_vec_idx_len - 1] - .push_str(&format!(" {}:", key_index_stock[target_idx + 1])); - key_index_stock.remove(target_idx + 1); - } - } - let mut key_idx = 0; - let mut output_value_stock = String::default(); - for (value_idx, value) in stocked_value.iter().enumerate() { - if key_idx >= key_index_stock.len() { - break; - } - let mut tmp = if value_idx == 0 && !value.is_empty() { - key.as_str() - } else { - key_index_stock[key_idx].as_str() - }; - if !output_value_stock.is_empty() { - let separate_chr = - if key_index_stock[key_idx].starts_with("ScriptBlock") { - " | " - } else { - ": " - }; - output_value_stock.push_str(separate_chr); - } - output_value_stock.push_str(&value.join(" ")); - //1つまえのキーの段階で以降にvalueの配列で区切りとなる空の配列が存在しているかを確認する - let is_remain_split_stock = key_index_stock.len() > 1 - && key_idx == key_index_stock.len() - 2 - && value_idx < stocked_value.len() - 1 - && !output_value_stock.is_empty() - && !stocked_value[value_idx + 1..] - .iter() - .any(|remain_value| remain_value.is_empty()); - if (value_idx < stocked_value.len() - 1 - && stocked_value[value_idx + 1].is_empty() - && key_idx != key_index_stock.len() - 1) - || is_remain_split_stock - { - // 次の要素を確認して、存在しないもしくは、キーが入っているとなった場合現在ストックしている内容が出力していいことが確定するので出力処理を行う - let output_tmp = format!("{tmp}: {output_value_stock}"); - let output: Vec<&str> = output_tmp.split(": ").collect(); - let key = _convert_valid_json_str(&[output[0]], false); - let fmted_val = _convert_valid_json_str(&output, false); + let details_key = match profile { + Profile::Details(_) => "Details", + Profile::AllFieldInfo(_) => "AllFieldInfo", + Profile::ExtraFieldInfo(_) => "ExtraFieldInfo", + _ => "", + }; + // 個々の段階でDetails, AllFieldInfo, ExtraFieldInfoの要素はdetails_infosに格納されているのでunwrapする + let details_stocks = details_infos[0] + .get(&CompactString::from(format!("#{details_key}"))) + .unwrap(); + for (idx, contents) in details_stocks.iter().enumerate() { + let (key, value) = contents.split_once(": ").unwrap_or_default(); + let output_key = _convert_valid_json_str(&[key], false); + let fmted_val = _convert_valid_json_str(&[value], false); + + if idx != details_stocks.len() - 1 { output_stock.push(format!( "{},", _create_json_output_format( - &key, + &output_key, &fmted_val, key.starts_with('\"'), fmted_val.starts_with('\"'), 8 ) )); - output_value_stock.clear(); - tmp = ""; - key_idx += 1; - } - if value_idx == stocked_value.len() - 1 - && !(tmp.is_empty() && stocked_value.is_empty()) - { - let output_tmp = format!("{tmp}: {output_value_stock}"); - let output: Vec<&str> = output_tmp.split(": ").collect(); - let key = _convert_valid_json_str(&[output[0]], false); - let fmted_val = _convert_valid_json_str(&output, false); + } else { let last_contents_end = if is_included_geo_ip && !valid_key_add_to_details.is_empty() { "," @@ -1652,7 +1582,6 @@ pub fn output_json_str( 8, ) )); - key_idx += 1; } } if is_included_geo_ip { diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 5e98aba93..8b576792a 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -655,6 +655,7 @@ impl Detection { _ => {} } } + //ルール側にdetailsの項目があればそれをそのまま出力し、そうでない場合はproviderとeventidの組で設定したdetailsの項目を出力する let details_fmt_str = match rule.yaml["details"].as_str() { Some(s) => s.to_string(), None => match stored_static @@ -662,10 +663,11 @@ impl Detection { .get(&CompactString::from(format!("{provider}_{eid}"))) { Some(str) => str.to_string(), - None => create_recordinfos(&record_info.record, &FieldDataMapKey::default(), &None), + None => create_recordinfos(&record_info.record, &FieldDataMapKey::default(), &None) + .join(" ¦ "), }, }; - let field_data_map_key = if stored_static.field_data_map.is_none() { + let field_data_map_key: FieldDataMapKey = if stored_static.field_data_map.is_none() { FieldDataMapKey::default() } else { FieldDataMapKey { @@ -693,13 +695,14 @@ impl Detection { detail: CompactString::default(), ext_field: stored_static.profiles.as_ref().unwrap().to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }; message::insert( &record_info.record, CompactString::new(details_fmt_str), detect_info, time, - &mut profile_converter, + &profile_converter, (false, is_json_timeline, included_all_field_info_flag), ( eventkey_alias, @@ -911,6 +914,7 @@ impl Detection { detail: output, ext_field: stored_static.profiles.as_ref().unwrap().to_owned(), is_condition: true, + details_convert_map: HashMap::default(), }; let binding = STORED_EKEY_ALIAS.read().unwrap(); let eventkey_alias = binding.as_ref().unwrap(); @@ -921,7 +925,7 @@ impl Detection { CompactString::new(rule.yaml["details"].as_str().unwrap_or("-")), detect_info, agg_result.start_timedate, - &mut profile_converter, + &profile_converter, (true, is_json_timeline, false), (eventkey_alias, &field_data_map_key, &None), ) @@ -1144,7 +1148,7 @@ impl Detection { is_csv_output: bool, ) -> CompactString { for alias in target_alias { - let search_data = message::parse_message( + let (search_data, _) = message::parse_message( record, CompactString::from(alias), eventkey_alias, diff --git a/src/detections/field_data_map.rs b/src/detections/field_data_map.rs index 1b294b001..21c4c4fe0 100644 --- a/src/detections/field_data_map.rs +++ b/src/detections/field_data_map.rs @@ -330,7 +330,7 @@ mod tests { Ok(record) => { let ret = utils::create_recordinfos(&record, &key, &Some(map)); let expected = "ElevatedToken: NO ¦ ImpersonationLevel: A ¦ NewProcessId: 6528 ¦ ProcessId: 1100".to_string(); - assert_eq!(ret, expected); + assert_eq!(ret.join(" ¦ "), expected); } Err(_) => { panic!("Failed to parse json record."); diff --git a/src/detections/message.rs b/src/detections/message.rs index 0cb342ae9..3946b9c9c 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -16,7 +16,7 @@ use lazy_static::lazy_static; use nested::Nested; use regex::Regex; use serde_json::Value; -use std::borrow::Borrow; + use std::env; use std::fs::{create_dir, File}; use std::io::{self, BufWriter, Write}; @@ -26,7 +26,7 @@ use termcolor::{BufferWriter, ColorChoice}; use super::configs::EventKeyAliasConfig; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct DetectInfo { pub rulepath: CompactString, pub ruleid: CompactString, @@ -37,6 +37,7 @@ pub struct DetectInfo { pub detail: CompactString, pub ext_field: Vec<(CompactString, Profile)>, pub is_condition: bool, + pub details_convert_map: HashMap>, } pub struct AlertMessage {} @@ -115,7 +116,7 @@ pub fn insert( output: CompactString, mut detect_info: DetectInfo, time: DateTime, - profile_converter: &mut HashMap<&str, Profile>, + profile_converter: &HashMap<&str, Profile>, (is_agg, is_json_timeline, included_all_field_info): (bool, bool, bool), (eventkey_alias, field_data_map_key, field_data_map): ( &EventKeyAliasConfig, @@ -123,9 +124,11 @@ pub fn insert( &Option, ), ) { + let mut record_details_info_map = HashMap::new(); if !is_agg { let mut prev = 'a'; - let mut removed_sp_parsed_detail = parse_message( + //ここの段階でdetailsの内容でaliasを置き換えた内容と各種、key,valueの組み合わせのmapを取得する + let (mut removed_sp_parsed_detail, alias_hash_map) = parse_message( event_record, output, eventkey_alias, @@ -133,6 +136,8 @@ pub fn insert( field_data_map_key, field_data_map, ); + record_details_info_map.insert("#Details".into(), alias_hash_map); + // 特殊文字の除外のためのretain処理 removed_sp_parsed_detail.retain(|ch| { let retain_flag = prev == ' ' && ch == ' ' && ch.is_control(); if !retain_flag { @@ -140,6 +145,8 @@ pub fn insert( } !retain_flag }); + + // Details内にある改行文字は除外しないために絵文字を含めた特殊な文字に変換することで対応する let parsed_detail = removed_sp_parsed_detail .replace('\n', "🛂n") .replace('\r', "🛂r") @@ -154,79 +161,106 @@ pub fn insert( for (key, profile) in detect_info.ext_field.iter() { match profile { Details(_) => { + // Detailsの要素がすでにreplaced_profilesに存在する場合は次の処理に進み let existed_flag = replaced_profiles .iter() .any(|(_, y)| matches!(y, Details(_))); if existed_flag { continue; } - if detect_info.borrow().detail.is_empty() { + if detect_info.detail.is_empty() { + //Detailsの中身が何も入っていない場合はそのままの値を入れる replaced_profiles.push((key.to_owned(), profile.to_owned())); } else { replaced_profiles .push((key.to_owned(), Details(detect_info.detail.clone().into()))); + // メモリの節約のためにDetailsの中身を空にする detect_info.detail = CompactString::default(); } } AllFieldInfo(_) => { - let existed_flag = replaced_profiles - .iter() - .any(|(_, y)| matches!(y, AllFieldInfo(_))); - if existed_flag { - continue; - } if is_agg { replaced_profiles.push((key.to_owned(), AllFieldInfo("-".into()))); } else { - let rec = + let recinfos = utils::create_recordinfos(event_record, field_data_map_key, field_data_map); - let rec = if rec.is_empty() { "-".to_string() } else { rec }; - replaced_profiles.push((key.to_owned(), AllFieldInfo(rec.into()))); + let rec = if recinfos.is_empty() { + "-".to_string() + } else if !is_json_timeline { + recinfos.join(" ¦ ") + } else { + String::default() + }; + if rec.is_empty() { + record_details_info_map.insert("#AllFieldInfo".into(), recinfos); + replaced_profiles.push((key.to_owned(), AllFieldInfo("".into()))); + } else { + replaced_profiles.push((key.to_owned(), AllFieldInfo(rec.into()))); + } } } Literal(_) => replaced_profiles.push((key.to_owned(), profile.to_owned())), ExtraFieldInfo(_) => { - let mut profile_all_field_info_prof = None; - let mut profile_details_prof = None; - replaced_profiles.iter().for_each(|(_, y)| match y { - AllFieldInfo(_) => profile_all_field_info_prof = Some(y.to_value()), - Details(_) => profile_details_prof = Some(y.to_value()), - _ => {} - }); - let profile_details = - profile_details_prof.unwrap_or(detect_info.detail.clone().into()); + let empty = vec![]; + let record_details_info_ref = record_details_info_map.clone(); + let profile_all_field_info_prof = record_details_info_ref.get("#AllFieldInfo"); + let details_splits: HashSet<&str> = HashSet::from_iter( + record_details_info_ref + .get("#Details") + .unwrap_or(&empty) + .iter() + .map(|x| x.split_once(": ").unwrap_or_default().1), + ); let profile_all_field_info = if let Some(all_field_info_val) = profile_all_field_info_prof { - all_field_info_val + all_field_info_val.to_owned() } else if is_agg { if included_all_field_info { // AllFieldInfoがまだ読み込まれていない場合は、AllFieldInfoを追加する replaced_profiles.push((key.to_owned(), AllFieldInfo("-".into()))); } - "-".to_string() + vec![] } else { - let rec = + let recinfos = utils::create_recordinfos(event_record, field_data_map_key, field_data_map); - let rec = if rec.is_empty() { "-".to_string() } else { rec }; + let rec = if recinfos.is_empty() { + "-".to_string() + } else if !is_json_timeline { + recinfos.join(" ¦ ") + } else { + String::default() + }; + if included_all_field_info { - replaced_profiles.push((key.to_owned(), AllFieldInfo(rec.clone().into()))); + if rec.is_empty() { + record_details_info_map + .insert("#AllFieldInfo".into(), recinfos.clone()); + replaced_profiles.push((key.to_owned(), AllFieldInfo("".into()))); + } else { + replaced_profiles + .push((key.to_owned(), AllFieldInfo(rec.clone().into()))); + } } - rec + recinfos }; - let details_splits: HashSet<&str> = HashSet::from_iter( - profile_details - .split(" ¦ ") - .map(|x| x.split_once(": ").unwrap_or_default().1), - ); - let extra_field_val = profile_all_field_info - .split(" ¦ ") + let mut extra_field_val = profile_all_field_info + .iter() .filter(|x| { let value = x.split_once(": ").unwrap_or_default().1; !details_splits.contains(value) }) - .join(" ¦ "); - replaced_profiles.push((key.to_owned(), ExtraFieldInfo(extra_field_val.into()))); + .map(|y| y.to_owned()); + if is_json_timeline { + record_details_info_map + .insert("#ExtraFieldInfo".into(), extra_field_val.collect()); + replaced_profiles.push((key.to_owned(), ExtraFieldInfo("".into()))); + } else { + replaced_profiles.push(( + key.to_owned(), + ExtraFieldInfo(extra_field_val.join(" ¦ ").into()), + )); + } } SrcASN(_) | SrcCountry(_) | SrcCity(_) | TgtASN(_) | TgtCountry(_) | TgtCity(_) => { replaced_profiles.push(( @@ -236,26 +270,25 @@ pub fn insert( } _ => { if let Some(p) = profile_converter.get(key.as_str()) { - replaced_profiles.push(( - key.to_owned(), - profile.convert(&parse_message( - event_record, - CompactString::new(p.to_value()), - eventkey_alias, - is_json_timeline, - field_data_map_key, - field_data_map, - )), - )) + let (parsed_message, _) = &parse_message( + event_record, + CompactString::new(p.to_value()), + eventkey_alias, + is_json_timeline, + field_data_map_key, + field_data_map, + ); + replaced_profiles.push((key.to_owned(), profile.convert(parsed_message))) } } } } detect_info.ext_field = replaced_profiles; + detect_info.details_convert_map = record_details_info_map; insert_message(detect_info, time) } -/// メッセージ内の%で囲まれた箇所をエイリアスとしてをレコード情報を参照して置き換える関数 +/// メッセージ内の%で囲まれた箇所をエイリアスとしてレコード情報を参照して置き換える関数 pub fn parse_message( event_record: &Value, output: CompactString, @@ -263,9 +296,13 @@ pub fn parse_message( json_timeline_flag: bool, field_data_map_key: &FieldDataMapKey, field_data_map: &Option, -) -> CompactString { - let mut return_message = output; - let mut hash_map: HashMap = HashMap::new(); +) -> (CompactString, Vec) { + let mut return_message = output.clone(); + let mut hash_map: HashMap> = HashMap::new(); + let detail_key: Vec<&str> = output + .split(" ¦ ") + .map(|x| x.split_once(": ").unwrap_or_default().0) + .collect(); for caps in ALIASREGEX.captures_iter(&return_message) { let full_target_str = &caps[0]; let target_str = full_target_str @@ -314,23 +351,31 @@ pub fn parse_message( converted_str.unwrap_or(hash_value) }; if json_timeline_flag { - hash_map.insert(CompactString::from(full_target_str), field_data); + hash_map.insert(CompactString::from(full_target_str), [field_data].to_vec()); } else { hash_map.insert( CompactString::from(full_target_str), - field_data.split_ascii_whitespace().join(" ").into(), + [field_data.split_ascii_whitespace().join(" ").into()].to_vec(), ); } } } else { - hash_map.insert(CompactString::from(full_target_str), "n/a".into()); + hash_map.insert( + CompactString::from(full_target_str), + ["n/a".into()].to_vec(), + ); } } - - for (k, v) in hash_map { - return_message = CompactString::new(return_message.replace(k.as_str(), v.as_str())); + let mut details_key_and_value: Vec = vec![]; + for (i, (k, v)) in hash_map.iter().enumerate() { + // JSON出力の場合は各種のaliasを置き換える処理はafterfactの出力用の関数で行うため、ここでは行わない + if !json_timeline_flag { + return_message = CompactString::new(return_message.replace(k.as_str(), v[0].as_str())); + } else { + details_key_and_value.push(format!("{}: {}", detail_key[i], v[0]).into()); + } } - return_message + (return_message, details_key_and_value) } /// メッセージを返す diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 1e548bd66..664f224b4 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -375,7 +375,7 @@ pub fn create_recordinfos( record: &Value, field_data_map_key: &FieldDataMapKey, field_data_map: &Option, -) -> String { +) -> Vec { let mut output = HashSet::new(); _collect_recordinfo(&mut vec![], "", record, &mut output); @@ -398,13 +398,13 @@ pub fn create_recordinfos( convert_field_data(map, field_data_map_key, &key.to_lowercase(), value) { let val = converted_str.strip_suffix(',').unwrap_or(&converted_str); - return format!("{key}: {val}"); + return format!("{key}: {val}").into(); } } let val = value.strip_suffix(',').unwrap_or(value); - format!("{key}: {val}") + format!("{key}: {val}").into() }) - .join(" ¦ ") + .collect() } /** diff --git a/src/timeline/search.rs b/src/timeline/search.rs index ecf664a6c..893293c41 100644 --- a/src/timeline/search.rs +++ b/src/timeline/search.rs @@ -310,7 +310,7 @@ fn extract_search_event_info( let datainfo = utils::create_recordinfos(&record.record, &FieldDataMapKey::default(), &None); let allfieldinfo = if !datainfo.is_empty() { - datainfo.into() + datainfo.join(" ¦ ").into() } else { CompactString::new("-") }; @@ -465,6 +465,7 @@ pub fn search_result_dsp_msg( jsonl_output, false, false, + &[&HashMap::default(), &HashMap::default()], ); file_wtr From 45bcde5220a4284461afea17461520f7817873a1 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:38:35 +0900 Subject: [PATCH 16/31] test: fixed test due to code refactoring #1145 --- src/afterfact.rs | 44 ++++++++++++++++++++++++++------------- src/detections/message.rs | 36 +++++++++++++++++++------------- src/detections/utils.rs | 4 ++-- 3 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index d810b53eb..1c86c7fc4 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -2010,9 +2010,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, false), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2032,9 +2033,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, false), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2333,9 +2335,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2355,9 +2358,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2646,9 +2650,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, false), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2668,9 +2673,10 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, + &profile_converter, (false, false, false), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); @@ -2954,6 +2960,8 @@ mod tests { .to_str() .unwrap(), ); + let details_convert_map: HashMap> = + HashMap::from_iter([("#AllFieldInfo".into(), vec![test_recinfo.into()])]); message::insert( &event, CompactString::new(output), @@ -2967,10 +2975,11 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map, }, expect_time, - &mut profile_converter, - (false, false, false), + &profile_converter, + (false, true, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); *profile_converter.get_mut("Computer").unwrap() = @@ -2989,10 +2998,11 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map: HashMap::default(), }, expect_time, - &mut profile_converter, - (false, false, false), + &profile_converter, + (false, true, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); let multi = message::MESSAGES.get(&expect_time).unwrap(); @@ -3498,6 +3508,8 @@ mod tests { ); let messages = &message::MESSAGES; messages.clear(); + let details_convert_map: HashMap> = + HashMap::from_iter([("#AllFieldInfo".into(), vec![test_recinfo.into()])]); message::insert( &event, CompactString::new(output), @@ -3511,10 +3523,11 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map, }, expect_time, - &mut profile_converter, - (false, false, false), + &profile_converter, + (false, true, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); *profile_converter.get_mut("Computer").unwrap() = @@ -3746,6 +3759,8 @@ mod tests { ("EvtxFile", Profile::EvtxFile(test_filepath.into())), ("Tags", Profile::MitreTags(test_attack.into())), ]); + let details_convert_map: HashMap> = + HashMap::from_iter([("#AllFieldInfo".into(), vec![test_recinfo.into()])]); let eventkey_alias = load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -3771,10 +3786,11 @@ mod tests { detail: CompactString::default(), ext_field: output_profile.to_owned(), is_condition: false, + details_convert_map, }, expect_time, - &mut profile_converter, - (false, false, false), + &profile_converter, + (false, true, true), (&eventkey_alias, &FieldDataMapKey::default(), &None), ); *profile_converter.get_mut("Computer").unwrap() = diff --git a/src/detections/message.rs b/src/detections/message.rs index 3946b9c9c..cacc624f0 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -515,10 +515,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -551,10 +552,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -593,10 +595,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -634,10 +637,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -680,10 +684,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -726,10 +731,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -772,10 +778,11 @@ mod tests { .to_str() .unwrap(), ), - true, + false, &FieldDataMapKey::default(), &None - ), + ) + .0, expected, ); } @@ -846,6 +853,7 @@ mod tests { detail: CompactString::default(), ext_field: vec![], is_condition: false, + details_convert_map: HashMap::default(), }; sample_detects.push((sample_event_time, detect_info, rng.gen_range(0..10))); } diff --git a/src/detections/utils.rs b/src/detections/utils.rs index 664f224b4..45ea19cb3 100644 --- a/src/detections/utils.rs +++ b/src/detections/utils.rs @@ -716,7 +716,7 @@ mod tests { let ret = utils::create_recordinfos(&record, &FieldDataMapKey::default(), &None); // Systemは除外される/属性(_attributesも除外される)/key順に並ぶ let expected = "AccessMask: %%1369 ¦ Process: lsass.exe ¦ User: u1".to_string(); - assert_eq!(ret, expected); + assert_eq!(ret.join(" ¦ "), expected); } Err(_) => { panic!("Failed to parse json record."); @@ -751,7 +751,7 @@ mod tests { // Systemは除外される/属性(_attributesも除外される)/key順に並ぶ let expected = "Binary: hogehoge ¦ Data: ¦ Data: Data1 ¦ Data: DataData2 ¦ Data: DataDataData3" .to_string(); - assert_eq!(ret, expected); + assert_eq!(ret.join(" ¦ "), expected); } Err(_) => { panic!("Failed to parse json record."); From 6ba9c97bfc10f01fe80fd70b41b101bbdcb2ce6a Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:39:37 +0900 Subject: [PATCH 17/31] refactor(configs): refactoring in pivotkeywordslist --- src/detections/configs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/detections/configs.rs b/src/detections/configs.rs index f9595f2ec..60cc6c033 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -3,7 +3,7 @@ use crate::detections::message::AlertMessage; use crate::detections::utils; use crate::options::geoip_search::GeoIPSearch; use crate::options::htmlreport; -use crate::options::pivot::{PivotKeyword, PIVOT_KEYWORD}; +use crate::options::pivot::PIVOT_KEYWORD; use crate::options::profile::{load_profile, Profile}; use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind}; use chrono::{DateTime, Days, Duration, Local, Months, Utc}; @@ -2033,7 +2033,7 @@ pub fn load_pivot_keywords(path: &str) { .write() .unwrap() .entry(key.to_string()) - .or_insert_with(PivotKeyword::new); + .or_default(); PIVOT_KEYWORD .write() From 364b544215aab6784cc14d73153972f86f15c226 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 16:43:17 +0900 Subject: [PATCH 18/31] refactor(afterfact): to remove unused vec processing to output value of details, allfieldinfo, extrafieldinfo #1145 --- src/afterfact.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 1c86c7fc4..5713ebf65 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1299,19 +1299,11 @@ fn _get_timestamp(output_option: &OutputOption, time: &DateTime) -> i64 { } /// json出力の際に配列として対応させるdetails,MitreTactics,MitreTags,OtherTagsに該当する場合に配列を返す関数 -fn _get_json_vec(profile: &Profile, target_data: &String) -> Vec { +fn _get_json_vec(profile: &Profile, target_data: &str) -> Vec { match profile { Profile::MitreTactics(_) | Profile::MitreTags(_) | Profile::OtherTags(_) => { target_data.split(": ").map(|x| x.to_string()).collect() } - Profile::Details(_) | Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) => { - let ret: Vec = target_data.split(" ¦ ").map(|x| x.to_string()).collect(); - if target_data == &ret[0] && !utils::contains_str(target_data, ": ") { - vec![] - } else { - ret - } - } _ => vec![], } } @@ -1575,7 +1567,7 @@ pub fn output_json_str( output_stock.push(format!( "{}{last_contents_end}", _create_json_output_format( - &key, + key, &fmted_val, key.starts_with('\"'), fmted_val.starts_with('\"'), From 29f36d007a45613d961f3d5b21799b41c62467e2 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:28:16 +0900 Subject: [PATCH 19/31] Revert "refactor(afterfact): to remove unused vec processing to output value of details, allfieldinfo, extrafieldinfo #1145" This reverts commit 364b544215aab6784cc14d73153972f86f15c226. --- src/afterfact.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 5713ebf65..1c86c7fc4 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1299,11 +1299,19 @@ fn _get_timestamp(output_option: &OutputOption, time: &DateTime) -> i64 { } /// json出力の際に配列として対応させるdetails,MitreTactics,MitreTags,OtherTagsに該当する場合に配列を返す関数 -fn _get_json_vec(profile: &Profile, target_data: &str) -> Vec { +fn _get_json_vec(profile: &Profile, target_data: &String) -> Vec { match profile { Profile::MitreTactics(_) | Profile::MitreTags(_) | Profile::OtherTags(_) => { target_data.split(": ").map(|x| x.to_string()).collect() } + Profile::Details(_) | Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) => { + let ret: Vec = target_data.split(" ¦ ").map(|x| x.to_string()).collect(); + if target_data == &ret[0] && !utils::contains_str(target_data, ": ") { + vec![] + } else { + ret + } + } _ => vec![], } } @@ -1567,7 +1575,7 @@ pub fn output_json_str( output_stock.push(format!( "{}{last_contents_end}", _create_json_output_format( - key, + &key, &fmted_val, key.starts_with('\"'), fmted_val.starts_with('\"'), From 7b0684cdf1ff10d1bc0708cbc864764bfea0eff6 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:45:21 +0900 Subject: [PATCH 20/31] fix(message): fixed error by test #1145 --- src/detections/message.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index cacc624f0..372104bce 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -174,6 +174,10 @@ pub fn insert( } else { replaced_profiles .push((key.to_owned(), Details(detect_info.detail.clone().into()))); + detect_info.details_convert_map.insert( + "#Details".into(), + detect_info.detail.split(" ¦ ").map(|x| x.into()).collect(), + ); // メモリの節約のためにDetailsの中身を空にする detect_info.detail = CompactString::default(); } @@ -371,7 +375,8 @@ pub fn parse_message( // JSON出力の場合は各種のaliasを置き換える処理はafterfactの出力用の関数で行うため、ここでは行わない if !json_timeline_flag { return_message = CompactString::new(return_message.replace(k.as_str(), v[0].as_str())); - } else { + } + if detail_key.len() > i { details_key_and_value.push(format!("{}: {}", detail_key[i], v[0]).into()); } } From 3ba54f6f2106f356b264e4b588d1260b1ff5c9d3 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:51:18 +0900 Subject: [PATCH 21/31] refactor(afterfact): fixed cargo clippy error --- src/afterfact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 1c86c7fc4..80f2188e0 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1575,7 +1575,7 @@ pub fn output_json_str( output_stock.push(format!( "{}{last_contents_end}", _create_json_output_format( - &key, + key, &fmted_val, key.starts_with('\"'), fmted_val.starts_with('\"'), From 02664897f5847269f074112c1034d4f5ec523aec Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 3 Sep 2023 11:19:23 +0900 Subject: [PATCH 22/31] fix(message): fixed processing of removing special character #1145 --- src/detections/message.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index 372104bce..38e86beb2 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -126,9 +126,8 @@ pub fn insert( ) { let mut record_details_info_map = HashMap::new(); if !is_agg { - let mut prev = 'a'; //ここの段階でdetailsの内容でaliasを置き換えた内容と各種、key,valueの組み合わせのmapを取得する - let (mut removed_sp_parsed_detail, alias_hash_map) = parse_message( + let (mut removed_sp_parsed_detail, details_in_record) = parse_message( event_record, output, eventkey_alias, @@ -136,15 +135,25 @@ pub fn insert( field_data_map_key, field_data_map, ); - record_details_info_map.insert("#Details".into(), alias_hash_map); - // 特殊文字の除外のためのretain処理 - removed_sp_parsed_detail.retain(|ch| { - let retain_flag = prev == ' ' && ch == ' ' && ch.is_control(); - if !retain_flag { - prev = ch; - } - !retain_flag + + let removed_sp_char = |mut cs: CompactString| -> CompactString { + let mut prev = 'a'; + cs.retain(|ch| { + let retain_flag = (prev == ' ' && ch == ' ') || ch.is_control(); + if !retain_flag { + prev = ch; + } + !retain_flag + }); + cs.clone() + }; + let mut sp_removed_details_in_record = vec![]; + details_in_record.iter().for_each(|v| { + sp_removed_details_in_record.push(removed_sp_char(v.clone())); }); + record_details_info_map.insert("#Details".into(), sp_removed_details_in_record); + // 特殊文字の除外のためのretain処理 + removed_sp_parsed_detail = removed_sp_char(removed_sp_parsed_detail); // Details内にある改行文字は除外しないために絵文字を含めた特殊な文字に変換することで対応する let parsed_detail = removed_sp_parsed_detail From 4d208b0da2396f41443e138f03191a60ceff8699 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 3 Sep 2023 16:19:24 +0900 Subject: [PATCH 23/31] fix(message): fixed porcessing of removing newline character #1145 --- src/detections/message.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index 38e86beb2..44419356c 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -127,7 +127,7 @@ pub fn insert( let mut record_details_info_map = HashMap::new(); if !is_agg { //ここの段階でdetailsの内容でaliasを置き換えた内容と各種、key,valueの組み合わせのmapを取得する - let (mut removed_sp_parsed_detail, details_in_record) = parse_message( + let (removed_sp_parsed_detail, details_in_record) = parse_message( event_record, output, eventkey_alias, @@ -136,16 +136,20 @@ pub fn insert( field_data_map, ); - let removed_sp_char = |mut cs: CompactString| -> CompactString { + let removed_sp_char = |cs: CompactString| -> CompactString { + let mut newline_replaced_cs = cs + .replace('\n', "🛂n") + .replace('\r', "🛂r") + .replace('\t', "🛂t"); let mut prev = 'a'; - cs.retain(|ch| { + newline_replaced_cs.retain(|ch| { let retain_flag = (prev == ' ' && ch == ' ') || ch.is_control(); if !retain_flag { prev = ch; } !retain_flag }); - cs.clone() + newline_replaced_cs.into() }; let mut sp_removed_details_in_record = vec![]; details_in_record.iter().for_each(|v| { @@ -153,17 +157,12 @@ pub fn insert( }); record_details_info_map.insert("#Details".into(), sp_removed_details_in_record); // 特殊文字の除外のためのretain処理 - removed_sp_parsed_detail = removed_sp_char(removed_sp_parsed_detail); - // Details内にある改行文字は除外しないために絵文字を含めた特殊な文字に変換することで対応する - let parsed_detail = removed_sp_parsed_detail - .replace('\n', "🛂n") - .replace('\r', "🛂r") - .replace('\t', "🛂t"); + let parsed_detail = removed_sp_char(removed_sp_parsed_detail); detect_info.detail = if parsed_detail.is_empty() { CompactString::from("-") } else { - parsed_detail.into() + parsed_detail }; } let mut replaced_profiles: Vec<(CompactString, Profile)> = vec![]; From 0b7f228ab5657325b3a660812b611cd5f6e41dfe Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:56:58 +0900 Subject: [PATCH 24/31] fix(message): fixed unmatched details key #1145 --- src/detections/message.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index 44419356c..1bb6719b6 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -311,9 +311,8 @@ pub fn parse_message( ) -> (CompactString, Vec) { let mut return_message = output.clone(); let mut hash_map: HashMap> = HashMap::new(); - let detail_key: Vec<&str> = output + let details_key: Vec<&str> = output .split(" ¦ ") - .map(|x| x.split_once(": ").unwrap_or_default().0) .collect(); for caps in ALIASREGEX.captures_iter(&return_message) { let full_target_str = &caps[0]; @@ -379,13 +378,17 @@ pub fn parse_message( } } let mut details_key_and_value: Vec = vec![]; - for (i, (k, v)) in hash_map.iter().enumerate() { + for (k, v) in hash_map.iter() { // JSON出力の場合は各種のaliasを置き換える処理はafterfactの出力用の関数で行うため、ここでは行わない if !json_timeline_flag { return_message = CompactString::new(return_message.replace(k.as_str(), v[0].as_str())); } - if detail_key.len() > i { - details_key_and_value.push(format!("{}: {}", detail_key[i], v[0]).into()); + for detail_contents in details_key.iter() { + if detail_contents.contains(k.as_str()) { + let key = detail_contents.split_once(": ").unwrap_or_default().0; + details_key_and_value.push(format!("{}: {}", key, v[0]).into()); + break; + } } } (return_message, details_key_and_value) From 274a733711022aff744ae6e9d742286f3e74e3e6 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:57:27 +0900 Subject: [PATCH 25/31] style(message): cargo fmt --- src/detections/message.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index 1bb6719b6..61435d186 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -311,9 +311,7 @@ pub fn parse_message( ) -> (CompactString, Vec) { let mut return_message = output.clone(); let mut hash_map: HashMap> = HashMap::new(); - let details_key: Vec<&str> = output - .split(" ¦ ") - .collect(); + let details_key: Vec<&str> = output.split(" ¦ ").collect(); for caps in ALIASREGEX.captures_iter(&return_message) { let full_target_str = &caps[0]; let target_str = full_target_str From b44109c512ac36a09a717ac627ff073b48e1cb87 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:10:29 +0900 Subject: [PATCH 26/31] UI(message): rearranged Details and ExtraFieldInfo keys slpabetically in json-timeline #1145 --- src/detections/message.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/detections/message.rs b/src/detections/message.rs index 61435d186..bc2e0b851 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -262,7 +262,8 @@ pub fn insert( let value = x.split_once(": ").unwrap_or_default().1; !details_splits.contains(value) }) - .map(|y| y.to_owned()); + .map(|y| y.to_owned()) + .sorted_unstable(); if is_json_timeline { record_details_info_map .insert("#ExtraFieldInfo".into(), extra_field_val.collect()); @@ -389,6 +390,7 @@ pub fn parse_message( } } } + details_key_and_value.sort_unstable(); (return_message, details_key_and_value) } From 7de9af71ca37d9ad514a9b913fed084313ba4b25 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:26:29 +0900 Subject: [PATCH 27/31] fix(afterfact): added details key is none case #1145 style: cargo fmt WIP: fix(afterfact/detection/message): fixed misprocessing of details field in JSON output #1145 --- src/afterfact.rs | 63 +++++++++++++++++++++++++++++++++---- src/detections/detection.rs | 2 +- src/detections/message.rs | 30 ++++++++++++------ src/timeline/search.rs | 1 + 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 80f2188e0..d75c8ca9c 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -411,6 +411,7 @@ fn emit_csv( jsonl_output_flag, GEOIP_DB_PARSER.read().unwrap().is_some(), remove_duplicate_data_flag, + detect_info.is_condition, &[&detect_info.details_convert_map, &prev_details_convert_map], ); prev_message = result.1; @@ -425,6 +426,7 @@ fn emit_csv( jsonl_output_flag, GEOIP_DB_PARSER.read().unwrap().is_some(), remove_duplicate_data_flag, + detect_info.is_condition, &[&detect_info.details_convert_map, &prev_details_convert_map], ); prev_message = result.1; @@ -1431,6 +1433,7 @@ pub fn output_json_str( jsonl_output_flag: bool, is_included_geo_ip: bool, remove_duplicate_flag: bool, + is_condition: bool, details_infos: &[&HashMap>], ) -> (String, HashMap) { let mut target: Vec = vec![]; @@ -1538,23 +1541,71 @@ pub fn output_json_str( } Profile::Details(_) | Profile::AllFieldInfo(_) | Profile::ExtraFieldInfo(_) => { let mut output_stock: Vec = vec![]; - output_stock.push(format!(" \"{key}\": {{")); let details_key = match profile { Profile::Details(_) => "Details", Profile::AllFieldInfo(_) => "AllFieldInfo", Profile::ExtraFieldInfo(_) => "ExtraFieldInfo", _ => "", }; - // 個々の段階でDetails, AllFieldInfo, ExtraFieldInfoの要素はdetails_infosに格納されているのでunwrapする - let details_stocks = details_infos[0] + let mut details_target_stocks = vec![]; + for details_info in details_infos { + let details_target_stock = + details_info.get(&CompactString::from(format!("#{details_key}"))); + if let Some(tmp_stock) = details_target_stock { + details_target_stocks.extend(tmp_stock); + } + } + + if details_infos[0] .get(&CompactString::from(format!("#{details_key}"))) - .unwrap(); - for (idx, contents) in details_stocks.iter().enumerate() { + .is_none() + { + continue; + } + // aggregation conditionの場合は分解せずにそのまま出力する + if is_condition && details_key == "Details" { + if details_target_stocks.is_empty() { + output_stock.push(format!( + "{}", + _create_json_output_format( + &key, + "-", + key.starts_with('\"'), + false, + 4 + ) + )); + } else { + let joined_details_target_stock = + details_target_stocks.iter().join(" "); + let output_str_details_target_stock = + joined_details_target_stock.trim(); + output_stock.push(format!( + "{}", + _create_json_output_format( + &key, + output_str_details_target_stock, + key.starts_with('\"'), + output_str_details_target_stock.starts_with('\"'), + 4 + ) + )); + } + if jsonl_output_flag { + target.push(output_stock.join("")); + } else { + target.push(output_stock.join("\n")); + } + continue; + } else { + output_stock.push(format!(" \"{key}\": {{")); + }; + for (idx, contents) in details_target_stocks.iter().enumerate() { let (key, value) = contents.split_once(": ").unwrap_or_default(); let output_key = _convert_valid_json_str(&[key], false); let fmted_val = _convert_valid_json_str(&[value], false); - if idx != details_stocks.len() - 1 { + if idx != details_target_stocks.len() - 1 { output_stock.push(format!( "{},", _create_json_output_format( diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 8b576792a..745fdc66a 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -1150,7 +1150,7 @@ impl Detection { for alias in target_alias { let (search_data, _) = message::parse_message( record, - CompactString::from(alias), + &CompactString::from(alias), eventkey_alias, is_csv_output, &FieldDataMapKey::default(), diff --git a/src/detections/message.rs b/src/detections/message.rs index bc2e0b851..ed1e28856 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -129,7 +129,7 @@ pub fn insert( //ここの段階でdetailsの内容でaliasを置き換えた内容と各種、key,valueの組み合わせのmapを取得する let (removed_sp_parsed_detail, details_in_record) = parse_message( event_record, - output, + &output, eventkey_alias, is_json_timeline, field_data_map_key, @@ -186,6 +186,16 @@ pub fn insert( "#Details".into(), detect_info.detail.split(" ¦ ").map(|x| x.into()).collect(), ); + if is_agg { + if output != "-" { + record_details_info_map.insert("#Details".into(), vec![output.clone()]); + } else if detect_info.detail != "-" { + record_details_info_map + .insert("#Details".into(), vec![detect_info.detail.clone()]); + } else { + record_details_info_map.insert("#Details".into(), vec!["-".into()]); + } + } // メモリの節約のためにDetailsの中身を空にする detect_info.detail = CompactString::default(); } @@ -285,7 +295,7 @@ pub fn insert( if let Some(p) = profile_converter.get(key.as_str()) { let (parsed_message, _) = &parse_message( event_record, - CompactString::new(p.to_value()), + &CompactString::new(p.to_value()), eventkey_alias, is_json_timeline, field_data_map_key, @@ -304,7 +314,7 @@ pub fn insert( /// メッセージ内の%で囲まれた箇所をエイリアスとしてレコード情報を参照して置き換える関数 pub fn parse_message( event_record: &Value, - output: CompactString, + output: &CompactString, eventkey_alias: &EventKeyAliasConfig, json_timeline_flag: bool, field_data_map_key: &FieldDataMapKey, @@ -520,7 +530,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("commandline:%CommandLine% computername:%ComputerName%"), + &CompactString::new("commandline:%CommandLine% computername:%ComputerName%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -557,7 +567,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("alias:%NoAlias%"), + &CompactString::new("alias:%NoAlias%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -600,7 +610,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("NoExistAlias:%NoAliasNoHit%"), + &CompactString::new("NoExistAlias:%NoAliasNoHit%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -642,7 +652,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("commandline:%CommandLine% computername:%ComputerName%"), + &CompactString::new("commandline:%CommandLine% computername:%ComputerName%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -689,7 +699,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("commandline:%CommandLine% data:%Data%"), + &CompactString::new("commandline:%CommandLine% data:%Data%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -736,7 +746,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("commandline:%CommandLine% data:%Data[2]%"), + &CompactString::new("commandline:%CommandLine% data:%Data[2]%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), @@ -783,7 +793,7 @@ mod tests { assert_eq!( parse_message( &event_record, - CompactString::new("commandline:%CommandLine% data:%Data[0]%"), + &CompactString::new("commandline:%CommandLine% data:%Data[0]%"), &load_eventkey_alias( utils::check_setting_path( &CURRENT_EXE_PATH.to_path_buf(), diff --git a/src/timeline/search.rs b/src/timeline/search.rs index 893293c41..ae3300e2e 100644 --- a/src/timeline/search.rs +++ b/src/timeline/search.rs @@ -465,6 +465,7 @@ pub fn search_result_dsp_msg( jsonl_output, false, false, + false, &[&HashMap::default(), &HashMap::default()], ); From 4a93008016ce0845e9bda5ba49f29a087c38f4f5 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:01:28 +0900 Subject: [PATCH 28/31] fix(afterfact/message): fixed Details field output misprocessing #1145 --- src/afterfact.rs | 63 +++++++++++++-------------------------- src/detections/message.rs | 38 +++++++++++++---------- 2 files changed, 43 insertions(+), 58 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index d75c8ca9c..2057ce9d9 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1547,50 +1547,27 @@ pub fn output_json_str( Profile::ExtraFieldInfo(_) => "ExtraFieldInfo", _ => "", }; - let mut details_target_stocks = vec![]; - for details_info in details_infos { - let details_target_stock = - details_info.get(&CompactString::from(format!("#{details_key}"))); - if let Some(tmp_stock) = details_target_stock { - details_target_stocks.extend(tmp_stock); - } - } - - if details_infos[0] - .get(&CompactString::from(format!("#{details_key}"))) - .is_none() - { + let details_target_stocks = + details_infos[0].get(&CompactString::from(format!("#{details_key}"))); + if details_target_stocks.is_none() { continue; } + let details_target_stock = details_target_stocks.unwrap(); // aggregation conditionの場合は分解せずにそのまま出力する - if is_condition && details_key == "Details" { - if details_target_stocks.is_empty() { - output_stock.push(format!( - "{}", - _create_json_output_format( - &key, - "-", - key.starts_with('\"'), - false, - 4 - ) - )); - } else { - let joined_details_target_stock = - details_target_stocks.iter().join(" "); - let output_str_details_target_stock = - joined_details_target_stock.trim(); - output_stock.push(format!( - "{}", - _create_json_output_format( - &key, - output_str_details_target_stock, - key.starts_with('\"'), - output_str_details_target_stock.starts_with('\"'), - 4 - ) - )); - } + if is_condition { + let details_val = + if details_target_stock.is_empty() || details_target_stock[0] == "-" { + "-".into() + } else { + details_target_stock[0].clone() + }; + output_stock.push(_create_json_output_format( + &key, + &details_val, + key.starts_with('\"'), + details_val.starts_with('\"'), + 4, + )); if jsonl_output_flag { target.push(output_stock.join("")); } else { @@ -1600,12 +1577,12 @@ pub fn output_json_str( } else { output_stock.push(format!(" \"{key}\": {{")); }; - for (idx, contents) in details_target_stocks.iter().enumerate() { + for (idx, contents) in details_target_stock.iter().enumerate() { let (key, value) = contents.split_once(": ").unwrap_or_default(); let output_key = _convert_valid_json_str(&[key], false); let fmted_val = _convert_valid_json_str(&[value], false); - if idx != details_target_stocks.len() - 1 { + if idx != details_target_stock.len() - 1 { output_stock.push(format!( "{},", _create_json_output_format( diff --git a/src/detections/message.rs b/src/detections/message.rs index ed1e28856..8728305b0 100644 --- a/src/detections/message.rs +++ b/src/detections/message.rs @@ -203,6 +203,9 @@ pub fn insert( AllFieldInfo(_) => { if is_agg { replaced_profiles.push((key.to_owned(), AllFieldInfo("-".into()))); + } else if record_details_info_map.get("#AllFieldInfo").is_some() { + // ExtraFieldInfoの要素の作成の際に、record_details_info_mapに要素を追加しているときにはAllFieldInfoの要素をすでに追加しているためスキップする + continue; } else { let recinfos = utils::create_recordinfos(event_record, field_data_map_key, field_data_map); @@ -213,7 +216,7 @@ pub fn insert( } else { String::default() }; - if rec.is_empty() { + if is_json_timeline { record_details_info_map.insert("#AllFieldInfo".into(), recinfos); replaced_profiles.push((key.to_owned(), AllFieldInfo("".into()))); } else { @@ -223,6 +226,16 @@ pub fn insert( } Literal(_) => replaced_profiles.push((key.to_owned(), profile.to_owned())), ExtraFieldInfo(_) => { + if is_agg { + if is_json_timeline { + record_details_info_map + .insert("#ExtraFieldInfo".into(), vec![CompactString::from("-")]); + replaced_profiles.push((key.to_owned(), ExtraFieldInfo("".into()))); + } else { + replaced_profiles.push((key.to_owned(), ExtraFieldInfo("-".into()))); + } + continue; + } let empty = vec![]; let record_details_info_ref = record_details_info_map.clone(); let profile_all_field_info_prof = record_details_info_ref.get("#AllFieldInfo"); @@ -237,12 +250,6 @@ pub fn insert( profile_all_field_info_prof { all_field_info_val.to_owned() - } else if is_agg { - if included_all_field_info { - // AllFieldInfoがまだ読み込まれていない場合は、AllFieldInfoを追加する - replaced_profiles.push((key.to_owned(), AllFieldInfo("-".into()))); - } - vec![] } else { let recinfos = utils::create_recordinfos(event_record, field_data_map_key, field_data_map); @@ -255,9 +262,8 @@ pub fn insert( }; if included_all_field_info { - if rec.is_empty() { - record_details_info_map - .insert("#AllFieldInfo".into(), recinfos.clone()); + record_details_info_map.insert("#AllFieldInfo".into(), recinfos.clone()); + if is_json_timeline { replaced_profiles.push((key.to_owned(), AllFieldInfo("".into()))); } else { replaced_profiles @@ -266,22 +272,24 @@ pub fn insert( } recinfos }; - let mut extra_field_val = profile_all_field_info + let extra_field_vec = profile_all_field_info .iter() .filter(|x| { let value = x.split_once(": ").unwrap_or_default().1; !details_splits.contains(value) }) .map(|y| y.to_owned()) - .sorted_unstable(); + .sorted_unstable() + .collect(); if is_json_timeline { - record_details_info_map - .insert("#ExtraFieldInfo".into(), extra_field_val.collect()); + record_details_info_map.insert("#ExtraFieldInfo".into(), extra_field_vec); replaced_profiles.push((key.to_owned(), ExtraFieldInfo("".into()))); + } else if extra_field_vec.is_empty() { + replaced_profiles.push((key.to_owned(), ExtraFieldInfo("-".into()))); } else { replaced_profiles.push(( key.to_owned(), - ExtraFieldInfo(extra_field_val.join(" ¦ ").into()), + ExtraFieldInfo(extra_field_vec.join(" ¦ ").into()), )); } } From b530aed5348e1580086a55310303610535b7dd25 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:17:07 +0900 Subject: [PATCH 29/31] style: fixed cargo clippy error --- src/afterfact.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 2057ce9d9..44074e79d 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -1562,7 +1562,7 @@ pub fn output_json_str( details_target_stock[0].clone() }; output_stock.push(_create_json_output_format( - &key, + key, &details_val, key.starts_with('\"'), details_val.starts_with('\"'), From bd8f82ce64b98334bc74803833bcd4ec0b82643b Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 16 Sep 2023 12:03:02 +0900 Subject: [PATCH 30/31] build(Cargo.lock): cargo update --- Cargo.lock | 110 +++++++++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed7ef2e17..ad5fb5147 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -158,9 +158,9 @@ checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bitflags" @@ -176,9 +176,9 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytecount" @@ -262,9 +262,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.2" +version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6" dependencies = [ "clap_builder", "clap_derive", @@ -334,7 +334,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -842,7 +842,7 @@ dependencies = [ "bytesize", "chrono", "cidr-utils", - "clap 4.4.2", + "clap 4.4.3", "comfy-table", "compact_str", "crossbeam-utils", @@ -881,7 +881,7 @@ dependencies = [ "serde_derive", "serde_json", "termcolor", - "terminal_size", + "terminal_size 0.3.0", "tokio", "ureq", "yaml-rust", @@ -1110,14 +1110,14 @@ checksum = "8244e0ff6c548152c07559ee9779dec5a5411eeee5bfd6146b38bd414a6841c6" dependencies = [ "anyhow", "chrono", - "clap 4.4.2", + "clap 4.4.3", "file-chunker", "memmap2 0.7.1", "num_cpus", "rayon", "regex", "tempfile", - "terminal_size", + "terminal_size 0.2.6", ] [[package]] @@ -1128,9 +1128,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libgit2-sys" @@ -1148,9 +1148,9 @@ dependencies = [ [[package]] name = "libmimalloc-sys" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" dependencies = [ "cc", "cty", @@ -1197,9 +1197,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -1264,9 +1264,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.38" +version = "0.1.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" dependencies = [ "libmimalloc-sys", ] @@ -1491,7 +1491,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -1619,9 +1619,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -1812,14 +1812,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] @@ -1831,15 +1831,15 @@ checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", - "rustls-webpki 0.101.4", + "rustls-webpki 0.101.5", "sct", ] [[package]] name = "rustls-webpki" -version = "0.100.2" +version = "0.100.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" +checksum = "5f6a5fc258f1c1276dfe3016516945546e2d5383911efc0fc4f1cdc5df3a4ae3" dependencies = [ "ring", "untrusted", @@ -1847,9 +1847,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.4" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -1933,14 +1933,14 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -2032,9 +2032,9 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -2148,9 +2148,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "59bf04c28bee9043ed9ea1e41afc0552288d3aba9c6efdd78903b802926f4879" dependencies = [ "proc-macro2", "quote", @@ -2166,7 +2166,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix 0.38.11", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -2189,6 +2189,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.13", + "windows-sys 0.48.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -2218,7 +2228,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -2331,7 +2341,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", ] [[package]] @@ -2351,9 +2361,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -2387,7 +2397,7 @@ dependencies = [ "log", "once_cell", "rustls", - "rustls-webpki 0.100.2", + "rustls-webpki 0.100.3", "url", "webpki-roots", ] @@ -2458,7 +2468,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", "wasm-bindgen-shared", ] @@ -2480,7 +2490,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.35", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2507,7 +2517,7 @@ version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ - "rustls-webpki 0.100.2", + "rustls-webpki 0.100.3", ] [[package]] From 39ec59a886e66ae7510f78091d41b41823318ac8 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 16 Sep 2023 22:58:53 +0900 Subject: [PATCH 31/31] build(Cargo): updated hayabusa-evtx crate --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad5fb5147..7a1e5cac3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -688,7 +688,7 @@ dependencies = [ [[package]] name = "evtx" version = "0.8.7" -source = "git+https://github.com/Yamato-Security/hayabusa-evtx.git?rev=fe38ad6#fe38ad649d6c73c1c7d7fcba1d38245f62582fb5" +source = "git+https://github.com/Yamato-Security/hayabusa-evtx.git?rev=c8391f1#c8391f173eb5d80b9def72ffd68e2a5c6867c945" dependencies = [ "anyhow", "bitflags 2.4.0", diff --git a/Cargo.toml b/Cargo.toml index 38296ef9b..c1ca840e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ include = ["src/**/*", "LICENSE.txt", "README.md", "CHANGELOG.md"] itertools = "*" dashmap = "*" clap = { version = "4.*", features = ["derive", "cargo", "color"]} -evtx = { git = "https://github.com/Yamato-Security/hayabusa-evtx.git" , features = ["fast-alloc"] , rev = "fe38ad6" } # 0.8.7 2023/08/30 update +evtx = { git = "https://github.com/Yamato-Security/hayabusa-evtx.git" , features = ["fast-alloc"] , rev = "c8391f1" } # 0.8.7 2023/08/30 update quick-xml = {version = "0.*", features = ["serialize"] } serde = { version = "1.*", features = ["derive"] } serde_json = { version = "1.0"}