From d3c6a65f5b8e021075ffba657625da46ac50c95a Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Fri, 23 Sep 2022 15:29:45 +0900 Subject: [PATCH 01/33] added html_report option #689 --- src/detections/configs.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/detections/configs.rs b/src/detections/configs.rs index f1849a65..8e8a2e49 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -248,6 +248,10 @@ pub struct Config { /// Do not display result summary #[clap(help_heading = Some("DISPLAY-SETTINGS"), long = "no-summary")] pub no_summary: bool, + + /// Save detail Results Summary in html (ex: results.html) + #[clap(help_heading = Some("OUTPUT"), short = 'H', long="html-report", value_name = "FILE")] + pub html_report: Option, } impl ConfigReader<'_> { From 7303ef33adc730924c317e0261426ebf5e93d9da Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Fri, 23 Sep 2022 20:18:15 +0900 Subject: [PATCH 02/33] added pulldown-cmark to convert from markdown string to html format string #689 --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ed2c707e..df6d3e9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,6 +749,7 @@ dependencies = [ "openssl", "pbr", "prettytable-rs", + "pulldown-cmark", "quick-xml", "rand", "regex", diff --git a/Cargo.toml b/Cargo.toml index b672d7c7..a644d232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ lock_api = "0.4.*" crossbeam-utils = "0.8.*" num-format = "*" comfy-table = "6.*" +pulldown-cmark = { version = "0.9.*", default-features = false, features = ["simd"] } [build-dependencies] static_vcruntime = "2.*" From fb801dcbad3ff867ff63299b8672970079d12fd8 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:38:25 +0900 Subject: [PATCH 03/33] added eventfile and filesize data to html summary #689 --- src/main.rs | 17 ++++++++++++++++- src/options/mod.rs | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 5da1b162..b8c986db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use hayabusa::detections::pivot::PivotKeyword; use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::omikuji::Omikuji; +use hayabusa::options::htmlreport::HTML_REPORTER; use hayabusa::options::profile::PROFILES; use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules}; use hayabusa::{afterfact::after_fact, detections::utils}; @@ -525,11 +526,25 @@ impl App { let meta = fs::metadata(file_path).ok(); total_file_size += ByteSize::b(meta.unwrap().len()); } - println!("Total file size: {}", total_file_size.to_string_as(false)); + let total_size_output = format!("Total file size: {}", total_file_size.to_string_as(false)); + println!("{}", total_size_output); println!(); println!("Loading detections rules. Please wait."); println!(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + let mut html_report_data = HTML_REPORTER + .write() + .unwrap().md_datas.clone(); + let entry = html_report_data + .entry("General Overview".to_string()) + .or_insert(Vec::new()); + entry.push(format!("- Analyzed event files: {:?}", evtx_files.len())); + entry.push("".to_string()); + entry.push(format!("- {}", total_size_output)); + HTML_REPORTER.write().unwrap().md_datas = html_report_data; + } + let rule_files = detection::Detection::parse_rule_files( level, &configs::CONFIG.read().unwrap().args.rules, diff --git a/src/options/mod.rs b/src/options/mod.rs index f63dd2b9..848ff1c0 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -1,3 +1,4 @@ +pub mod htmlreport; pub mod level_tuning; pub mod profile; pub mod update_rules; From 8cc73e20c9762a5a88739239781b8fe39545a78b Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 16:55:58 +0900 Subject: [PATCH 04/33] refactoring --- src/main.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index b8c986db..b8212d1e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use hayabusa::detections::pivot::PivotKeyword; use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::omikuji::Omikuji; -use hayabusa::options::htmlreport::HTML_REPORTER; +use hayabusa::options::htmlreport::{HTML_REPORTER, self}; use hayabusa::options::profile::PROFILES; use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules}; use hayabusa::{afterfact::after_fact, detections::utils}; @@ -505,7 +505,6 @@ impl App { } } } - fn analysis_files(&mut self, evtx_files: Vec, time_filter: &TargetEventTime) { let level = configs::CONFIG .read() @@ -533,16 +532,8 @@ impl App { println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - let mut html_report_data = HTML_REPORTER - .write() - .unwrap().md_datas.clone(); - let entry = html_report_data - .entry("General Overview".to_string()) - .or_insert(Vec::new()); - entry.push(format!("- Analyzed event files: {:?}", evtx_files.len())); - entry.push("".to_string()); - entry.push(format!("- {}", total_size_output)); - HTML_REPORTER.write().unwrap().md_datas = html_report_data; + let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); + htmlreport::add_md_data(html_report_data, "General Overview".to_string(),format!("- Analyzed event files: {}", evtx_files.len())); } let rule_files = detection::Detection::parse_rule_files( From 56681d85e7965f5b00132fe3693766531148f191 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 17:16:03 +0900 Subject: [PATCH 05/33] added rules info in General Overview to HTML Summary #689 --- src/detections/detection.rs | 50 +++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index f1b5af43..0d67e7cd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -27,6 +27,7 @@ use hashbrown::HashMap; use serde_json::Value; use std::fmt::Write; use std::path::Path; +use crate::options::htmlreport::{self, HTML_REPORTER}; use std::sync::Arc; use tokio::{runtime::Runtime, spawn, task::JoinHandle}; @@ -605,6 +606,7 @@ impl Detection { let mut sorted_ld_rc: Vec<(&String, &u128)> = ld_rc.iter().collect(); sorted_ld_rc.sort_by(|a, b| a.0.cmp(b.0)); let args = &configs::CONFIG.read().unwrap().args; + let mut html_report_stock = Vec::new(); sorted_ld_rc.into_iter().for_each(|(key, value)| { if value != &0_u128 { @@ -614,12 +616,16 @@ impl Detection { "" }; //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する + let output_str = format!( "{} rules: {}{}", + make_ascii_titlecase(key.clone().as_mut()), + value, + disable_flag); println!( - "{} rules: {}{}", - make_ascii_titlecase(key.clone().as_mut()), - value, - disable_flag, + "{}", output_str ); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_report_stock.push(output_str); + } } }); if err_rc != &0_u128 { @@ -644,20 +650,24 @@ impl Detection { } else { "" }; + let output_str = format!( + "{} rules: {} ({:.2}%){}", + make_ascii_titlecase(key.clone().as_mut()), + value, + rate, + deprecated_flag + ); //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, - &format!( - "{} rules: {} ({:.2}%){}", - make_ascii_titlecase(key.clone().as_mut()), - value, - rate, - deprecated_flag - ), + &output_str, true, ) .ok(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_report_stock.push(output_str); + } } }); println!(); @@ -665,17 +675,31 @@ impl Detection { let mut sorted_rc: Vec<(&String, &u128)> = rc.iter().collect(); sorted_rc.sort_by(|a, b| a.0.cmp(b.0)); sorted_rc.into_iter().for_each(|(key, value)| { + let output_str = format!("{} rules: {}", key, value); write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, - &format!("{} rules: {}", key, value), + &output_str, true, ) .ok(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_report_stock.push(output_str); + } }); - println!("Total enabled detection rules: {}", total_loaded_rule_cnt); + let tmp_total_detect_output = format!("Total enabled detection rules: {}", total_loaded_rule_cnt); + println!("{}", tmp_total_detect_output); println!(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_report_stock.push(tmp_total_detect_output); + } + if !html_report_stock.is_empty() { + for report_row in html_report_stock { + let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); + htmlreport::add_md_data(html_report_data, "General Overview".to_string(),format!("- Analyzed event files: {}", report_row)); + } + } } } From 50ec9105f68a6a696879680acf090246300eac81 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 17:16:48 +0900 Subject: [PATCH 06/33] added file size to HTML Summary #689 --- src/main.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index b8212d1e..c5a35813 100644 --- a/src/main.rs +++ b/src/main.rs @@ -532,8 +532,15 @@ impl App { println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); - htmlreport::add_md_data(html_report_data, "General Overview".to_string(),format!("- Analyzed event files: {}", evtx_files.len())); + let output_data = vec![ + format!("- Analyzed event files: {}", evtx_files.len()), + format!("- {}", total_size_output), + "".to_string(), + ]; + for output in output_data { + let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); + htmlreport::add_md_data(html_report_data, "General Overview".to_string(),output); + } } let rule_files = detection::Detection::parse_rule_files( From 1dd4928bd58ec2131f466e29c9597dfbe2aca5ea Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 17:17:39 +0900 Subject: [PATCH 07/33] cargo fmt --- src/detections/detection.rs | 25 +++++++++++++++---------- src/main.rs | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 0d67e7cd..5062aef1 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -22,12 +22,12 @@ use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; use crate::detections::utils::{get_serde_number_to_string, make_ascii_titlecase}; use crate::filter; +use crate::options::htmlreport::{self, HTML_REPORTER}; use crate::yaml::ParseYaml; use hashbrown::HashMap; use serde_json::Value; use std::fmt::Write; use std::path::Path; -use crate::options::htmlreport::{self, HTML_REPORTER}; use std::sync::Arc; use tokio::{runtime::Runtime, spawn, task::JoinHandle}; @@ -616,13 +616,13 @@ impl Detection { "" }; //タイトルに利用するものはascii文字であることを前提として1文字目を大文字にするように変更する - let output_str = format!( "{} rules: {}{}", - make_ascii_titlecase(key.clone().as_mut()), - value, - disable_flag); - println!( - "{}", output_str + let output_str = format!( + "{} rules: {}{}", + make_ascii_titlecase(key.clone().as_mut()), + value, + disable_flag ); + println!("{}", output_str); if configs::CONFIG.read().unwrap().args.html_report.is_some() { html_report_stock.push(output_str); } @@ -688,7 +688,8 @@ impl Detection { } }); - let tmp_total_detect_output = format!("Total enabled detection rules: {}", total_loaded_rule_cnt); + let tmp_total_detect_output = + format!("Total enabled detection rules: {}", total_loaded_rule_cnt); println!("{}", tmp_total_detect_output); println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { @@ -697,10 +698,14 @@ impl Detection { if !html_report_stock.is_empty() { for report_row in html_report_stock { let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); - htmlreport::add_md_data(html_report_data, "General Overview".to_string(),format!("- Analyzed event files: {}", report_row)); + htmlreport::add_md_data( + html_report_data, + "General Overview".to_string(), + format!("- Analyzed event files: {}", report_row), + ); + } } } - } } #[cfg(test)] diff --git a/src/main.rs b/src/main.rs index c5a35813..ec37edf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use hayabusa::detections::pivot::PivotKeyword; use hayabusa::detections::pivot::PIVOT_KEYWORD; use hayabusa::detections::rule::{get_detection_keys, RuleNode}; use hayabusa::omikuji::Omikuji; -use hayabusa::options::htmlreport::{HTML_REPORTER, self}; +use hayabusa::options::htmlreport::{self, HTML_REPORTER}; use hayabusa::options::profile::PROFILES; use hayabusa::options::{level_tuning::LevelTuning, update_rules::UpdateRules}; use hayabusa::{afterfact::after_fact, detections::utils}; @@ -539,7 +539,7 @@ impl App { ]; for output in output_data { let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); - htmlreport::add_md_data(html_report_data, "General Overview".to_string(),output); + htmlreport::add_md_data(html_report_data, "General Overview".to_string(), output); } } From b80a66020794d44b70795458eb636d70e73869fa Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sat, 24 Sep 2022 17:33:09 +0900 Subject: [PATCH 08/33] refactoring --- src/detections/detection.rs | 21 +++++++++------------ src/main.rs | 18 +++++++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 5062aef1..019dd95e 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -624,7 +624,7 @@ impl Detection { ); println!("{}", output_str); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_report_stock.push(output_str); + html_report_stock.push(format!("- {}", output_str)); } } }); @@ -666,7 +666,8 @@ impl Detection { ) .ok(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_report_stock.push(output_str); + html_report_stock.push(format!( + "- {}", output_str)); } } }); @@ -684,7 +685,7 @@ impl Detection { ) .ok(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_report_stock.push(output_str); + html_report_stock.push(format!("- {}", output_str)); } }); @@ -693,17 +694,13 @@ impl Detection { println!("{}", tmp_total_detect_output); println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_report_stock.push(tmp_total_detect_output); + html_report_stock.push(format!("- {}", tmp_total_detect_output)); } if !html_report_stock.is_empty() { - for report_row in html_report_stock { - let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); - htmlreport::add_md_data( - html_report_data, - "General Overview".to_string(), - format!("- Analyzed event files: {}", report_row), - ); - } + htmlreport::add_md_data( + "General Overview".to_string(), + html_report_stock + ); } } } diff --git a/src/main.rs b/src/main.rs index ec37edf5..19ed6e66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -323,15 +323,22 @@ impl App { let analysis_end_time: DateTime = Local::now(); let analysis_duration = analysis_end_time.signed_duration_since(analysis_start_time); + let elapsed_output_str = format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()); write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, - &format!("Elapsed Time: {}", &analysis_duration.hhmmssxxx()), - true, + &elapsed_output_str, + true ) .ok(); println!(); - + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + let output_data = vec![ + format!("- {}", elapsed_output_str), + "".to_string(), + ]; + htmlreport::add_md_data( "General Overview".to_string(), output_data); + } // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。 if ERROR_LOG_STACK.lock().unwrap().len() > 0 { AlertMessage::create_error_log(ERROR_LOG_PATH.to_string()); @@ -537,10 +544,7 @@ impl App { format!("- {}", total_size_output), "".to_string(), ]; - for output in output_data { - let html_report_data = HTML_REPORTER.write().unwrap().md_datas.clone(); - htmlreport::add_md_data(html_report_data, "General Overview".to_string(), output); - } + htmlreport::add_md_data("General Overview".to_string(), output_data); } let rule_files = detection::Detection::parse_rule_files( From 5c0bc48a78c101b0c09325db10eef201c74eed5b Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:01:45 +0900 Subject: [PATCH 09/33] added Result Summary data in to html summary #689 --- src/afterfact.rs | 114 ++++++++++++++++++++++++++++-------- src/detections/detection.rs | 8 +-- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index b8bc0ecb..da0fd00a 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -4,6 +4,7 @@ use crate::detections::message::{self, LEVEL_ABBR}; use crate::detections::message::{AlertMessage, LEVEL_FULL}; use crate::detections::utils::{self, format_time}; use crate::detections::utils::{get_writable_color, write_color_buffer}; +use crate::options::htmlreport; use crate::options::profile::PROFILES; use bytesize::ByteSize; use chrono::{DateTime, Local, TimeZone, Utc}; @@ -210,6 +211,8 @@ fn emit_csv( all_record_cnt: u128, profile: LinkedHashMap, ) -> io::Result<()> { + let mut html_output_stock: Vec = vec![]; + let html_output_flag = configs::CONFIG.read().unwrap().args.html_report.is_some(); let disp_wtr = BufferWriter::stdout(ColorChoice::Always); let mut disp_wtr_buf = disp_wtr.buffer(); let json_output_flag = configs::CONFIG.read().unwrap().args.json_timeline; @@ -454,31 +457,35 @@ fn emit_csv( ) .ok(); write_color_buffer(&disp_wtr, get_writable_color(None), ": ", false).ok(); + let saved_alerts_output = + (all_record_cnt - reducted_record_cnt).to_formatted_string(&Locale::en); write_color_buffer( &disp_wtr, get_writable_color(Some(Color::Rgb(255, 255, 0))), - &(all_record_cnt - reducted_record_cnt).to_formatted_string(&Locale::en), + &saved_alerts_output, false, ) .ok(); write_color_buffer(&disp_wtr, get_writable_color(None), " / ", false).ok(); + let all_record_output = all_record_cnt.to_formatted_string(&Locale::en); write_color_buffer( &disp_wtr, get_writable_color(Some(Color::Rgb(0, 255, 255))), - &all_record_cnt.to_formatted_string(&Locale::en), + &all_record_output, false, ) .ok(); write_color_buffer(&disp_wtr, get_writable_color(None), " (", false).ok(); + let reduction_output = format!( + "Data reduction: {} events ({:.2}%)", + reducted_record_cnt.to_formatted_string(&Locale::en), + reducted_percent + ); write_color_buffer( &disp_wtr, get_writable_color(Some(Color::Rgb(0, 255, 0))), - &format!( - "Data reduction: {} events ({:.2}%)", - reducted_record_cnt.to_formatted_string(&Locale::en), - reducted_percent - ), + &reduction_output, false, ) .ok(); @@ -487,6 +494,12 @@ fn emit_csv( println!(); println!(); + if html_output_flag { + html_output_stock.push(format!("Saved alerts and events: {}", &saved_alerts_output)); + html_output_stock.push(format!("Total events analyzed: {}", &all_record_output)); + html_output_stock.push(reduction_output); + } + _print_unique_results( total_detect_counts_by_level, unique_detect_counts_by_level, @@ -496,17 +509,40 @@ fn emit_csv( ); println!(); - _print_detection_summary_by_date(detect_counts_by_date_and_level, &color_map); + _print_detection_summary_by_date( + detect_counts_by_date_and_level, + &color_map, + &mut html_output_stock, + ); println!(); println!(); + if html_output_flag { + html_output_stock.push("".to_string()); + } - _print_detection_summary_by_computer(detect_counts_by_computer_and_level, &color_map); + _print_detection_summary_by_computer( + detect_counts_by_computer_and_level, + &color_map, + &mut html_output_stock, + ); println!(); + if html_output_flag { + html_output_stock.push("".to_string()); + } - _print_detection_summary_tables(detect_counts_by_rule_and_level, &color_map); + _print_detection_summary_tables( + detect_counts_by_rule_and_level, + &color_map, + &mut html_output_stock, + ); println!(); + if html_output_flag { + html_output_stock.push("".to_string()); + } + } + if html_output_flag { + htmlreport::add_md_data("Results Summary".to_string(), html_output_stock); } - Ok(()) } @@ -634,13 +670,16 @@ fn _print_unique_results( fn _print_detection_summary_by_date( detect_counts_by_date: HashMap>, color_map: &HashMap, + html_output_stock: &mut Vec, ) { let buf_wtr = BufferWriter::stdout(ColorChoice::Always); let mut wtr = buf_wtr.buffer(); wtr.set_color(ColorSpec::new().set_fg(None)).ok(); - - writeln!(wtr, "Dates with most total detections:").ok(); - + let output_header = "Dates with most total detections:"; + writeln!(wtr, "{}", output_header).ok(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_output_stock.push(output_header.to_string()); + } for (idx, level) in LEVEL_ABBR.values().enumerate() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 let detections_by_day = detect_counts_by_date.get(level).unwrap(); @@ -662,32 +701,38 @@ fn _print_detection_summary_by_date( if !exist_max_data { max_detect_str = "n/a".to_string(); } - write!( - wtr, + let output_str = format!( "{}: {}", LEVEL_FULL.get(level.as_str()).unwrap(), &max_detect_str - ) - .ok(); + ); + write!(wtr, "{}", output_str).ok(); if idx != LEVEL_ABBR.len() - 1 { wtr.set_color(ColorSpec::new().set_fg(None)).ok(); write!(wtr, ", ").ok(); } + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_output_stock.push(output_str); + } } buf_wtr.print(&wtr).ok(); } -/// 各レベル毎で最も高い検知数を出した日付を出力する +/// 各レベル毎で最も高い検知数を出したコンピュータ名を出力する fn _print_detection_summary_by_computer( detect_counts_by_computer: HashMap>, color_map: &HashMap, + html_output_stock: &mut Vec, ) { let buf_wtr = BufferWriter::stdout(ColorChoice::Always); let mut wtr = buf_wtr.buffer(); wtr.set_color(ColorSpec::new().set_fg(None)).ok(); writeln!(wtr, "Top 5 computers with most unique detections:").ok(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_output_stock.push("Computers with most unique critical detections:".to_string()); + } for level in LEVEL_ABBR.values() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 let detections_by_computer = detect_counts_by_computer.get(level).unwrap(); @@ -700,6 +745,17 @@ fn _print_detection_summary_by_computer( sorted_detections.sort_by(|a, b| (-a.1).cmp(&(-b.1))); + // html出力は各種すべてのコンピュータ名を表示するようにする + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + for x in sorted_detections.iter() { + html_output_stock.push(format!( + "{} ({})", + x.0, + x.1.to_formatted_string(&Locale::en) + )); + } + html_output_stock.push("".to_string()); + } for x in sorted_detections.iter().take(5) { result_vec.push(format!( "{} ({})", @@ -733,6 +789,7 @@ fn _print_detection_summary_by_computer( fn _print_detection_summary_tables( detect_counts_by_rule_and_level: HashMap>, color_map: &HashMap, + html_output_stock: &mut Vec, ) { let buf_wtr = BufferWriter::stdout(ColorChoice::Always); let mut wtr = buf_wtr.buffer(); @@ -741,10 +798,8 @@ fn _print_detection_summary_tables( let mut col_color = vec![]; for level in LEVEL_ABBR.values() { let mut col_output: Vec = vec![]; - col_output.push(format!( - "Top {} alerts:", - LEVEL_FULL.get(level.as_str()).unwrap() - )); + let header_output = &format!("Top {} alerts:", LEVEL_FULL.get(level.as_str()).unwrap()); + col_output.push(header_output.to_owned()); col_color.push(_get_table_color( color_map, @@ -757,6 +812,19 @@ fn _print_detection_summary_tables( sorted_detections.sort_by(|a, b| (-a.1).cmp(&(-b.1))); + // html出力の場合はすべての内容を出力するようにする + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_output_stock.push(header_output.to_string()); + for x in sorted_detections.iter() { + html_output_stock.push(format!( + "{} ({})", + x.0, + x.1.to_formatted_string(&Locale::en) + )); + } + html_output_stock.push("".to_string()); + } + let take_cnt = if LEVEL_FULL.get(level.as_str()).unwrap_or(&"-".to_string()) == "informational" { 10 diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 019dd95e..ec7a22bd 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -666,8 +666,7 @@ impl Detection { ) .ok(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_report_stock.push(format!( - "- {}", output_str)); + html_report_stock.push(format!("- {}", output_str)); } } }); @@ -697,10 +696,7 @@ impl Detection { html_report_stock.push(format!("- {}", tmp_total_detect_output)); } if !html_report_stock.is_empty() { - htmlreport::add_md_data( - "General Overview".to_string(), - html_report_stock - ); + htmlreport::add_md_data("General Overview".to_string(), html_report_stock); } } } From a379b3c077b8d1e207df4b6f1048c0437a952065 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:02:29 +0900 Subject: [PATCH 10/33] fixed elapse time HTML output --- src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 19ed6e66..f8cf65fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -328,16 +328,13 @@ impl App { &BufferWriter::stdout(ColorChoice::Always), None, &elapsed_output_str, - true + true, ) .ok(); println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - let output_data = vec![ - format!("- {}", elapsed_output_str), - "".to_string(), - ]; - htmlreport::add_md_data( "General Overview".to_string(), output_data); + let output_data = vec![format!("- {}", elapsed_output_str), "".to_string()]; + htmlreport::add_md_data("General Overview".to_string(), output_data); } // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。 if ERROR_LOG_STACK.lock().unwrap().len() > 0 { From c41206accd4381b8c0140274750a15b230ad6175 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:15:58 +0900 Subject: [PATCH 11/33] added html output processing #689 --- src/main.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main.rs b/src/main.rs index f8cf65fa..738e30d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -413,6 +413,11 @@ impl App { }); } } + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + let html_str = HTML_REPORTER.read().unwrap().clone().create_html(); + htmlreport::create_html_file(html_str, configs::CONFIG.read().unwrap().args.html_report.as_ref().unwrap().to_str().unwrap_or("").to_string()) + + } } #[cfg(not(target_os = "windows"))] From ecc19b332bc0246da9fe11403241070c5a1cfb06 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:16:35 +0900 Subject: [PATCH 12/33] cargo fmt --- src/main.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 738e30d7..b3a4cb30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -415,8 +415,19 @@ impl App { } if configs::CONFIG.read().unwrap().args.html_report.is_some() { let html_str = HTML_REPORTER.read().unwrap().clone().create_html(); - htmlreport::create_html_file(html_str, configs::CONFIG.read().unwrap().args.html_report.as_ref().unwrap().to_str().unwrap_or("").to_string()) - + htmlreport::create_html_file( + html_str, + configs::CONFIG + .read() + .unwrap() + .args + .html_report + .as_ref() + .unwrap() + .to_str() + .unwrap_or("") + .to_string(), + ) } } From 64be8258e5b57894fad6d29ed12e579c3f80049e Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:17:18 +0900 Subject: [PATCH 13/33] removed unnecessary crate use --- src/detections/detection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detections/detection.rs b/src/detections/detection.rs index ec7a22bd..c52deed7 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -22,7 +22,7 @@ use crate::detections::rule::AggResult; use crate::detections::rule::RuleNode; use crate::detections::utils::{get_serde_number_to_string, make_ascii_titlecase}; use crate::filter; -use crate::options::htmlreport::{self, HTML_REPORTER}; +use crate::options::htmlreport::{self}; use crate::yaml::ParseYaml; use hashbrown::HashMap; use serde_json::Value; From 477f1c492f322c2837455c87b0ca482752d8f04a Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:36:53 +0900 Subject: [PATCH 14/33] fixed html format #689 --- src/afterfact.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index da0fd00a..4e0c1517 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -495,9 +495,12 @@ fn emit_csv( println!(); if html_output_flag { - html_output_stock.push(format!("Saved alerts and events: {}", &saved_alerts_output)); - html_output_stock.push(format!("Total events analyzed: {}", &all_record_output)); - html_output_stock.push(reduction_output); + html_output_stock.push(format!( + "- Saved alerts and events: {}", + &saved_alerts_output + )); + html_output_stock.push(format!("- Total events analyzed: {}", &all_record_output)); + html_output_stock.push(format!("- {}", reduction_output)); } _print_unique_results( @@ -678,7 +681,7 @@ fn _print_detection_summary_by_date( let output_header = "Dates with most total detections:"; writeln!(wtr, "{}", output_header).ok(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push(output_header.to_string()); + html_output_stock.push(format!("- {}", output_header)); } for (idx, level) in LEVEL_ABBR.values().enumerate() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 @@ -709,11 +712,10 @@ fn _print_detection_summary_by_date( write!(wtr, "{}", output_str).ok(); if idx != LEVEL_ABBR.len() - 1 { wtr.set_color(ColorSpec::new().set_fg(None)).ok(); - write!(wtr, ", ").ok(); } if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push(output_str); + html_output_stock.push(format!(" - {}", output_str)); } } buf_wtr.print(&wtr).ok(); @@ -731,7 +733,7 @@ fn _print_detection_summary_by_computer( writeln!(wtr, "Top 5 computers with most unique detections:").ok(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push("Computers with most unique critical detections:".to_string()); + html_output_stock.push("### Computers with most unique critical detections:".to_string()); } for level in LEVEL_ABBR.values() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 @@ -749,7 +751,7 @@ fn _print_detection_summary_by_computer( if configs::CONFIG.read().unwrap().args.html_report.is_some() { for x in sorted_detections.iter() { html_output_stock.push(format!( - "{} ({})", + "- {} ({})", x.0, x.1.to_formatted_string(&Locale::en) )); @@ -814,10 +816,10 @@ fn _print_detection_summary_tables( // html出力の場合はすべての内容を出力するようにする if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push(header_output.to_string()); + html_output_stock.push(format!("### {}", header_output)); for x in sorted_detections.iter() { html_output_stock.push(format!( - "{} ({})", + "- {} ({})", x.0, x.1.to_formatted_string(&Locale::en) )); From e6e4f7484c5dd5c919a58f665dbb7f42e258c416 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:49:56 +0900 Subject: [PATCH 15/33] updated changelog #689 --- CHANGELOG-Japanese.md | 4 +++- CHANGELOG.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-Japanese.md b/CHANGELOG-Japanese.md index 296b665b..58b40561 100644 --- a/CHANGELOG-Japanese.md +++ b/CHANGELOG-Japanese.md @@ -1,9 +1,11 @@ # 変更点 -## 1.x.x [2022/XX/XX] +## 1.7.0 [2022/XX/XX] **新機能:** +- HTMLレポート機能の追加。 (#689) (@hitenkoku) + **改善:** **バグ修正:** diff --git a/CHANGELOG.md b/CHANGELOG.md index 52eb2549..7594223c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changes -## 1.x.x [2022/XX/XX] +## 1.7.0 [2022/XX/XX] **New Features:** +- Added html summary output. (``-H, --html-report` option) (#689) (@hitenkoku) + **Enhancements:** **Bug Fixes:** From 1ee84aafd3888d4995a86bc167adb554ed5e6b31 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 15:55:10 +0900 Subject: [PATCH 16/33] updated readme #689 --- README-Japanese.md | 1 + README.md | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README-Japanese.md b/README-Japanese.md index afef4813..46aeb403 100644 --- a/README-Japanese.md +++ b/README-Japanese.md @@ -393,6 +393,7 @@ ADVANCED: --target-file-ext ... evtx以外の拡張子を解析対象に追加する。 (例1: evtx_data 例2:evtx1 evtx2) OUTPUT: + -H, --html-report HTML形式で詳細な結果を出力する (例: results.html) -j, --json タイムラインの出力をJSON形式で保存する(例: -j -o results.json) -J, --jsonl タイムラインの出力をJSONL形式で保存する (例: -J -o results.jsonl) -o, --output タイムラインをCSV形式で保存する (例: results.csv) diff --git a/README.md b/README.md index 0bc30352..a5c6a3a7 100644 --- a/README.md +++ b/README.md @@ -384,10 +384,11 @@ ADVANCED: --target-file-ext ... Specify additional target file extensions (ex: evtx_data) (ex: evtx1 evtx2) OUTPUT: - -j, --json Save the timeline in JSON format (ex: -j -o results.json) - -J, --jsonl Save the timeline in JSONL format (ex: -J -o results.jsonl) - -o, --output Save the timeline in CSV format (ex: results.csv) - -P, --profile Specify output profile (minimal, standard, verbose, verbose-all-field-info, verbose-details-and-all-field-info) + -H, --html-report Save detail Results Summary in html (ex: results.html) + -j, --json Save the timeline in JSON format (ex: -j -o results.json) + -J, --jsonl Save the timeline in JSONL format (ex: -J -o results.jsonl) + -o, --output Save the timeline in CSV format (ex: results.csv) + -P, --profile Specify output profile (minimal, standard, verbose, verbose-all-field-info, verbose-details-and-all-field-info) DISPLAY-SETTINGS: --no-color Disable color output From 5eb23f0b14815ffb79056cc1de42e0ddb4931126 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 25 Sep 2022 16:19:32 +0900 Subject: [PATCH 17/33] fixed test --- src/options/htmlreport.rs | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/options/htmlreport.rs diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs new file mode 100644 index 00000000..cf55a0de --- /dev/null +++ b/src/options/htmlreport.rs @@ -0,0 +1,149 @@ +use crate::detections::message::AlertMessage; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use pulldown_cmark::{html, Options, Parser}; +use std::fs::create_dir; +use std::fs::File; +use std::io::BufWriter; +use std::io::Write; +use std::path::Path; +use std::sync::RwLock; + +lazy_static! { + pub static ref HTML_REPORTER: RwLock = RwLock::new(HtmlReporter::new()); +} + +#[derive(Clone)] +pub struct HtmlReporter { + pub section_order: Vec, + pub md_datas: HashMap>, +} + +impl HtmlReporter { + pub fn new() -> HtmlReporter { + let (init_section_order, init_data) = get_init_md_data_map(); + HtmlReporter { + section_order: init_section_order, + md_datas: init_data, + } + } + + /// return converted String from md_data(markdown fmt string). + pub fn create_html(self) -> String { + let mut options = Options::empty(); + options.insert(Options::ENABLE_TABLES); + let mut md_data = vec![]; + for section_name in self.section_order { + if let Some(v) = self.md_datas.get(§ion_name) { + md_data.push(format!("## {}\n", §ion_name)); + if v.is_empty() { + md_data.push("not found data.\n".to_string()); + } else { + md_data.push(v.join("\n")); + } + } + } + let md_str = md_data.join(""); + let parser = Parser::new_ext(&md_str, options); + + let mut ret = String::new(); + html::push_html(&mut ret, parser); + ret + } +} + +impl Default for HtmlReporter { + fn default() -> Self { + Self::new() + } +} + +/// get html report section data in LinkedHashMap +fn get_init_md_data_map() -> (Vec, HashMap>) { + let mut ret = HashMap::new(); + let section_order = vec![ + "General Overview".to_string(), + "Results Summary".to_string(), + ]; + for section in section_order.iter() { + ret.insert(section.to_owned(), vec![]); + } + + (section_order, ret) +} + +pub fn add_md_data(section_name: String, data: Vec) { + let mut md_with_section_data = HTML_REPORTER.write().unwrap().md_datas.clone(); + for c in data { + let entry = md_with_section_data + .entry(section_name.clone()) + .or_insert(Vec::new()); + entry.push(c); + } + HTML_REPORTER.write().unwrap().md_datas = md_with_section_data; +} + +/// create html file +pub fn create_html_file(input_html: String, path_str: String) { + let path = Path::new(&path_str); + if !path.parent().unwrap().exists() { + create_dir(path.parent().unwrap()).ok(); + } + + // if already exists same html report file. output alert message and exit + if path.exists() { + AlertMessage::alert(&format!( + " The file {} already exists. Please specify a different filename.", + &path_str + )) + .ok(); + return; + } + let mut html_writer = BufWriter::new(File::create(path).unwrap()); + writeln!(html_writer, "{}", input_html).ok(); + println!( + "HTML Report was generated. Please check {} for details.", + path_str + ); + println!(); +} + +#[cfg(test)] +mod tests { + + use crate::options::htmlreport::HtmlReporter; + + #[test] + fn test_create_html() { + let mut html_reporter = HtmlReporter::new(); + let general_data = vec![ + "- Analyzed event files: 581".to_string(), + "- Total file size: 148.5 MB".to_string(), + "- Excluded rules: 12".to_string(), + "- Noisy rules: 5 (Disabled)".to_string(), + "- Experimental rules: 1935 (65.97%)".to_string(), + "- Stable rules: 215 (7.33%)".to_string(), + "- Test rules: 783 (26.70%)".to_string(), + "- Hayabusa rules: 138".to_string(), + "- Sigma rules: 2795".to_string(), + "- Total enabled detection rules: 2933".to_string(), + "- Elapsed Time: 00:00:29.035".to_string(), + "".to_string(), + ]; + html_reporter + .md_datas + .insert("General Overview".to_string(), general_data.clone()); + let general_overview_str = format!( + "
    \n
  • {}
  • \n
