Merge pull request #734 from Yamato-Security/724-output-rule-authors-in-standard-out

Added output rule authors in standard out
This commit is contained in:
DustInDark
2022-10-09 11:18:43 +09:00
committed by GitHub
4 changed files with 125 additions and 3 deletions

View File

@@ -4,8 +4,9 @@
**改善:**
- 検知ルールを利用しないオプション(`-M``-L`オプション)の時のメッセージの出力内容を修正した。 (#730) (@hitenkoku)
- より正確な結果を出力するために、チャンネルとEIDの情報を`rules/config/channel_eid_info.txt`に基づいてチェックするようにした。 (#463) (@garigariganzy)
- 検知ルールを利用しないオプション(`-M``-L`オプション)の時のメッセージの出力内容を修正した。 (#730) (@hitenkoku)
- 検出したルールの作者名を標準出力に追加した。 (#724) (@hitenkoku)
**バグ修正:**

View File

@@ -4,12 +4,13 @@
**Enhancements:**
- Do not display a message about loading detection rules when using the `-M` or `-L` options. (#730) (@hitenkoku)
- 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 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]

View File

@@ -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,7 +15,9 @@ use itertools::Itertools;
use krapslog::{build_sparkline, build_time_markers};
use lazy_static::lazy_static;
use linked_hash_map::LinkedHashMap;
use std::path::Path;
use std::str::FromStr;
use yaml_rust::YamlLoader;
use comfy_table::*;
use hashbrown::{HashMap, HashSet};
@@ -236,6 +239,8 @@ fn emit_csv<W: std::io::Write>(
let mut detect_counts_by_rule_and_level: HashMap<String, HashMap<String, i128>> =
HashMap::new();
let mut rule_title_path_map: HashMap<String, String> = HashMap::new();
let mut rule_author_counter: HashMap<String, i128> = HashMap::new();
let levels = Vec::from(["crit", "high", "med ", "low ", "info", "undefined"]);
// レベル別、日ごとの集計用変数の初期化
for level_init in levels {
@@ -339,6 +344,9 @@ fn emit_csv<W: std::io::Write>(
.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,20 @@ fn emit_csv<W: std::io::Write>(
}
};
disp_wtr_buf.clear();
write_color_buffer(
&disp_wtr,
get_writable_color(Some(Color::Rgb(0, 255, 0))),
"Rule Authors:",
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();
write_color_buffer(
@@ -1151,6 +1173,103 @@ fn output_json_str(
}
}
fn output_detected_rule_authors(rule_author_counter: HashMap<String, i128>) {
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..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
));
}
}
output.push(tmp);
}
let mut tbrows = vec![];
for c in output.iter() {
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)
.set_style(TableComponent::MiddleIntersections, hlch)
.set_style(TableComponent::TopBorderIntersections, tbch)
.set_style(TableComponent::BottomBorderIntersections, hlch);
println!("{tb}");
}
/// 与えられたyaml_pathからauthorの名前を抽出して配列で返却する関数
fn extract_author_name(yaml_path: String) -> Vec<String> {
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() {
let authors_vec: Vec<String> = 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();
};
}
// ここまで来た場合は要素がない場合なので空配列を返す
vec![]
}
#[cfg(test)]
mod tests {
use crate::afterfact::_get_serialized_disp_output;

View File

@@ -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, _)| {