From 6de8abfe09120645f725b347804b376e1f78da26 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 8 Oct 2022 10:40:01 +0900 Subject: [PATCH 1/6] add comment --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index f1213028..0163b369 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,6 +214,7 @@ impl App { return; } + // pivot 機能でファイルを出力する際に同名ファイルが既に存在していた場合はエラー文を出して終了する。 if let Some(csv_path) = &configs::CONFIG.read().unwrap().args.output { let pivot_key_unions = PIVOT_KEYWORD.read().unwrap(); pivot_key_unions.iter().for_each(|(key, _)| { From 9b83a87b9ae50a298b698bc3af3d726c330ee118 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 8 Oct 2022 14:13:21 +0900 Subject: [PATCH 2/6] added rule author output feature #724 --- src/afterfact.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/afterfact.rs b/src/afterfact.rs index 13dc42b7..eea8736e 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -3,6 +3,7 @@ use crate::detections::message::{self, AlertMessage, LEVEL_ABBR, LEVEL_FULL}; use crate::detections::utils::{self, format_time, get_writable_color, write_color_buffer}; use crate::options::htmlreport; use crate::options::profile::PROFILES; +use crate::yaml::{ParseYaml}; use bytesize::ByteSize; use chrono::{DateTime, Local, TimeZone, Utc}; use comfy_table::modifiers::UTF8_ROUND_CORNERS; @@ -14,6 +15,8 @@ use itertools::Itertools; use krapslog::{build_sparkline, build_time_markers}; use lazy_static::lazy_static; use linked_hash_map::LinkedHashMap; +use yaml_rust::YamlLoader; +use std::path::Path; use std::str::FromStr; use comfy_table::*; @@ -236,6 +239,8 @@ fn emit_csv( let mut detect_counts_by_rule_and_level: HashMap> = HashMap::new(); let mut rule_title_path_map: HashMap = HashMap::new(); + let mut rule_author_counter: HashMap = HashMap::new(); + let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]); // レベル別、日ごとの集計用変数の初期化 for level_init in levels { @@ -339,6 +344,9 @@ fn emit_csv( .or_insert(0) += 1; if !detected_rule_files.contains(&detect_info.rulepath) { detected_rule_files.insert(detect_info.rulepath.clone()); + for author in extract_author_name(detect_info.rulepath.clone()) { + *rule_author_counter.entry(author).or_insert(1) += 1; + } unique_detect_counts_by_level[level_suffix] += 1; } @@ -410,6 +418,17 @@ fn emit_csv( } }; + disp_wtr_buf.clear(); + write_color_buffer( + &disp_wtr, + get_writable_color(Some(Color::Rgb(0, 255, 0))), + "Rules brought to you by:", + true, + ) + .ok(); + + output_detected_rule_authors(rule_author_counter); + if !configs::CONFIG.read().unwrap().args.no_summary { disp_wtr_buf.clear(); write_color_buffer( @@ -1151,6 +1170,66 @@ fn output_json_str( } } +fn output_detected_rule_authors(rule_author_counter: HashMap) { + let mut sorted_authors: Vec<(&String, &i128)> = rule_author_counter + .iter() + .collect(); + + sorted_authors.sort_by(|a, b| (-a.1).cmp(&(-b.1))); + let mut output = Vec::new(); + let div = if sorted_authors.len()%4 != 0{ + sorted_authors.len()/4 + 1 + } else { + sorted_authors.len()/4 + }; + + for x in 0..div { + let mut tmp = Vec::new(); + for y in 0..4 { + tmp.push(format!("{}({})", sorted_authors[x*4 + y].0, sorted_authors[x*4 + y].1)); + } + output.push(tmp); + } + let mut tbrows = vec![]; + for c in output.iter() { + tbrows.push(Cell::new(c.join("\n"))); + } + let mut tb = Table::new(); + tb.load_preset(UTF8_FULL) + .apply_modifier(UTF8_ROUND_CORNERS) + .set_style(TableComponent::VerticalLines, ' '); + tb.add_row(tbrows); + println!("{tb}"); +} + +/// 与えられたyaml_pathからauthorの名前を抽出して配列で返却する関数 +fn extract_author_name(yaml_path: String) -> Vec { + let parser = ParseYaml::new(); + let contents = match parser.read_file(Path::new(&yaml_path).to_path_buf()) { + Ok(yaml) => Some(yaml), + Err(e) => { + AlertMessage::alert(&e).ok(); + None + } + }; + if contents.is_none() { + // 対象のファイルが存在しなかった場合は空配列を返す(検知しているルールに対して行うため、ここは通る想定はないが、ファイルが検知途中で削除された場合などを考慮して追加) + return vec![]; + } + for yaml in YamlLoader::load_from_str(&contents.unwrap()).unwrap_or_default().into_iter() { + if let Some(author) = yaml["author"].as_str() { + return author.to_string().split(',').into_iter().map(|s| { + // 各要素の括弧以降の記載は名前としないためtmpの一番最初の要素のみを参照する + let tmp:Vec<&str> = s.split('(').collect(); + // データの中にdouble quote と single quoteが入っているためここで除外する + tmp[0].to_string().replace('"', "").replace('\'', "") + }).collect(); + }; + } + // ここまで来た場合は要素がない場合なので空配列を返す + return vec![]; +} + #[cfg(test)] mod tests { use crate::afterfact::_get_serialized_disp_output; From 2bdfc72bfa7fad7acfb485c0af035170432b85ba Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 8 Oct 2022 20:26:33 +0900 Subject: [PATCH 3/6] added output rule authors in standard output #724 --- src/afterfact.rs | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index eea8736e..16fa1605 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -423,11 +423,20 @@ fn emit_csv( &disp_wtr, get_writable_color(Some(Color::Rgb(0, 255, 0))), "Rules brought to you by:", + false, + ) + .ok(); + write_color_buffer( + &disp_wtr, + get_writable_color(None), + " ", true, ) .ok(); + println!(); output_detected_rule_authors(rule_author_counter); + println!(); if !configs::CONFIG.read().unwrap().args.no_summary { disp_wtr_buf.clear(); @@ -1183,22 +1192,29 @@ fn output_detected_rule_authors(rule_author_counter: HashMap) { sorted_authors.len()/4 }; - for x in 0..div { + for x in 0..4 { let mut tmp = Vec::new(); - for y in 0..4 { - tmp.push(format!("{}({})", sorted_authors[x*4 + y].0, sorted_authors[x*4 + y].1)); + for y in 0..div { + if y*4 + x < sorted_authors.len() { + tmp.push(format!("{}({})", sorted_authors[y*4 + x].0, sorted_authors[y*4 + x].1)); + } } output.push(tmp); } let mut tbrows = vec![]; for c in output.iter() { - tbrows.push(Cell::new(c.join("\n"))); + tbrows.push(Cell::new(c.join("\n")).fg(comfy_table::Color::Reset)); } let mut tb = Table::new(); + let hlch = tb.style(TableComponent::HorizontalLines).unwrap(); + let tbch = tb.style(TableComponent::TopBorder).unwrap(); + tb.load_preset(UTF8_FULL) .apply_modifier(UTF8_ROUND_CORNERS) .set_style(TableComponent::VerticalLines, ' '); - tb.add_row(tbrows); + tb.add_row(tbrows).set_style(TableComponent::MiddleIntersections, hlch) + .set_style(TableComponent::TopBorderIntersections, tbch) + .set_style(TableComponent::BottomBorderIntersections, hlch); println!("{tb}"); } @@ -1218,16 +1234,26 @@ fn extract_author_name(yaml_path: String) -> Vec { } for yaml in YamlLoader::load_from_str(&contents.unwrap()).unwrap_or_default().into_iter() { if let Some(author) = yaml["author"].as_str() { - return author.to_string().split(',').into_iter().map(|s| { + let authors_vec: Vec = author.to_string().split(',').into_iter().map(|s| { // 各要素の括弧以降の記載は名前としないためtmpの一番最初の要素のみを参照する let tmp:Vec<&str> = s.split('(').collect(); // データの中にdouble quote と single quoteが入っているためここで除外する - tmp[0].to_string().replace('"', "").replace('\'', "") + tmp[0].to_string() + }).collect(); + let mut ret: Vec<&str> = Vec::new(); + for author in &authors_vec { + ret.extend(author.split(';')); + } + + return ret.iter().map(|r| { + r.split('/').map(|p| { + p.to_string().replace('"', "").replace('\'', "").trim().to_owned() + }).collect() }).collect(); }; } // ここまで来た場合は要素がない場合なので空配列を返す - return vec![]; + vec![] } #[cfg(test)] From cecbe49f42b60226e2736209c71394b8728f51a6 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 8 Oct 2022 20:28:31 +0900 Subject: [PATCH 4/6] cargo fmt --- src/afterfact.rs | 80 ++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 16fa1605..e2a2aa2d 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -3,7 +3,7 @@ use crate::detections::message::{self, AlertMessage, LEVEL_ABBR, LEVEL_FULL}; use crate::detections::utils::{self, format_time, get_writable_color, write_color_buffer}; use crate::options::htmlreport; use crate::options::profile::PROFILES; -use crate::yaml::{ParseYaml}; +use crate::yaml::ParseYaml; use bytesize::ByteSize; use chrono::{DateTime, Local, TimeZone, Utc}; use comfy_table::modifiers::UTF8_ROUND_CORNERS; @@ -15,9 +15,9 @@ use itertools::Itertools; use krapslog::{build_sparkline, build_time_markers}; use lazy_static::lazy_static; use linked_hash_map::LinkedHashMap; -use yaml_rust::YamlLoader; use std::path::Path; use std::str::FromStr; +use yaml_rust::YamlLoader; use comfy_table::*; use hashbrown::{HashMap, HashSet}; @@ -426,13 +426,7 @@ fn emit_csv( false, ) .ok(); - write_color_buffer( - &disp_wtr, - get_writable_color(None), - " ", - true, - ) - .ok(); + write_color_buffer(&disp_wtr, get_writable_color(None), " ", true).ok(); println!(); output_detected_rule_authors(rule_author_counter); @@ -1180,23 +1174,25 @@ fn output_json_str( } fn output_detected_rule_authors(rule_author_counter: HashMap) { - let mut sorted_authors: Vec<(&String, &i128)> = rule_author_counter - .iter() - .collect(); + let mut sorted_authors: Vec<(&String, &i128)> = rule_author_counter.iter().collect(); sorted_authors.sort_by(|a, b| (-a.1).cmp(&(-b.1))); let mut output = Vec::new(); - let div = if sorted_authors.len()%4 != 0{ - sorted_authors.len()/4 + 1 + let div = if sorted_authors.len() % 4 != 0 { + sorted_authors.len() / 4 + 1 } else { - sorted_authors.len()/4 + sorted_authors.len() / 4 }; - + for x in 0..4 { let mut tmp = Vec::new(); for y in 0..div { - if y*4 + x < sorted_authors.len() { - tmp.push(format!("{}({})", sorted_authors[y*4 + x].0, sorted_authors[y*4 + x].1)); + if y * 4 + x < sorted_authors.len() { + tmp.push(format!( + "{}({})", + sorted_authors[y * 4 + x].0, + sorted_authors[y * 4 + x].1 + )); } } output.push(tmp); @@ -1212,9 +1208,10 @@ fn output_detected_rule_authors(rule_author_counter: HashMap) { tb.load_preset(UTF8_FULL) .apply_modifier(UTF8_ROUND_CORNERS) .set_style(TableComponent::VerticalLines, ' '); - tb.add_row(tbrows).set_style(TableComponent::MiddleIntersections, hlch) - .set_style(TableComponent::TopBorderIntersections, tbch) - .set_style(TableComponent::BottomBorderIntersections, hlch); + tb.add_row(tbrows) + .set_style(TableComponent::MiddleIntersections, hlch) + .set_style(TableComponent::TopBorderIntersections, tbch) + .set_style(TableComponent::BottomBorderIntersections, hlch); println!("{tb}"); } @@ -1232,24 +1229,41 @@ fn extract_author_name(yaml_path: String) -> Vec { // 対象のファイルが存在しなかった場合は空配列を返す(検知しているルールに対して行うため、ここは通る想定はないが、ファイルが検知途中で削除された場合などを考慮して追加) return vec![]; } - for yaml in YamlLoader::load_from_str(&contents.unwrap()).unwrap_or_default().into_iter() { + for yaml in YamlLoader::load_from_str(&contents.unwrap()) + .unwrap_or_default() + .into_iter() + { if let Some(author) = yaml["author"].as_str() { - let authors_vec: Vec = author.to_string().split(',').into_iter().map(|s| { - // 各要素の括弧以降の記載は名前としないためtmpの一番最初の要素のみを参照する - let tmp:Vec<&str> = s.split('(').collect(); - // データの中にdouble quote と single quoteが入っているためここで除外する - tmp[0].to_string() - }).collect(); + let authors_vec: Vec = author + .to_string() + .split(',') + .into_iter() + .map(|s| { + // 各要素の括弧以降の記載は名前としないためtmpの一番最初の要素のみを参照する + let tmp: Vec<&str> = s.split('(').collect(); + // データの中にdouble quote と single quoteが入っているためここで除外する + tmp[0].to_string() + }) + .collect(); let mut ret: Vec<&str> = Vec::new(); for author in &authors_vec { ret.extend(author.split(';')); } - return ret.iter().map(|r| { - r.split('/').map(|p| { - p.to_string().replace('"', "").replace('\'', "").trim().to_owned() - }).collect() - }).collect(); + return ret + .iter() + .map(|r| { + r.split('/') + .map(|p| { + p.to_string() + .replace('"', "") + .replace('\'', "") + .trim() + .to_owned() + }) + .collect() + }) + .collect(); }; } // ここまで来た場合は要素がない場合なので空配列を返す From 0612a3a04d4ef7b9b95504f4cb2556252686a362 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 8 Oct 2022 20:30:48 +0900 Subject: [PATCH 5/6] updated changelog #724 --- CHANGELOG-Japanese.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index b2220eb7..ca1dc5d0 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -5,6 +5,7 @@ **改善:** - 検知ルールを利用しないオプション(`-M`と`-L`オプション)の時のメッセージの出力内容を修正した。 (#730) (@hitenkoku) +- 検出したルールの作者名を標準出力に追加した。 (#724) (@hitenkoku) **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e694a5..7b967247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Enhancements:** - Do not display a message about loading detection rules when using the `-M` or `-L` options. (#730) (@hitenkoku) +- Added output of detected rule authors in standard output. (#724) (@hitenkoku) **Bug Fixes:** From 8c212b7524cd1e34bc3070344cef8791d2595d6a Mon Sep 17 00:00:00 2001 From: Yamato Security <71482215+YamatoSecurity@users.noreply.github.com> Date: Sun, 9 Oct 2022 06:07:38 +0900 Subject: [PATCH 6/6] update changelog --- CHANGELOG.md | 4 ++-- src/afterfact.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c21de01..3a8b6a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,11 @@ - Hayabusa now checks Channel and EID information based on `rules/config/channel_eid_info.txt` to provide more accurate results. (#463) (@garigariganzy) - Do not display a message about loading detection rules when using the `-M` or `-L` options. (#730) (@hitenkoku) -- Added output of detected rule authors in standard output. (#724) (@hitenkoku) +- Added a table of rule authors to standard output. (#724) (@hitenkoku) **Bug Fixes:** -- Fixed duplicate event outputted with metric option due to sum up of event IDs key name. (#729) (@hitenkoku) +- Fixed a bug where the same Channel and EID would be counted seperately with the `-M` option. (#729) (@hitenkoku) ## 1.7.0 [2022/09/29] diff --git a/src/afterfact.rs b/src/afterfact.rs index e2a2aa2d..1286acbf 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -422,7 +422,7 @@ fn emit_csv( write_color_buffer( &disp_wtr, get_writable_color(Some(Color::Rgb(0, 255, 0))), - "Rules brought to you by:", + "Rule Authors:", false, ) .ok(); @@ -1189,7 +1189,7 @@ fn output_detected_rule_authors(rule_author_counter: HashMap) { for y in 0..div { if y * 4 + x < sorted_authors.len() { tmp.push(format!( - "{}({})", + "{} ({})", sorted_authors[y * 4 + x].0, sorted_authors[y * 4 + x].1 ));