", + general_data[..general_data.len() - 1] + .join("\n
  • ") + .replace("- ", "") + ); + let expect_str = format!( + "

    General Overview

    \n{}\n

    Results Summary

    \n

    not found data.

    \n", + general_overview_str + ); + + assert_eq!(html_reporter.create_html(), expect_str); + } +} From d048855eeb83e69b0da0faf0a79c28bacfd035d0 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:55:47 +0900 Subject: [PATCH 18/33] added start time in General Overview --- src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b3a4cb30..d3d71bbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,6 +92,14 @@ impl App { return; } let analysis_start_time: DateTime = Local::now(); + if configs::CONFIG.read().unwrap().args.html_report.is_some() { + let output_data = vec![ + format!("- Start time: {}", analysis_start_time.format("%Y/%m/%d %H:%M")), + "".to_string(), + ]; + htmlreport::add_md_data("General Overview".to_string(), output_data); + } + // Show usage when no arguments. if std::env::args().len() == 1 { self.output_logo(); @@ -108,7 +116,6 @@ impl App { &analysis_start_time.day().to_owned() )); } - if !self.is_matched_architecture_and_binary() { AlertMessage::alert( "The hayabusa version you ran does not match your PC architecture.\nPlease use the correct architecture. (Binary ending in -x64.exe for 64-bit and -x86.exe for 32-bit.)", From 6d7e2ce8b86f04e4c0a3828757cf0c1085b5a780 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 26 Sep 2022 19:56:13 +0900 Subject: [PATCH 19/33] fixed detection summary by computer h3 tag --- src/afterfact.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 4e0c1517..242bfbe2 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -732,9 +732,6 @@ fn _print_detection_summary_by_computer( wtr.set_color(ColorSpec::new().set_fg(None)).ok(); writeln!(wtr, "Top 5 computers with most unique detections:").ok(); - if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push("### Computers with most unique critical detections:".to_string()); - } for level in LEVEL_ABBR.values() { // output_levelsはlevelsからundefinedを除外した配列であり、各要素は必ず初期化されているのでSomeであることが保証されているのでunwrapをそのまま実施 let detections_by_computer = detect_counts_by_computer.get(level).unwrap(); @@ -749,6 +746,7 @@ fn _print_detection_summary_by_computer( // html出力は各種すべてのコンピュータ名を表示するようにする if configs::CONFIG.read().unwrap().args.html_report.is_some() { + html_output_stock.push(format!("### Computers with most unique {} detections:", LEVEL_FULL.get(level.as_str()).unwrap())); for x in sorted_detections.iter() { html_output_stock.push(format!( "- {} ({})", From a64f9c8da2a7e9c54ddffc1ce6e755a37516e728 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:27:41 +0900 Subject: [PATCH 20/33] changed alert title to rule file path --- src/afterfact.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 242bfbe2..9c4f60ed 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -241,7 +241,7 @@ fn emit_csv( HashMap::new(); let mut detect_counts_by_rule_and_level: HashMap> = HashMap::new(); - + let mut rule_title_path_map:HashMap = HashMap::new(); let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]); // レベル別、日ごとの集計用変数の初期化 for level_init in levels { @@ -375,6 +375,7 @@ fn emit_csv( .unwrap() }) .clone(); + rule_title_path_map.insert(detect_info.ruletitle.clone(), detect_info.rulepath.clone()); *detect_counts_by_rules .entry(Clone::clone(&detect_info.ruletitle)) .or_insert(0) += 1; @@ -536,6 +537,7 @@ fn emit_csv( _print_detection_summary_tables( detect_counts_by_rule_and_level, &color_map, + rule_title_path_map, &mut html_output_stock, ); println!(); @@ -789,6 +791,7 @@ fn _print_detection_summary_by_computer( fn _print_detection_summary_tables( detect_counts_by_rule_and_level: HashMap>, color_map: &HashMap, + rule_title_path_map: HashMap, html_output_stock: &mut Vec, ) { let buf_wtr = BufferWriter::stdout(ColorChoice::Always); @@ -818,7 +821,7 @@ fn _print_detection_summary_tables( for x in sorted_detections.iter() { html_output_stock.push(format!( "- {} ({})", - x.0, + rule_title_path_map.get(x.0).unwrap_or(&"".to_string()), x.1.to_formatted_string(&Locale::en) )); } From 9f1794f50ac46b96e9ba2c8c3f34e913f64a6e63 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:28:02 +0900 Subject: [PATCH 21/33] added start time output --- src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.rs b/src/main.rs index d3d71bbd..d3211277 100644 --- a/src/main.rs +++ b/src/main.rs @@ -222,6 +222,13 @@ impl App { println!(); } + write_color_buffer( + &BufferWriter::stdout(ColorChoice::Always), + None, + &format!("Start time: {}\n", analysis_start_time.format("%Y/%m/%d %H:%M")), + true, + ) + .ok(); if configs::CONFIG.read().unwrap().args.live_analysis { let live_analysis_list = self.collect_liveanalysis_files(); if live_analysis_list.is_none() { From 7c11872022b1542730c4d77c220853f921045cd0 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:28:44 +0900 Subject: [PATCH 22/33] cargo fmt --- src/afterfact.rs | 11 ++++++++--- src/main.rs | 10 ++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 9c4f60ed..5aea8995 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -241,7 +241,7 @@ fn emit_csv( HashMap::new(); let mut detect_counts_by_rule_and_level: HashMap> = HashMap::new(); - let mut rule_title_path_map:HashMap = HashMap::new(); + let mut rule_title_path_map: HashMap = HashMap::new(); let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]); // レベル別、日ごとの集計用変数の初期化 for level_init in levels { @@ -748,7 +748,10 @@ fn _print_detection_summary_by_computer( // html出力は各種すべてのコンピュータ名を表示するようにする if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push(format!("### Computers with most unique {} detections:", LEVEL_FULL.get(level.as_str()).unwrap())); + html_output_stock.push(format!( + "### Computers with most unique {} detections:", + LEVEL_FULL.get(level.as_str()).unwrap() + )); for x in sorted_detections.iter() { html_output_stock.push(format!( "- {} ({})", @@ -821,7 +824,9 @@ fn _print_detection_summary_tables( for x in sorted_detections.iter() { html_output_stock.push(format!( "- {} ({})", - rule_title_path_map.get(x.0).unwrap_or(&"".to_string()), + rule_title_path_map + .get(x.0) + .unwrap_or(&"".to_string()), x.1.to_formatted_string(&Locale::en) )); } diff --git a/src/main.rs b/src/main.rs index d3211277..e423d6f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,7 +94,10 @@ impl App { let analysis_start_time: DateTime = Local::now(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { let output_data = vec![ - format!("- Start time: {}", analysis_start_time.format("%Y/%m/%d %H:%M")), + format!( + "- Start time: {}", + analysis_start_time.format("%Y/%m/%d %H:%M") + ), "".to_string(), ]; htmlreport::add_md_data("General Overview".to_string(), output_data); @@ -225,7 +228,10 @@ impl App { write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, - &format!("Start time: {}\n", analysis_start_time.format("%Y/%m/%d %H:%M")), + &format!( + "Start time: {}\n", + analysis_start_time.format("%Y/%m/%d %H:%M") + ), true, ) .ok(); From 974ec45c9f388c9eb7185a69708c4946146454b9 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:19:06 +0900 Subject: [PATCH 23/33] added id attribute to html header tag #689 --- src/afterfact.rs | 7 ++++--- src/detections/detection.rs | 2 +- src/main.rs | 10 ++++------ src/options/htmlreport.rs | 13 ++++++++----- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 5aea8995..13e58316 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -546,7 +546,7 @@ fn emit_csv( } } if html_output_flag { - htmlreport::add_md_data("Results Summary".to_string(), html_output_stock); + htmlreport::add_md_data("Results Summary {#results_summary}".to_string(), html_output_stock); } Ok(()) } @@ -749,7 +749,8 @@ fn _print_detection_summary_by_computer( // html出力は各種すべてのコンピュータ名を表示するようにする if configs::CONFIG.read().unwrap().args.html_report.is_some() { html_output_stock.push(format!( - "### Computers with most unique {} detections:", + "### Computers with most unique {} detections: {{#computers_with_most_unique_{}_detections}}", + LEVEL_FULL.get(level.as_str()).unwrap(), LEVEL_FULL.get(level.as_str()).unwrap() )); for x in sorted_detections.iter() { @@ -804,7 +805,7 @@ fn _print_detection_summary_tables( let mut col_color = vec![]; for level in LEVEL_ABBR.values() { let mut col_output: Vec = vec![]; - let header_output = &format!("Top {} alerts:", LEVEL_FULL.get(level.as_str()).unwrap()); + let header_output = &format!("Top {} alerts: {{#top_{}_alerts}}", LEVEL_FULL.get(level.as_str()).unwrap(), LEVEL_FULL.get(level.as_str()).unwrap()); col_output.push(header_output.to_owned()); col_color.push(_get_table_color( diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 9fea0f6c..b33fc109 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -696,7 +696,7 @@ impl Detection { html_report_stock.push(format!("- {}", tmp_total_detect_output)); } if !html_report_stock.is_empty() { - htmlreport::add_md_data("General Overview".to_string(), html_report_stock); + htmlreport::add_md_data("General Overview {#general_overview}".to_string(), html_report_stock); } } } diff --git a/src/main.rs b/src/main.rs index e423d6f9..489bdad4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,9 +98,8 @@ impl App { "- Start time: {}", analysis_start_time.format("%Y/%m/%d %H:%M") ), - "".to_string(), ]; - htmlreport::add_md_data("General Overview".to_string(), output_data); + htmlreport::add_md_data("General Overview {#general_overview}".to_string(), output_data); } // Show usage when no arguments. @@ -353,8 +352,8 @@ impl App { .ok(); println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - let output_data = vec![format!("- {}", elapsed_output_str), "".to_string()]; - htmlreport::add_md_data("General Overview".to_string(), output_data); + let output_data = vec![format!("- {}", elapsed_output_str)]; + htmlreport::add_md_data("General Overview {#general_overview}".to_string(), output_data); } // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。 if ERROR_LOG_STACK.lock().unwrap().len() > 0 { @@ -575,9 +574,8 @@ impl App { let output_data = vec![ format!("- Analyzed event files: {}", evtx_files.len()), format!("- {}", total_size_output), - "".to_string(), ]; - htmlreport::add_md_data("General Overview".to_string(), output_data); + htmlreport::add_md_data("General Overview #{general_overview}".to_string(), output_data); } let rule_files = detection::Detection::parse_rule_files( diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index cf55a0de..db8e9ebc 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -32,6 +32,9 @@ impl HtmlReporter { pub fn create_html(self) -> String { let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); + options.insert(Options::ENABLE_HEADING_ATTRIBUTES); + options.insert(Options::ENABLE_FOOTNOTES); + let mut md_data = vec![]; for section_name in self.section_order { if let Some(v) = self.md_datas.get(§ion_name) { @@ -43,7 +46,7 @@ impl HtmlReporter { } } } - let md_str = md_data.join(""); + let md_str = md_data.join("\n"); let parser = Parser::new_ext(&md_str, options); let mut ret = String::new(); @@ -62,8 +65,8 @@ impl Default for HtmlReporter { fn get_init_md_data_map() -> (Vec, HashMap>) { let mut ret = HashMap::new(); let section_order = vec![ - "General Overview".to_string(), - "Results Summary".to_string(), + "General Overview {#general_overview}".to_string(), + "Results Summary {#results_summary}".to_string(), ]; for section in section_order.iter() { ret.insert(section.to_owned(), vec![]); @@ -132,7 +135,7 @@ mod tests { ]; html_reporter .md_datas - .insert("General Overview".to_string(), general_data.clone()); + .insert("General Overview {#general_overview}".to_string(), general_data.clone()); let general_overview_str = format!( "
      \n
    • {}
    • \n
    ", general_data[..general_data.len() - 1] @@ -140,7 +143,7 @@ mod tests { .replace("- ", "") ); let expect_str = format!( - "

    General Overview

    \n{}\n

    Results Summary

    \n

    not found data.

    \n", + "

    General Overview

    \n{}\n

    Results Summary

    \n

    not found data.

    \n", general_overview_str ); From 0d439ed3516a06bb3a7c29031c062b13d93b9e14 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:20:00 +0900 Subject: [PATCH 24/33] added url link to rule yml #689 --- src/afterfact.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index 13e58316..e51c8d7a 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -824,10 +824,11 @@ fn _print_detection_summary_tables( html_output_stock.push(format!("### {}", header_output)); for x in sorted_detections.iter() { html_output_stock.push(format!( - "- {} ({})", - rule_title_path_map + "- [{}]({}) ({})", + x.0 + ,rule_title_path_map .get(x.0) - .unwrap_or(&"".to_string()), + .unwrap_or(&"".to_string()).replace('\\', "/"), x.1.to_formatted_string(&Locale::en) )); } From 9e56eb6481314f76802ba64f3bacfa6c63ac0571 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:08:34 +0900 Subject: [PATCH 25/33] added css style reference to hayabusa_report.css in html report #689 --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/lib.rs | 2 ++ src/options/htmlreport.rs | 15 ++++++++++++++- 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 79f40b8c..5b6b3458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -769,6 +769,7 @@ dependencies = [ "hashbrown", "hex", "hhmmss", + "horrorshow", "hyper", "is_elevated", "itertools", @@ -827,6 +828,12 @@ dependencies = [ "time 0.2.27", ] +[[package]] +name = "horrorshow" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371fb981840150b1a54f7cb117bf6699f7466a1d4861daac33bc6fe2b5abea0" + [[package]] name = "http" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index bf36ee69..f1435d16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ num-format = "*" comfy-table = "6.*" pulldown-cmark = { version = "0.9.*", default-features = false, features = ["simd"] } reqwest = {version = "0.11.*", features = ["blocking", "json"]} +horrorshow = "0.8.*" [build-dependencies] static_vcruntime = "2.*" diff --git a/src/lib.rs b/src/lib.rs index 45a8b1e5..db666270 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,5 @@ pub mod timeline; pub mod yaml; #[macro_use] extern crate prettytable; +#[macro_use] +extern crate horrorshow; diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index db8e9ebc..673796f4 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -8,6 +8,8 @@ use std::io::BufWriter; use std::io::Write; use std::path::Path; use std::sync::RwLock; +use horrorshow::prelude::*; +use horrorshow::helper::doctype; lazy_static! { pub static ref HTML_REPORTER: RwLock = RwLock::new(HtmlReporter::new()); @@ -103,7 +105,18 @@ pub fn create_html_file(input_html: String, path_str: String) { return; } let mut html_writer = BufWriter::new(File::create(path).unwrap()); - writeln!(html_writer, "{}", input_html).ok(); + let html_data = format!("{}", html! { + : doctype::HTML; + html { + head { + meta(charset="UTF-8"); + link(rel="stylesheet", type="text/css", href="./hayabusa_report.css"); + } + body : Raw(input_html.clone().as_str()) + } + }); + + writeln!(html_writer,"{}", html_data).ok(); println!( "HTML Report was generated. Please check {} for details.", path_str From 20715bdeefa703f52f43aa5ededd717881df152f Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:09:34 +0900 Subject: [PATCH 26/33] cargo fmt --- src/afterfact.rs | 18 +++++++++++++----- src/detections/detection.rs | 5 ++++- src/main.rs | 25 ++++++++++++++++--------- src/options/htmlreport.rs | 32 ++++++++++++++++++-------------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index e51c8d7a..dc6a8923 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -546,7 +546,10 @@ fn emit_csv( } } if html_output_flag { - htmlreport::add_md_data("Results Summary {#results_summary}".to_string(), html_output_stock); + htmlreport::add_md_data( + "Results Summary {#results_summary}".to_string(), + html_output_stock, + ); } Ok(()) } @@ -805,7 +808,11 @@ fn _print_detection_summary_tables( let mut col_color = vec![]; for level in LEVEL_ABBR.values() { let mut col_output: Vec = vec![]; - let header_output = &format!("Top {} alerts: {{#top_{}_alerts}}", LEVEL_FULL.get(level.as_str()).unwrap(), LEVEL_FULL.get(level.as_str()).unwrap()); + let header_output = &format!( + "Top {} alerts: {{#top_{}_alerts}}", + LEVEL_FULL.get(level.as_str()).unwrap(), + LEVEL_FULL.get(level.as_str()).unwrap() + ); col_output.push(header_output.to_owned()); col_color.push(_get_table_color( @@ -825,10 +832,11 @@ fn _print_detection_summary_tables( for x in sorted_detections.iter() { html_output_stock.push(format!( "- [{}]({}) ({})", - x.0 - ,rule_title_path_map + x.0, + rule_title_path_map .get(x.0) - .unwrap_or(&"".to_string()).replace('\\', "/"), + .unwrap_or(&"".to_string()) + .replace('\\', "/"), x.1.to_formatted_string(&Locale::en) )); } diff --git a/src/detections/detection.rs b/src/detections/detection.rs index 9fb7557b..43581a7c 100644 --- a/src/detections/detection.rs +++ b/src/detections/detection.rs @@ -696,7 +696,10 @@ impl Detection { html_report_stock.push(format!("- {}", tmp_total_detect_output)); } if !html_report_stock.is_empty() { - htmlreport::add_md_data("General Overview {#general_overview}".to_string(), html_report_stock); + htmlreport::add_md_data( + "General Overview {#general_overview}".to_string(), + html_report_stock, + ); } } } diff --git a/src/main.rs b/src/main.rs index 1adb19f8..432f1f28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,13 +93,14 @@ impl App { } let analysis_start_time: DateTime = Local::now(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { - let output_data = vec![ - format!( - "- Start time: {}", - analysis_start_time.format("%Y/%m/%d %H:%M") - ), - ]; - htmlreport::add_md_data("General Overview {#general_overview}".to_string(), output_data); + let output_data = vec![format!( + "- Start time: {}", + analysis_start_time.format("%Y/%m/%d %H:%M") + )]; + htmlreport::add_md_data( + "General Overview {#general_overview}".to_string(), + output_data, + ); } // Show usage when no arguments. @@ -390,7 +391,10 @@ impl App { println!(); if configs::CONFIG.read().unwrap().args.html_report.is_some() { let output_data = vec![format!("- {}", elapsed_output_str)]; - htmlreport::add_md_data("General Overview {#general_overview}".to_string(), output_data); + htmlreport::add_md_data( + "General Overview {#general_overview}".to_string(), + output_data, + ); } // Qオプションを付けた場合もしくはパースのエラーがない場合はerrorのstackが0となるのでエラーログファイル自体が生成されない。 if ERROR_LOG_STACK.lock().unwrap().len() > 0 { @@ -612,7 +616,10 @@ impl App { format!("- Analyzed event files: {}", evtx_files.len()), format!("- {}", total_size_output), ]; - htmlreport::add_md_data("General Overview #{general_overview}".to_string(), output_data); + htmlreport::add_md_data( + "General Overview #{general_overview}".to_string(), + output_data, + ); } let rule_files = detection::Detection::parse_rule_files( diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index 673796f4..fa52f953 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -1,5 +1,7 @@ use crate::detections::message::AlertMessage; use hashbrown::HashMap; +use horrorshow::helper::doctype; +use horrorshow::prelude::*; use lazy_static::lazy_static; use pulldown_cmark::{html, Options, Parser}; use std::fs::create_dir; @@ -8,8 +10,6 @@ use std::io::BufWriter; use std::io::Write; use std::path::Path; use std::sync::RwLock; -use horrorshow::prelude::*; -use horrorshow::helper::doctype; lazy_static! { pub static ref HTML_REPORTER: RwLock = RwLock::new(HtmlReporter::new()); @@ -105,18 +105,21 @@ pub fn create_html_file(input_html: String, path_str: String) { return; } let mut html_writer = BufWriter::new(File::create(path).unwrap()); - let html_data = format!("{}", html! { - : doctype::HTML; - html { - head { - meta(charset="UTF-8"); - link(rel="stylesheet", type="text/css", href="./hayabusa_report.css"); + let html_data = format!( + "{}", + html! { + : doctype::HTML; + html { + head { + meta(charset="UTF-8"); + link(rel="stylesheet", type="text/css", href="./hayabusa_report.css"); + } + body : Raw(input_html.clone().as_str()) } - body : Raw(input_html.clone().as_str()) } - }); + ); - writeln!(html_writer,"{}", html_data).ok(); + writeln!(html_writer, "{}", html_data).ok(); println!( "HTML Report was generated. Please check {} for details.", path_str @@ -146,9 +149,10 @@ mod tests { "- Elapsed Time: 00:00:29.035".to_string(), "".to_string(), ]; - html_reporter - .md_datas - .insert("General Overview {#general_overview}".to_string(), general_data.clone()); + html_reporter.md_datas.insert( + "General Overview {#general_overview}".to_string(), + general_data.clone(), + ); let general_overview_str = format!( "
      \n
    • {}
    • \n
    ", general_data[..general_data.len() - 1] From 7c9c87786e1efc153efb8d697031d29f8373bc6d Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:12:15 +0900 Subject: [PATCH 27/33] added hayabusa_report.css --- hayabusa_report.css | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 hayabusa_report.css diff --git a/hayabusa_report.css b/hayabusa_report.css new file mode 100644 index 00000000..e6b930c9 --- /dev/null +++ b/hayabusa_report.css @@ -0,0 +1,4 @@ +h2 { + background-color: blue; + color: white; +} \ No newline at end of file From 6b51b5368ecaf1a93329d70ba6074e608ba7d4a8 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:49:49 +0900 Subject: [PATCH 28/33] updated rules submodule --- rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules b/rules index 2b0f88d1..aaf910cd 160000 --- a/rules +++ b/rules @@ -1 +1 @@ -Subproject commit 2b0f88d1c09b5b9979b99686a29a244993508210 +Subproject commit aaf910cdcaca32e89b0f81b0af4e180228d21eb6 From fbf43fde337fdf57229495179f00472d96b0d286 Mon Sep 17 00:00:00 2001 From: Yamato Security <71482215+YamatoSecurity@users.noreply.github.com> Date: Tue, 27 Sep 2022 19:36:16 +0900 Subject: [PATCH 29/33] ignore .html and .css files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cdf50523..b32b62ba 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ test_* *.csv *.json *.jsonl -hayabusa* \ No newline at end of file +hayabusa* +*.html +*.htm +*.css \ No newline at end of file From 9d741b7c5c9fe76fcd4f492e412947955ede3d31 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:21:25 +0900 Subject: [PATCH 30/33] fixed output --- src/afterfact.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index dc6a8923..7e57103a 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -808,12 +808,10 @@ fn _print_detection_summary_tables( let mut col_color = vec![]; for level in LEVEL_ABBR.values() { let mut col_output: Vec = vec![]; - let header_output = &format!( - "Top {} alerts: {{#top_{}_alerts}}", - LEVEL_FULL.get(level.as_str()).unwrap(), + col_output.push(format!( + "Top {} alerts:", LEVEL_FULL.get(level.as_str()).unwrap() - ); - col_output.push(header_output.to_owned()); + )); col_color.push(_get_table_color( color_map, @@ -828,7 +826,11 @@ fn _print_detection_summary_tables( // html出力の場合はすべての内容を出力するようにする if configs::CONFIG.read().unwrap().args.html_report.is_some() { - html_output_stock.push(format!("### {}", header_output)); + html_output_stock.push(format!( + "### Top {} alerts: {{#top_{}_alerts}}", + LEVEL_FULL.get(level.as_str()).unwrap(), + LEVEL_FULL.get(level.as_str()).unwrap() + )); for x in sorted_detections.iter() { html_output_stock.push(format!( "- [{}]({}) ({})", From d1851c6a423da56a9595b5ede41ee7824a1793b6 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:30:34 +0900 Subject: [PATCH 31/33] added favicon link --- src/options/htmlreport.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index fa52f953..c1386288 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -113,6 +113,7 @@ pub fn create_html_file(input_html: String, path_str: String) { head { meta(charset="UTF-8"); link(rel="stylesheet", type="text/css", href="./hayabusa_report.css"); + link(rel="icon", type="image/png", href="./favicon.png"); } body : Raw(input_html.clone().as_str()) } From 793f28d7feeda8a51e83dd3aa41f84346a6539e9 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:41:17 +0900 Subject: [PATCH 32/33] changed html report file output check before scanning --- src/main.rs | 12 ++++++++++++ src/options/htmlreport.rs | 9 --------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 432f1f28..d20fc8fe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,6 +262,18 @@ impl App { println!(); } + if let Some(path) = &configs::CONFIG.read().unwrap().args.html_report { + // if already exists same html report file. output alert message and exit + if path.exists() { + AlertMessage::alert(&format!( + " The file {} already exists. Please specify a different filename.", + path.to_str().unwrap() + )) + .ok(); + return; + } + } + write_color_buffer( &BufferWriter::stdout(ColorChoice::Always), None, diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index c1386288..46ea5437 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -95,15 +95,6 @@ pub fn create_html_file(input_html: String, path_str: String) { create_dir(path.parent().unwrap()).ok(); } - // if already exists same html report file. output alert message and exit - if path.exists() { - AlertMessage::alert(&format!( - " The file {} already exists. Please specify a different filename.", - &path_str - )) - .ok(); - return; - } let mut html_writer = BufWriter::new(File::create(path).unwrap()); let html_data = format!( "{}", From 4f4f96470af4611af479e07f70be8804eb7bda65 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:43:42 +0900 Subject: [PATCH 33/33] remove unnecessary use --- src/options/htmlreport.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/options/htmlreport.rs b/src/options/htmlreport.rs index 46ea5437..89826a1e 100644 --- a/src/options/htmlreport.rs +++ b/src/options/htmlreport.rs @@ -1,4 +1,3 @@ -use crate::detections::message::AlertMessage; use hashbrown::HashMap; use horrorshow::helper::doctype; use horrorshow::prelude::*;